Skip to content

Commit d909a3a

Browse files
committed
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)
1 parent 04b4bce commit d909a3a

File tree

11 files changed

+166
-2
lines changed

11 files changed

+166
-2
lines changed

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ node-oracledb `v6.5.1 <https://github.com/oracle/node-oracledb/compare/v6.5.0...
1212

1313
Thin Mode Changes
1414
+++++++++++++++++
15+
#) Fixed issue which throws the `ORA-00932` error, when the same SELECT SQL
16+
statement is run for the second time with a different bind type.
17+
See `Issue #1669 <https://github.com/oracle/node-oracledb/issues/1669>`__.
1518

1619
#) Fixed exponent check condition for out-of-bounds number.
1720
See `Issue #1659 <https://github.com/oracle/node-oracledb/issues/1659>`__.

lib/errors.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ const util = require('util');
3131
// define error prefix for all messages
3232
const ERR_PREFIX = "NJS";
3333

34+
const ERR_INTEGRITY_ERROR_CODES = [
35+
1, // unique constraint violated
36+
1400, // cannot insert NULL
37+
1438, // value larger than specified precision
38+
2290, // check constraint violated
39+
2291, // integrity constraint violated - parent key not found
40+
2292, // integrity constraint violated - child record found
41+
21525, // attribute or collection element violated its constraints
42+
40479, // internal JSON serializer error
43+
];
44+
3445
// define error number constants (used in JavaScript library)
3546
const ERR_INVALID_POOL = 2;
3647
const ERR_INVALID_CONNECTION = 3;
@@ -695,6 +706,7 @@ function transformErr(err, fnOpt) {
695706

696707
// define exports
697708
module.exports = {
709+
ERR_INTEGRITY_ERROR_CODES,
698710
ERR_INVALID_POOL,
699711
ERR_INVALID_CONNECTION,
700712
ERR_INVALID_PROPERTY_VALUE,

lib/thin/protocol/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,7 @@ module.exports = {
724724
TNS_XML_TYPE_FLAG_SKIP_NEXT_4: 0x100000,
725725

726726
// errors
727+
TNS_ERR_INCONSISTENT_DATA_TYPES: 932,
727728
TNS_ERR_VAR_NOT_IN_SELECT_LIST: 1007,
728729
TNS_ERR_INBAND_MESSAGE: 12573,
729730
TNS_ERR_INVALID_SERVICE_NAME: 12514,

lib/thin/protocol/messages/withData.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,18 @@ class MessageWithData extends Message {
113113
this.errorInfo.num = 0;
114114
this.errorOccurred = false;
115115
this.statement.moreRowsToFetch = false;
116+
} else if (this.retry) {
117+
this.retry = false;
118+
} else if (this.statement.isQuery &&
119+
(this.errorInfo.num === constants.TNS_ERR_VAR_NOT_IN_SELECT_LIST
120+
|| this.errorInfo.num === constants.TNS_ERR_INCONSISTENT_DATA_TYPES)) {
121+
this.retry = true;
122+
this.connection.statementCache.clearCursor(this.statement);
116123
} else if (this.errorInfo.num !== 0 && this.errorInfo.cursorId !== 0) {
117-
this.connection.statementCache._cachedStatements.delete(this.statement.sql);
118-
this.statement.returnToCache = false;
124+
if (!errors.ERR_INTEGRITY_ERROR_CODES.includes(this.errorInfo.num)) {
125+
this.connection.statementCache.clearCursor(this.statement);
126+
this.statement.returnToCache = false;
127+
}
119128
}
120129
if (this.errorInfo.batchErrors) {
121130
this.errorOccurred = false;

lib/thin/protocol/protocol.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ class Protocol {
174174
if (callTimeoutExpired) {
175175
errors.throwErr(errors.ERR_CALL_TIMEOUT_EXCEEDED, this.callTimeout);
176176
}
177+
if (message.retry) {
178+
message.errorOccurred = false;
179+
return await this._processMessage(message);
180+
}
177181
let err = new Error(message.errorInfo.message);
178182
err.offset = message.errorInfo.pos;
179183
err.errorNum = message.errorInfo.num;

lib/thin/statement.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
const { Buffer } = require('buffer');
3030
const constants = require('../constants');
3131
const errors = require('../errors');
32+
const protoConstants = require('./protocol/constants');
3233

3334
/**
3435
* It is used to cache the metadata about bind information
@@ -519,6 +520,9 @@ class Statement {
519520
// cursor also requires a full execute.
520521
//---------------------------------------------------------------------------
521522
_setVariable(bindInfo, variable) {
523+
if (variable.type._oraTypeNum === protoConstants.TNS_DATA_TYPE_CURSOR) {
524+
this.requiresFullExecute = true;
525+
}
522526
if (variable.maxSize !== bindInfo.maxSize
523527
|| variable.dir !== bindInfo.dir
524528
|| variable.isArray !== bindInfo.isArray

lib/thin/statementCache.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ class StatementCache {
146146
return stmt;
147147
}
148148

149+
clearCursor(statement) {
150+
this._addCursorToClose(statement);
151+
statement.cursorId = 0;
152+
}
153+
149154
//---------------------------------------------------------------------------
150155
// returnStatement()
151156
// Return the statement to the statement cache, if applicable. If the

test/binding.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,4 +1119,72 @@ describe('4. binding.js', function() {
11191119
}
11201120
});
11211121
});
1122+
1123+
describe('4.15 binding different data types for same sql', function() {
1124+
let connection;
1125+
let sysDBAConn;
1126+
let sid;
1127+
const numIters = 40;
1128+
1129+
before(async function() {
1130+
if (!dbConfig.test.DBA_PRIVILEGE) this.skip();
1131+
const dbaConfig = {
1132+
user: dbConfig.test.DBA_user,
1133+
password: dbConfig.test.DBA_password,
1134+
connectionString: dbConfig.connectString,
1135+
privilege: oracledb.SYSDBA
1136+
};
1137+
sysDBAConn = await oracledb.getConnection(dbaConfig);
1138+
connection = await oracledb.getConnection(dbConfig);
1139+
sid = await testsUtil.getSid(connection);
1140+
});
1141+
1142+
after(async function() {
1143+
if (connection) {
1144+
await connection.close();
1145+
}
1146+
if (sysDBAConn) {
1147+
await sysDBAConn.close();
1148+
}
1149+
});
1150+
1151+
it('4.15.1 change bindtypes using bindByPosition for queries',
1152+
async function() {
1153+
const sql = 'SELECT :1 FROM DUAL';
1154+
const dt = new Date();
1155+
const openCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
1156+
for (let i = 0; i < numIters; i++) {
1157+
let result = await connection.execute(sql, [1]);
1158+
assert.strictEqual(result.rows[0][0], 1);
1159+
result = await connection.execute(sql, [dt]);
1160+
assert.deepStrictEqual(result.rows[0][0], dt);
1161+
result = await connection.execute(sql, [2]);
1162+
assert.strictEqual(result.rows[0][0], 2);
1163+
}
1164+
const newOpenCount = await testsUtil.
1165+
getOpenCursorCount(sysDBAConn, sid);
1166+
1167+
// ensure cursors are not linearly opened as numIters causing leak.
1168+
assert(newOpenCount - openCount < 4);
1169+
});
1170+
1171+
it('4.15.2 change bindtypes using bindByName for queries',
1172+
async function() {
1173+
const sql = 'SELECT :x FROM DUAL';
1174+
const openCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
1175+
const dt = new Date();
1176+
for (let i = 0; i < numIters; i++) {
1177+
let result = await connection.execute(sql, {x: 1});
1178+
assert.strictEqual(result.rows[0][0], 1);
1179+
result = await connection.execute(sql, {x: dt});
1180+
assert.deepStrictEqual(result.rows[0][0], dt);
1181+
result = await connection.execute(sql, {x: 2});
1182+
assert.strictEqual(result.rows[0][0], 2);
1183+
}
1184+
const newOpenCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
1185+
1186+
// ensure cursors are not linearly opened as numIters causing leak.
1187+
assert(newOpenCount - openCount < 4);
1188+
});
1189+
});
11221190
});

test/dmlReturning.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,5 +515,29 @@ describe('6. dmlReturning.js', function() {
515515
// NJS-011: encountered bind value and type mismatch
516516
});
517517

518+
it('6.2.10 INSERT statement, check re-execute by changing the out bindtype', async function() {
519+
let id = 50;
520+
let sql = `INSERT INTO ${tableName} VALUES (${id}, TO_DATE('2015-01-11','YYYY-DD-MM'))
521+
RETURNING num, content INTO :rnum, :rcontent`;
522+
const dt = new Date('2015-11-01 00:00:00');
523+
const bindVar =
524+
{
525+
rnum: { type: oracledb.NUMBER, dir: oracledb.BIND_OUT },
526+
rcontent: { type: oracledb.DATE, dir: oracledb.BIND_OUT }
527+
};
528+
529+
// outbind as date.
530+
let result = await connection.execute(sql, bindVar);
531+
assert.strictEqual(result.outBinds.rcontent[0].getTime(), dt.getTime());
532+
533+
// Change outbind type to string
534+
bindVar.rcontent.type = oracledb.STRING;
535+
id = 51;
536+
sql = `INSERT INTO ${tableName} VALUES (${id}, TO_DATE('2015-01-11','YYYY-DD-MM'))
537+
RETURNING num, content INTO :rnum, :rcontent`;
538+
result = await connection.execute(sql, bindVar);
539+
assert.strictEqual(result.outBinds.rcontent[0], '01-NOV-15');
540+
});
541+
518542
}); // 6.2
519543
});

test/list.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Overview of node-oracledb functional tests
222222
6.2.7 DELETE statement, single row matched, Object binding format
223223
6.2.8 DELETE statement, multiple rows matched, Array binding format
224224
6.2.9 Negative test - bind value and type mismatch
225+
6.2.10 INSERT statement, check re-execute by changing the out bindtype
225226

226227
7. autoCommit.js
227228
7.1 autoCommit takes effect when setting oracledb.autoCommit before connecting
@@ -4804,6 +4805,7 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
48044805
239.4 implicit binding type
48054806
239.5 check REF CURSOR round-trips with no prefetching
48064807
239.6 check REF CURSOR round-trips with prefetching
4808+
239.7 check REF CURSOR bind with re-execute
48074809

48084810
240. errorOffset.js
48094811
240.1 checks the offset value of the error

0 commit comments

Comments
 (0)