Skip to content

Commit 8bdf4c2

Browse files
committed
Ensure prefetchRows is used for REF CURSORS
1 parent a7c7f2f commit 8bdf4c2

File tree

7 files changed

+199
-11
lines changed

7 files changed

+199
-11
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
`oracledb.OUT_FORMAT_OBJECT` mode, allowing all columns to be represented in
1818
the JavaScript object.
1919

20+
- The value of `prefetchRows` set when getting a REF CURSOR as a BIND_OUT
21+
parameter is now used in the subsequent data retrieval from that cursor.
22+
2023
- Fixed a compatibility regression affecting SODA "get" operations using older
2124
Oracle Client releases.
2225

doc/api.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,10 +1798,11 @@ to return query results, see [Tuning Fetch Performance](#rowfetching).
17981798
The `prefetchRows` value is ignored in some cases, such as when the query
17991799
involves a LOB.
18001800

1801-
If you fetch a REF CURSOR and subsequently pass it back to a PL/SQL block, then
1802-
set `prefetchRows` to 0 during the initial query to ensure that rows are not
1803-
internally fetched from the REF CURSOR by node-oracledb thus making them
1804-
unavailable in the PL/SQL code.
1801+
If you fetch a REF CURSOR, retrieve rows from that cursor, and then pass it back
1802+
to a PL/SQL block, you should set `prefetchRows` to 0 during the initial
1803+
statement that gets the REF CURSOR. This ensures that rows are not internally
1804+
fetched from the REF CURSOR by node-oracledb thus making them unavailable in the
1805+
final PL/SQL code.
18051806

18061807
The default value is 2.
18071808

src/njsVariable.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ static bool njsVariable_setInvalidBind(njsVariable *var, uint32_t pos,
4444
bool njsVariable_createBuffer(njsVariable *var, njsConnection *conn,
4545
njsBaton *baton)
4646
{
47+
dpiData *data;
48+
uint32_t i;
49+
4750
// if the variable is not an array use the bind array size
4851
if (!var->isArray)
4952
var->maxArraySize = baton->bindArraySize;
@@ -116,6 +119,20 @@ bool njsVariable_createBuffer(njsVariable *var, njsConnection *conn,
116119
&var->buffer->dpiVarData) < 0)
117120
return njsBaton_setErrorDPI(baton);
118121

122+
// for cursors, set the prefetch value, if it differs from the default;
123+
// also mark the variable as not null in order for the prefetch rows to
124+
// take effect
125+
if (var->nativeTypeNum == DPI_NATIVE_TYPE_STMT &&
126+
baton->prefetchRows != DPI_DEFAULT_PREFETCH_ROWS) {
127+
for (i = 0; i < var->maxArraySize; i++) {
128+
data = &var->buffer->dpiVarData[i];
129+
data->isNull = 0;
130+
if (dpiStmt_setPrefetchRows(data->value.asStmt,
131+
baton->prefetchRows) < 0)
132+
return njsBaton_setErrorDPI(baton);
133+
}
134+
}
135+
119136
return true;
120137
}
121138

test/list.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3760,6 +3760,9 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
37603760
147.12 Negative - null
37613761
147.13 Negative - random string
37623762
147.14 Negative - Boolean
3763+
147.15 Query round-trips with no prefetch
3764+
147.16 Query round-trips with prefetch equal to row count
3765+
147.17 Query round-trips with prefetch larger than row count
37633766

37643767
148. fetchArraySize1.js
37653768
148.1 oracledb.fetchArraySize
@@ -4737,6 +4740,8 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
47374740
239.2 prefetchRows is enabled with default value
47384741
239.3 cursor bind OUT then bind IN
47394742
239.4 implicit binding type
4743+
239.5 check REF CURSOR round-trips with no prefetching
4744+
239.6 check REF CURSOR round-trips with prefetching
47404745

47414746
240. errorOffset.js
47424747
240.1 checks the offset value of the error
@@ -4780,7 +4785,7 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
47804785
243.1.1 OUT bind DB Object
47814786
243.1.2 IN OUT bind DB Object
47824787
243.2 set dbObjectAsPojo in BIND_OUT and BIND_INOUT options
4783-
243.2.1 OUT bind DB Object
4788+
243.2.1 OUT bind DB Object
47844789
243.2.2 IN OUT bind DB Object
47854790
243.3 set dbObjectAsPojo in bind variables doesn't work
47864791
243.3.1 OUT bind DB Object

test/plsqlBindCursorsIN.js

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('239. plsqlBindCursorsIN.js', () => {
5050
select 4, 'String 4' from dual
5151
union all
5252
select 5, 'String 5' from dual
53+
order by 1
5354
`;
5455

5556
before(async () => {
@@ -99,7 +100,8 @@ describe('239. plsqlBindCursorsIN.js', () => {
99100
union all
100101
select 4, 'String 4' from dual
101102
union all
102-
select 5, 'String 5' from dual;
103+
select 5, 'String 5' from dual
104+
order by 1;
103105
end;
104106
`;
105107
await conn.execute(plsql);
@@ -235,4 +237,64 @@ describe('239. plsqlBindCursorsIN.js', () => {
235237
should.not.exist(error);
236238
}
237239
}); // 239.4
238-
});
240+
241+
it('239.5 check REF CURSOR round-trips with no prefetching', async () => {
242+
if (!dbconfig.test.DBA_PRIVILEGE) {
243+
it.skip('');
244+
return;
245+
}
246+
try {
247+
const sid = await testsUtil.getSid(conn);
248+
let rt = await testsUtil.getRoundTripCount(sid);
249+
let result = await conn.execute(
250+
`begin ${procName2}(:bv); end;`,
251+
{
252+
bv: {dir: oracledb.BIND_OUT, type: oracledb.CURSOR }
253+
},
254+
{
255+
prefetchRows: 0
256+
}
257+
);
258+
const rc = result.outBinds.bv;
259+
await rc.getRows(2);
260+
await rc.getRows(2);
261+
rt = await testsUtil.getRoundTripCount(sid) - rt;
262+
263+
should.strictEqual(rt, 3);
264+
265+
} catch (error) {
266+
should.not.exist(error);
267+
}
268+
}); // 239.5
269+
270+
it('239.6 check REF CURSOR round-trips with prefetching', async () => {
271+
if (!dbconfig.test.DBA_PRIVILEGE) {
272+
it.skip('');
273+
return;
274+
}
275+
try {
276+
const sid = await testsUtil.getSid(conn);
277+
let rt = await testsUtil.getRoundTripCount(sid);
278+
279+
let result = await conn.execute(
280+
`begin ${procName2}(:bv); end;`,
281+
{
282+
bv: {dir: oracledb.BIND_OUT, type: oracledb.CURSOR }
283+
},
284+
{
285+
prefetchRows: 100
286+
}
287+
);
288+
const rc = result.outBinds.bv;
289+
await rc.getRows(2);
290+
await rc.getRows(2);
291+
rt = await testsUtil.getRoundTripCount(sid) - rt;
292+
293+
should.strictEqual(rt, 2);
294+
295+
} catch (error) {
296+
should.not.exist(error);
297+
}
298+
}); // 239.6
299+
300+
});

test/prefetchRows.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,24 @@ const oracledb = require('oracledb');
2828
const should = require('should');
2929
const assert = require('assert');
3030
const dbconfig = require('./dbconfig.js');
31+
const testsUtil = require('./testsUtil.js');
3132

3233
describe("147. prefetchRows.js", function() {
3334

3435
let conn;
3536

37+
const sql = `
38+
select 1, 'String 1' from dual
39+
union all
40+
select 2, 'String 2' from dual
41+
union all
42+
select 3, 'String 3' from dual
43+
union all
44+
select 4, 'String 4' from dual
45+
union all
46+
select 5, 'String 5' from dual
47+
order by 1`;
48+
3649
const DefaultPrefetchRows = oracledb.prefetchRows;
3750
before(async () => {
3851
try {
@@ -236,4 +249,58 @@ describe("147. prefetchRows.js", function() {
236249
}
237250
});
238251

239-
});
252+
it('147.15 Query round-trips with no prefetch', async () => {
253+
if (!dbconfig.test.DBA_PRIVILEGE) {
254+
return;
255+
}
256+
try {
257+
const sid = await testsUtil.getSid(conn);
258+
let rt = await testsUtil.getRoundTripCount(sid);
259+
260+
await conn.execute(sql, [], { prefetchRows: 0 });
261+
rt = await testsUtil.getRoundTripCount(sid) - rt;
262+
263+
should.strictEqual(rt, 2);
264+
265+
} catch (error) {
266+
should.not.exist(error);
267+
}
268+
});
269+
270+
it('147.16 Query round-trips with prefetch equal to row count', async () => {
271+
if (!dbconfig.test.DBA_PRIVILEGE) {
272+
return;
273+
}
274+
try {
275+
const sid = await testsUtil.getSid(conn);
276+
let rt = await testsUtil.getRoundTripCount(sid);
277+
278+
await conn.execute(sql, [], { prefetchRows: 5 });
279+
rt = await testsUtil.getRoundTripCount(sid) - rt;
280+
281+
should.strictEqual(rt, 2);
282+
283+
} catch (error) {
284+
should.not.exist(error);
285+
}
286+
});
287+
288+
it('147.16 Query round-trips with prefetch larger than row count', async () => {
289+
if (!dbconfig.test.DBA_PRIVILEGE) {
290+
return;
291+
}
292+
try {
293+
const sid = await testsUtil.getSid(conn);
294+
let rt = await testsUtil.getRoundTripCount(sid);
295+
296+
await conn.execute(sql, [], { prefetchRows: 6 });
297+
rt = await testsUtil.getRoundTripCount(sid) - rt;
298+
299+
should.strictEqual(rt, 1);
300+
301+
} catch (error) {
302+
should.not.exist(error);
303+
}
304+
});
305+
306+
});

test/testsUtil.js

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,43 @@ testsUtil.measureNetworkRoundTripTime = async function() {
212212
return new Date() - startTime;
213213
};
214214

215+
testsUtil.getSid = async function(conn) {
216+
const sql = `select sys_context('userenv','sid') from dual`;
217+
const result = await conn.execute(sql);
218+
return result.rows[0][0]; // session id
219+
};
220+
221+
testsUtil.getRoundTripCount = async function(sid) {
222+
if (!dbconfig.test.DBA_PRIVILEGE) {
223+
let msg = "Note: DBA privilege environment variable is not true!\n";
224+
msg += "Without DBA privilege the test cannot get the current round trip count!";
225+
throw new Error(msg);
226+
} else {
227+
let dbaCredential = {
228+
user: dbconfig.test.DBA_user,
229+
password: dbconfig.test.DBA_password,
230+
connectString: dbconfig.connectString,
231+
privilege: oracledb.SYSDBA
232+
};
233+
234+
const sql = `
235+
select ss.value
236+
from v$sesstat ss, v$statname sn
237+
where ss.sid = :sid
238+
and ss.statistic# = sn.statistic#
239+
and sn.name like '%roundtrip%client%'`;
240+
const conn = await oracledb.getConnection(dbaCredential);
241+
const result = await conn.execute(sql, [sid]);
242+
await conn.close();
243+
return result.rows[0][0]; // number of round-trips executed so far in the session
244+
}
245+
};
246+
215247
testsUtil.createAQtestUser = async function(AQ_USER, AQ_USER_PWD) {
216248

217249
if (!dbconfig.test.DBA_PRIVILEGE) {
218-
let msg = "Note: DBA privilege environment variable is false!\n";
219-
msg += "Without DBA privilege, it could not create schema!";
250+
let msg = "Note: DBA privilege environment variable is not true!\n";
251+
msg += "Without DBA privilege, the test cannot create the schema!";
220252
throw new Error(msg);
221253
} else {
222254
let dbaCredential = {
@@ -265,7 +297,8 @@ testsUtil.createAQtestUser = async function(AQ_USER, AQ_USER_PWD) {
265297

266298
testsUtil.dropAQtestUser = async function(AQ_USER) {
267299
if (!dbconfig.test.DBA_PRIVILEGE) {
268-
let msg = "Without DBA privilege, it could not drop schema!\n";
300+
let msg = "Note: DBA privilege environment variable is not true!\n";
301+
msg += "Without DBA privilege, the test cannot drop the schema!\n";
269302
throw new Error(msg);
270303
} else {
271304
let dbaCredential = {

0 commit comments

Comments
 (0)