Fixed issue which throws an error, when the same SELECT SQL statement is run for the second time with a different bind type. (Issue #1669)

This commit is contained in:
Sharad Chandran R 2024-05-22 16:07:30 +05:30
parent 04b4bcea0a
commit d909a3afeb
11 changed files with 166 additions and 2 deletions

View File

@ -12,6 +12,9 @@ node-oracledb `v6.5.1 <https://github.com/oracle/node-oracledb/compare/v6.5.0...
Thin Mode Changes
+++++++++++++++++
#) Fixed issue which throws the `ORA-00932` error, when the same SELECT SQL
statement is run for the second time with a different bind type.
See `Issue #1669 <https://github.com/oracle/node-oracledb/issues/1669>`__.
#) Fixed exponent check condition for out-of-bounds number.
See `Issue #1659 <https://github.com/oracle/node-oracledb/issues/1659>`__.

View File

@ -31,6 +31,17 @@ const util = require('util');
// define error prefix for all messages
const ERR_PREFIX = "NJS";
const ERR_INTEGRITY_ERROR_CODES = [
1, // unique constraint violated
1400, // cannot insert NULL
1438, // value larger than specified precision
2290, // check constraint violated
2291, // integrity constraint violated - parent key not found
2292, // integrity constraint violated - child record found
21525, // attribute or collection element violated its constraints
40479, // internal JSON serializer error
];
// define error number constants (used in JavaScript library)
const ERR_INVALID_POOL = 2;
const ERR_INVALID_CONNECTION = 3;
@ -695,6 +706,7 @@ function transformErr(err, fnOpt) {
// define exports
module.exports = {
ERR_INTEGRITY_ERROR_CODES,
ERR_INVALID_POOL,
ERR_INVALID_CONNECTION,
ERR_INVALID_PROPERTY_VALUE,

View File

@ -724,6 +724,7 @@ module.exports = {
TNS_XML_TYPE_FLAG_SKIP_NEXT_4: 0x100000,
// errors
TNS_ERR_INCONSISTENT_DATA_TYPES: 932,
TNS_ERR_VAR_NOT_IN_SELECT_LIST: 1007,
TNS_ERR_INBAND_MESSAGE: 12573,
TNS_ERR_INVALID_SERVICE_NAME: 12514,

View File

@ -113,9 +113,18 @@ class MessageWithData extends Message {
this.errorInfo.num = 0;
this.errorOccurred = false;
this.statement.moreRowsToFetch = false;
} else if (this.retry) {
this.retry = false;
} else if (this.statement.isQuery &&
(this.errorInfo.num === constants.TNS_ERR_VAR_NOT_IN_SELECT_LIST
|| this.errorInfo.num === constants.TNS_ERR_INCONSISTENT_DATA_TYPES)) {
this.retry = true;
this.connection.statementCache.clearCursor(this.statement);
} else if (this.errorInfo.num !== 0 && this.errorInfo.cursorId !== 0) {
this.connection.statementCache._cachedStatements.delete(this.statement.sql);
this.statement.returnToCache = false;
if (!errors.ERR_INTEGRITY_ERROR_CODES.includes(this.errorInfo.num)) {
this.connection.statementCache.clearCursor(this.statement);
this.statement.returnToCache = false;
}
}
if (this.errorInfo.batchErrors) {
this.errorOccurred = false;

View File

@ -174,6 +174,10 @@ class Protocol {
if (callTimeoutExpired) {
errors.throwErr(errors.ERR_CALL_TIMEOUT_EXCEEDED, this.callTimeout);
}
if (message.retry) {
message.errorOccurred = false;
return await this._processMessage(message);
}
let err = new Error(message.errorInfo.message);
err.offset = message.errorInfo.pos;
err.errorNum = message.errorInfo.num;

View File

@ -29,6 +29,7 @@
const { Buffer } = require('buffer');
const constants = require('../constants');
const errors = require('../errors');
const protoConstants = require('./protocol/constants');
/**
* It is used to cache the metadata about bind information
@ -519,6 +520,9 @@ class Statement {
// cursor also requires a full execute.
//---------------------------------------------------------------------------
_setVariable(bindInfo, variable) {
if (variable.type._oraTypeNum === protoConstants.TNS_DATA_TYPE_CURSOR) {
this.requiresFullExecute = true;
}
if (variable.maxSize !== bindInfo.maxSize
|| variable.dir !== bindInfo.dir
|| variable.isArray !== bindInfo.isArray

View File

@ -146,6 +146,11 @@ class StatementCache {
return stmt;
}
clearCursor(statement) {
this._addCursorToClose(statement);
statement.cursorId = 0;
}
//---------------------------------------------------------------------------
// returnStatement()
// Return the statement to the statement cache, if applicable. If the

View File

@ -1119,4 +1119,72 @@ describe('4. binding.js', function() {
}
});
});
describe('4.15 binding different data types for same sql', function() {
let connection;
let sysDBAConn;
let sid;
const numIters = 40;
before(async function() {
if (!dbConfig.test.DBA_PRIVILEGE) this.skip();
const dbaConfig = {
user: dbConfig.test.DBA_user,
password: dbConfig.test.DBA_password,
connectionString: dbConfig.connectString,
privilege: oracledb.SYSDBA
};
sysDBAConn = await oracledb.getConnection(dbaConfig);
connection = await oracledb.getConnection(dbConfig);
sid = await testsUtil.getSid(connection);
});
after(async function() {
if (connection) {
await connection.close();
}
if (sysDBAConn) {
await sysDBAConn.close();
}
});
it('4.15.1 change bindtypes using bindByPosition for queries',
async function() {
const sql = 'SELECT :1 FROM DUAL';
const dt = new Date();
const openCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
for (let i = 0; i < numIters; i++) {
let result = await connection.execute(sql, [1]);
assert.strictEqual(result.rows[0][0], 1);
result = await connection.execute(sql, [dt]);
assert.deepStrictEqual(result.rows[0][0], dt);
result = await connection.execute(sql, [2]);
assert.strictEqual(result.rows[0][0], 2);
}
const newOpenCount = await testsUtil.
getOpenCursorCount(sysDBAConn, sid);
// ensure cursors are not linearly opened as numIters causing leak.
assert(newOpenCount - openCount < 4);
});
it('4.15.2 change bindtypes using bindByName for queries',
async function() {
const sql = 'SELECT :x FROM DUAL';
const openCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
const dt = new Date();
for (let i = 0; i < numIters; i++) {
let result = await connection.execute(sql, {x: 1});
assert.strictEqual(result.rows[0][0], 1);
result = await connection.execute(sql, {x: dt});
assert.deepStrictEqual(result.rows[0][0], dt);
result = await connection.execute(sql, {x: 2});
assert.strictEqual(result.rows[0][0], 2);
}
const newOpenCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
// ensure cursors are not linearly opened as numIters causing leak.
assert(newOpenCount - openCount < 4);
});
});
});

View File

@ -515,5 +515,29 @@ describe('6. dmlReturning.js', function() {
// NJS-011: encountered bind value and type mismatch
});
it('6.2.10 INSERT statement, check re-execute by changing the out bindtype', async function() {
let id = 50;
let sql = `INSERT INTO ${tableName} VALUES (${id}, TO_DATE('2015-01-11','YYYY-DD-MM'))
RETURNING num, content INTO :rnum, :rcontent`;
const dt = new Date('2015-11-01 00:00:00');
const bindVar =
{
rnum: { type: oracledb.NUMBER, dir: oracledb.BIND_OUT },
rcontent: { type: oracledb.DATE, dir: oracledb.BIND_OUT }
};
// outbind as date.
let result = await connection.execute(sql, bindVar);
assert.strictEqual(result.outBinds.rcontent[0].getTime(), dt.getTime());
// Change outbind type to string
bindVar.rcontent.type = oracledb.STRING;
id = 51;
sql = `INSERT INTO ${tableName} VALUES (${id}, TO_DATE('2015-01-11','YYYY-DD-MM'))
RETURNING num, content INTO :rnum, :rcontent`;
result = await connection.execute(sql, bindVar);
assert.strictEqual(result.outBinds.rcontent[0], '01-NOV-15');
});
}); // 6.2
});

View File

@ -222,6 +222,7 @@ Overview of node-oracledb functional tests
6.2.7 DELETE statement, single row matched, Object binding format
6.2.8 DELETE statement, multiple rows matched, Array binding format
6.2.9 Negative test - bind value and type mismatch
6.2.10 INSERT statement, check re-execute by changing the out bindtype
7. autoCommit.js
7.1 autoCommit takes effect when setting oracledb.autoCommit before connecting
@ -4804,6 +4805,7 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
239.4 implicit binding type
239.5 check REF CURSOR round-trips with no prefetching
239.6 check REF CURSOR round-trips with prefetching
239.7 check REF CURSOR bind with re-execute
240. errorOffset.js
240.1 checks the offset value of the error

View File

@ -263,4 +263,36 @@ describe('239. plsqlBindCursorsIN.js', () => {
((oracledb.thin) ? assert.strictEqual(rt, 3) : assert.strictEqual(rt, 2));
}); // 239.6
it('239.7 check REF CURSOR bind with re-execute ', async () => {
const refCursorOptions = {
resultSet: true,
};
let result = await conn.execute(sqlRefCursor, [], refCursorOptions);
const plsql = `begin ${procName1}(:bv); end;`;
await conn.execute(
plsql,
{
bv: {val: result.resultSet, type: oracledb.CURSOR, dir: oracledb.BIND_IN, maxSize: 10 }
}
);
result = await conn.execute(sqlRefCursor, [], refCursorOptions);
// Check Re-execute.
await conn.execute(
plsql,
{
bv: {val: result.resultSet, type: oracledb.CURSOR, dir: oracledb.BIND_IN, maxSize: 10 }
}
);
await result.resultSet.close();
const sqlQuery = `select * from ${tableName}`;
const queryResult = await conn.execute(sqlQuery);
assert.strictEqual(queryResult.rows.length, 3);
}); // 239.7
});