Skip to content

Commit ef885dc

Browse files
committed
Close cursors for repeated SQL queries that return NJS errors
1 parent 61d42a8 commit ef885dc

File tree

5 files changed

+84
-3
lines changed

5 files changed

+84
-3
lines changed

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Common Changes
1919
Thin Mode Changes
2020
++++++++++++++++++
2121

22+
#) Fixed bug that throws an 'ORA-01000' error when a query is executed multiple
23+
times even though query returned errors like 'NJS-119', 'NJS-016'.
24+
2225
#) Internal performance optimizations for network buffer and packet handling
2326

2427
#) Ensure that the database port is passed as a number to the network connection.

lib/thin/protocol/messages/base.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,13 @@ class Message {
448448
}
449449
return writtenBytes;
450450
}
451+
452+
// Called when an error is encountered during decode of RPC
453+
saveDeferredErr() {
454+
if (!this.deferredErr) {
455+
this.deferredErr = errors.getErr(...arguments);
456+
}
457+
}
451458
}
452459

453460
module.exports = Message;

lib/thin/protocol/messages/withData.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class MessageWithData extends Message {
5757
this.inFetch = false;
5858
this.parseOnly = false;
5959
this.resultSetsToSetup = [];
60+
this.deferredErr = null;
6061
}
6162

6263
/**
@@ -472,7 +473,7 @@ class MessageWithData extends Message {
472473
if (actualNumBytes < 0 && oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) {
473474
colValue = null;
474475
} else if (actualNumBytes !== 0 && colValue !== null) {
475-
errors.throwErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS);
476+
this.saveDeferredErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS);
476477
}
477478
} else if (oraTypeNum === constants.TNS_DATA_TYPE_LONG || oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW || variable.maxSize > buf.caps.maxStringSize) {
478479
buf.skipSB4(); // null indicator
@@ -525,11 +526,15 @@ class MessageWithData extends Message {
525526
}
526527

527528
async postProcess() {
529+
if (this.deferredErr) {
530+
throw this.deferredErr;
531+
}
532+
528533
if (this.outVariables) {
529534
for (const variable of this.outVariables) {
530535
if (variable.isArray) {
531-
for (let pos = 0; pos < variable.numElementsInArray; pos++) {
532-
if (variable.outConverter) {
536+
if (variable.outConverter) {
537+
for (let pos = 0; pos < variable.numElementsInArray; pos++) {
533538
variable.values[0][pos] = await variable.outConverter(variable.values[0][pos]);
534539
}
535540
}

test/clobPlsqlBindAsString_bindout.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const assert = require('assert');
3636
const fs = require('fs');
3737
const dbConfig = require('./dbconfig.js');
3838
const random = require('./random.js');
39+
const testsUtil = require('./testsUtil.js');
3940

4041
describe('75. clobPlsqlBindAsString_bindout.js', function() {
4142

@@ -162,13 +163,17 @@ describe('75. clobPlsqlBindAsString_bindout.js', function() {
162163
"END nodb_clobs_out_741; ";
163164
const sqlRun = "BEGIN nodb_clobs_out_741 (:i, :c); END;";
164165
const proc_drop = "DROP PROCEDURE nodb_clobs_out_741";
166+
let sysDBAConn;
165167

166168
before(async function() {
167169
await connection.execute(proc);
168170
});
169171

170172
after(async function() {
171173
await connection.execute(proc_drop);
174+
if (sysDBAConn) {
175+
await sysDBAConn.close();
176+
}
172177
});
173178

174179
it('75.1.1 works with EMPTY_LOB', async function() {
@@ -546,6 +551,51 @@ describe('75. clobPlsqlBindAsString_bindout.js', function() {
546551
);
547552
}); // 75.1.28
548553

554+
it('75.1.29 Verify Cursor leak is not there for Bind Error', async function() {
555+
if (!dbConfig.test.DBA_PRIVILEGE) {
556+
this.skip();
557+
}
558+
const len = 32769;
559+
const sequence = insertID++;
560+
const specialStr = "75.1.24.1";
561+
const clobStr = random.getRandomString(len, specialStr);
562+
const bindVar = {
563+
i: { val: sequence, type: oracledb.NUMBER, dir: oracledb.BIND_IN },
564+
c: { type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: len - 1 }
565+
};
566+
await insertClobWithString(sequence, clobStr);
567+
568+
// Remove the sqlRun statement from cache before checking leak.
569+
// It will also let server open any internal cursors for this statement
570+
// and hence the actual cursor count for this statement can be verified
571+
// after running in loop below.
572+
await assert.rejects(
573+
async () => await connection.execute(sqlRun, bindVar, { keepInStmtCache: false}),
574+
/NJS-016:/
575+
);
576+
577+
const sid = await testsUtil.getSid(connection);
578+
const dbaConfig = {
579+
user : dbConfig.test.DBA_user,
580+
password: dbConfig.test.DBA_password,
581+
connectionString: dbConfig.connectString,
582+
privilege: oracledb.SYSDBA
583+
};
584+
sysDBAConn = await oracledb.getConnection(dbaConfig);
585+
const openCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
586+
587+
// Running in loop should not result in opening more than 1 cursor
588+
// additionally on server.
589+
for (let i = 0; i < 15; i++) {
590+
await assert.rejects(
591+
async () => await connection.execute(sqlRun, bindVar),
592+
/NJS-016:/
593+
);
594+
}
595+
const newOpenCount = await testsUtil.getOpenCursorCount(sysDBAConn, sid);
596+
assert.strictEqual(newOpenCount - openCount, 1);
597+
}); // 75.1.29
598+
549599
}); // 75.1
550600

551601
describe('75.2 CLOB, PLSQL, BIND_OUT to VARCHAR2', function() {

test/testsUtil.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,22 @@ testsUtil.getRoundTripCount = async function(sid) {
280280
}
281281
};
282282

283+
testsUtil.getMaxCursorsConfigured = async function(conn) {
284+
const sql = `SELECT value FROM v$parameter WHERE name = 'open_cursors'`;
285+
const result = await conn.execute(sql);
286+
return Number(result.rows[0][0]);// Max Cursors config
287+
};
288+
289+
testsUtil.getOpenCursorCount = async function(systemconn, sid) {
290+
const sql = `
291+
select ss.value
292+
from v$sesstat ss, v$statname sn
293+
where ss.sid = :sid
294+
and ss.statistic# = sn.statistic#
295+
and sn.name = 'opened cursors current'`;
296+
const result = await systemconn.execute(sql, [sid]);
297+
return result.rows[0][0]; // open cursor count so far in the session
298+
};
283299

284300
testsUtil.getParseCount = async function(systemconn, sid) {
285301
const sql = `

0 commit comments

Comments
 (0)