Skip to content

Commit

Permalink
Code refactoring + bug fixes for cursor leaks and SODA lock setting
Browse files Browse the repository at this point in the history
  • Loading branch information
sharadraju committed Sep 22, 2023
1 parent 6602843 commit d045f4c
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 208 deletions.
3 changes: 3 additions & 0 deletions doc/src/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Thin Mode Changes
in embedded quotes and in JSON syntax.
`Issue #1605 <https://github.com/oracle/node-oracledb/issues/1605>`__.

#) Corrected bug that caused cursors to be leaked when calling
Connection.getStatementInfo().

#) Fixed bug that caused an exception to be thrown unnecessarily when a connection was closed.
`Issue #1604 <https://github.com/oracle/node-oracledb/issues/1604>`__.

Expand Down
255 changes: 117 additions & 138 deletions lib/thin/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,98 @@ class ThinConnectionImpl extends ConnectionImpl {
}
}

//---------------------------------------------------------------------------
// _execute()
//
// Calls the RPC that executes a SQL statement and returns the results.
//---------------------------------------------------------------------------
async _execute(statement, numIters, binds, options, executeManyFlag) {

// perform binds
const numBinds = statement.bindInfoList.length;
const numVars = binds.length;
if (numBinds !== numVars && (numVars === 0 || !binds[0].name)) {
errors.throwErr(errors.ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, numBinds, numVars);
}
for (let i = 0; i < binds.length; i++) {
await this._bind(statement, binds[i], i + 1);
}
if (statement.isPlSql && (options.batchErrors || options.dmlRowCounts)) {
errors.throwErr(errors.ERR_EXEC_MODE_ONLY_FOR_DML);
}

// send database request
const message = new messages.ExecuteMessage(this, statement, options);
message.numExecs = numIters;
message.arrayDmlRowCounts = options.dmlRowCounts;
message.batchErrors = options.batchErrors;
if (statement.isPlSql && statement.requiresFullExecute) {
message.numExecs = 1;
await this._protocol._processMessage(message);
statement.requiresFullExecute = false;
message.numExecs = numIters - 1;
message.offset = 1;
}
if (message.numExecs > 0) {
await this._protocol._processMessage(message);
statement.requiresFullExecute = false;
}

// if a define is required, send an additional request to the database
if (statement.requiresDefine && statement.sql) {
statement.requiresFullExecute = true;
await this._protocol._processMessage(message);
statement.requiresFullExecute = false;
statement.requiresDefine = false;
}

// process results
const result = {};
if (statement.numQueryVars > 0) {
result.resultSet = message.resultSet;
} else {
statement.bufferRowIndex = 0;
const outBinds = thinUtil.getOutBinds(statement, numIters,
executeManyFlag);
if (outBinds) {
result.outBinds = outBinds;
}
if (executeManyFlag) {
if (!statement.isPlSql) {
result.rowsAffected = statement.rowCount;
delete statement.rowCount;
}
if (options.dmlRowCounts) {
result.dmlRowCounts = options.dmlRowCounts;
}
if (options.batchErrors) {
result.batchErrors = options.batchErrors;
}
} else {
if (statement.isPlSql && options.implicitResultSet) {
result.implicitResults = options.implicitResultSet;
}
if (statement.lastRowid) {
result.lastRowid = statement.lastRowid;
delete statement.lastRowid;
}
if (statement.isPlSql) {
if (statement.rowCount) {
result.rowsAffected = statement.rowCount;
}
} else {
result.rowsAffected = statement.rowCount || 0;
}
if (statement.rowCount) {
delete statement.rowCount;
}
}
this._returnStatement(statement);
}

return result;
}

//---------------------------------------------------------------------------
// _parseElementType()
//
Expand Down Expand Up @@ -778,26 +870,6 @@ class ThinConnectionImpl extends ConnectionImpl {
return resultSet;
}

//---------------------------------------------------------------------------
// Prepares the sql given by user and binds the value to the statement object
//---------------------------------------------------------------------------
async _prepareAndBind(sql, binds, options, isParse = false) {
const stmt = this._prepare(sql, options);
let position = 0;
if (!isParse) {
const numBinds = stmt.bindInfoList.length;
const numVars = binds.length;
if (numBinds !== numVars && (numVars === 0 || !binds[0].name)) {
errors.throwErr(errors.ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, numBinds, numVars);
}
for (const variable of binds) {
await this._bind(stmt, variable, position + 1);
position += 1;
}
}
return stmt;
}

//---------------------------------------------------------------------------
// Clears the statement cache for the connection
//---------------------------------------------------------------------------
Expand All @@ -818,139 +890,46 @@ class ThinConnectionImpl extends ConnectionImpl {
}

//---------------------------------------------------------------------------
// Parses the sql given by User
// calls the OAL8 RPC that parses the SQL statement and returns the metadata
// information for a statment.
// getStatementInfo()
//
// Parses the SQL statement and returns information about the statement.
//---------------------------------------------------------------------------
async getStatementInfo(sql) {
const options = {};
const result = {};
const statement = await this._prepareAndBind(sql, null, options, true);
const statement = this._prepare(sql, options);
options.connection = this;
// parse the statement (but not for DDL which doesn't support it)
if (!statement.isDdl) {
const message = new messages.ExecuteMessage(this, statement, options);
message.parseOnly = true;
await this._protocol._processMessage(message);
}
if (statement.numQueryVars > 0) {
result.metaData = thinUtil.getMetadataMany(statement.queryVars);
}
result.bindNames = Array.from(statement.bindInfoDict.keys());
result.statementType = statement.statementType;
return result;
}

//---------------------------------------------------------------------------
// Prepares the sql given by the user,
// calls the OAL8 RPC that executes a SQL statement and returns the results.
//---------------------------------------------------------------------------
async execute(sql, numIters, binds, options, executeManyFlag) {
const result = {};
if (executeManyFlag) {
return await this.executeMany(sql, numIters, binds, options);
}
const statement = await this._prepareAndBind(sql, binds, options);

// send the initial request to the database
const message = new messages.ExecuteMessage(this, statement, options);
message.numExecs = 1;
await this._protocol._processMessage(message);
statement.requiresFullExecute = false;

// if a define is required, send an additional request to the database
if (statement.requiresDefine && statement.sql) {
statement.requiresFullExecute = true;
await this._protocol._processMessage(message);
statement.requiresFullExecute = false;
statement.requiresDefine = false;
}

// process message results
if (statement.numQueryVars > 0) {
result.resultSet = message.resultSet;
} else {
statement.bufferRowIndex = 0;
const bindVars = thinUtil.getBindVars(statement);
const outBinds = thinUtil.getExecuteOutBinds(bindVars);
if (outBinds) {
result.outBinds = outBinds;
}
if (statement.isPlSql) {
if (options.implicitResultSet) {
result.implicitResults = options.implicitResultSet;
}
}
if (statement.lastRowid) {
result.lastRowid = statement.lastRowid;
delete statement.lastRowid;
}
if (statement.isPlSql) {
if (statement.rowCount) {
result.rowsAffected = statement.rowCount;
}
} else {
result.rowsAffected = statement.rowCount || 0;
try {
if (!statement.isDdl) {
const message = new messages.ExecuteMessage(this, statement, options);
message.parseOnly = true;
await this._protocol._processMessage(message);
}
if (statement.rowCount) {
delete statement.rowCount;
if (statement.numQueryVars > 0) {
result.metaData = thinUtil.getMetadataMany(statement.queryVars);
}
result.bindNames = Array.from(statement.bindInfoDict.keys());
result.statementType = statement.statementType;
return result;
} finally {
this._returnStatement(statement);
}

return result;
}

//---------------------------------------------------------------------------
// executeMany()
// execute()
//
// Prepares the sql given by the user, calls the OAL8 RPC that executes a SQL
// statement multiple times and returns the results.
// Calls the RPC that executes a SQL statement and returns the results.
//---------------------------------------------------------------------------
async executeMany(sql, numIters, binds, options) {
const statement = await this._prepareAndBind(sql, binds, options);
if (statement.isPlSql && (options.batchErrors || options.dmlRowCounts)) {
errors.throwErr(errors.ERR_EXEC_MODE_ONLY_FOR_DML);
}

// send database request
const message = new messages.ExecuteMessage(this, statement, options);
message.numExecs = numIters;
message.arrayDmlRowCounts = options.dmlRowCounts;
message.batchErrors = options.batchErrors;
if (statement.isPlSql && statement.requiresFullExecute) {
message.numExecs = 1;
await this._protocol._processMessage(message);
statement.requiresFullExecute = false;
message.numExecs = numIters - 1;
message.offset = 1;
}
if (message.numExecs > 0) {
await this._protocol._processMessage(message);
}

// process results
const returnObj = {};
statement.bufferRowIndex = 0;
const bindVars = thinUtil.getBindVars(statement);
const outBinds = thinUtil.getExecuteManyOutBinds(bindVars, numIters);
if (outBinds) {
returnObj.outBinds = outBinds;
}
const rowsAffected = !statement.isPlSql ? statement.rowCount : undefined;
if (rowsAffected) {
returnObj.rowsAffected = rowsAffected;
delete statement.rowCount;
}
if (options.dmlRowCounts) {
returnObj.dmlRowCounts = options.dmlRowCounts;
}
if (options.batchErrors) {
returnObj.batchErrors = options.batchErrors;
async execute(sql, numIters, binds, options, executeManyFlag) {
const statement = this._prepare(sql, options);
try {
return await this._execute(statement, numIters, binds, options,
executeManyFlag);
} catch (err) {
this._returnStatement(statement);
throw err;
}
this._returnStatement(statement);

return returnObj;
}

//---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion lib/thin/protocol/messages/withData.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ class MessageWithData extends Message {
this.errorOccurred = false;
this.statement.moreRowsToFetch = false;
} else if (this.errorInfo.num !== 0 && this.errorInfo.cursorId !== 0) {
this.connection._addCursorToClose(this.statement.cursorId);
this.connection.statementCache.delete(this.statement.sql);
this.statement.returnToCache = false;
}
if (this.errorInfo.batchErrors) {
this.errorOccurred = false;
Expand Down
Loading

0 comments on commit d045f4c

Please sign in to comment.