Skip to content

Commit c7b3c31

Browse files
committed
Fixed bug which throws an NJS-130 error when fetching a DbObjectClass with an object type name containing %ROWTYPE
1 parent 94ad326 commit c7b3c31

File tree

6 files changed

+315
-16
lines changed

6 files changed

+315
-16
lines changed

doc/src/release_notes.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ node-oracledb Release Notes
77

88
For deprecated and desupported features, see :ref:`Deprecations and desupported features <deprecations>`.
99

10-
node-oracledb `v6.6.0 <https://github.com/oracle/node-oracledb/compare/v6.5.1...v6.6.0>`__ (TBD)
10+
node-oracledb `v6.6 <https://github.com/oracle/node-oracledb/compare/v6.5.1...v6.6>`__ (TBD)
1111
---------------------------------------------------------------------------------------------------------
1212

1313
Thin Mode Changes
1414
+++++++++++++++++
1515

16+
#) Fixed bug which throws an error ``NJS-130`` when calling
17+
:meth:`connection.getDbObjectClass()` with an object type name containing
18+
``%ROWTYPE``.
19+
1620
#) Fixed bug which throws an `NJS-112` error during fetching of JSON and
1721
vector columns after table recreation. This is similar to the
1822
fix provided for GitHub issue #1565.

lib/impl/resultset.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,30 @@ class ResultSetImpl {
275275
errors.throwNotImplemented("getting rows");
276276
}
277277

278+
//---------------------------------------------------------------------------
279+
// _getAllRows() [INTERNAL]
280+
//
281+
// Fetches all the rows from the database to use internally.
282+
//---------------------------------------------------------------------------
283+
async _getAllRows() {
284+
const fetchArraySize = 100;
285+
// fetch all rows
286+
let rowsFetched = [];
287+
while (true) { // eslint-disable-line
288+
// constant default value for fetchArraySize
289+
const rows = await this.getRows(fetchArraySize, {});
290+
if (rows) {
291+
await this._processRows(rows, false);
292+
rowsFetched = rowsFetched.concat(rows);
293+
}
294+
if (rows.length < fetchArraySize) {
295+
break;
296+
}
297+
}
298+
299+
return rowsFetched;
300+
}
301+
278302
}
279303

280304
module.exports = ResultSetImpl;

lib/thin/connection.js

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,27 @@ class ThinConnectionImpl extends ConnectionImpl {
439439
end;
440440
end if;
441441
end;`;
442+
443+
// get column and datatype information in case of %ROWTYPE handling.
444+
const getColumnsSQL = `
445+
SELECT
446+
column_name,
447+
data_type,
448+
data_type_owner,
449+
case
450+
when data_type in
451+
('CHAR', 'NCHAR', 'VARCHAR2', 'NVARCHAR2', 'RAW')
452+
then data_length
453+
else 0
454+
end,
455+
nvl(data_precision, 0),
456+
nvl(data_scale, 0)
457+
from all_tab_cols
458+
where owner = :owner
459+
and table_name = :name
460+
and hidden_column != 'YES'
461+
order by column_id`;
462+
442463
const binds = [
443464
{
444465
name: "full_name",
@@ -517,32 +538,102 @@ class ThinConnectionImpl extends ConnectionImpl {
517538
return info;
518539

519540
// process TDS and attributes cursor
520-
info.version = result.outBinds.version;
521-
const attrRows = await result.outBinds.attrs_rc.getRows(1000, {});
522-
if (attrRows.length > 0) {
523-
// Its an object not a collection.
541+
if (info.name.endsWith('%ROWTYPE')) {
542+
const bindVal = [
543+
{
544+
name: "owner",
545+
type: types.DB_TYPE_VARCHAR,
546+
maxSize: 128,
547+
dir: constants.BIND_IN,
548+
values: [result.outBinds.schema],
549+
},
550+
{
551+
name: "name",
552+
type: types.DB_TYPE_VARCHAR,
553+
maxSize: 128,
554+
dir: constants.BIND_IN,
555+
values: [info.name.substring(0, info.name.length - 8)]
556+
}
557+
];
558+
const val = await this.execute(
559+
getColumnsSQL, 1, bindVal, options, false
560+
);
561+
const attrRows = await val.resultSet._getAllRows();
524562
info.attributes = [];
525563
for (const row of attrRows) {
526-
const attr = { name: row[1] };
527-
if (row[4]) {
528-
attr.type = types.DB_TYPE_OBJECT;
529-
attr.typeClass = this._getDbObjectType(row[4], row[3], row[5], row[6]);
530-
if (attr.typeClass.partial) {
531-
this._partialDbObjectTypes.push(attr.typeClass);
564+
const metaData = {
565+
name: row[0],
566+
dataType: row[1],
567+
dataTypeOwner: row[2],
568+
maxSize: row[3],
569+
dataPrecision: row[4],
570+
dataScale: row[5],
571+
};
572+
if (!metaData.dataTypeOwner) {
573+
const startPos = row[1].indexOf('(');
574+
const endPos = row[1].indexOf(')');
575+
if (endPos > startPos) {
576+
metaData.dataType = metaData.dataType.substring(0, startPos) +
577+
metaData.dataType.substring(
578+
endPos + 1, metaData.dataType.length
579+
);
532580
}
533-
} else {
534-
attr.type = types.getTypeByColumnTypeName(row[3]);
535581
}
536-
info.attributes.push(attr);
582+
this._addAttr(info.attributes, metaData);
537583
}
538-
}
584+
} else {
585+
info.version = result.outBinds.version;
586+
const attrRows = await result.outBinds.attrs_rc._getAllRows();
587+
if (attrRows.length > 0) {
588+
// Its an object not a collection.
589+
info.attributes = [];
590+
for (const row of attrRows) {
591+
const metaData = {
592+
name: row[1],
593+
dataType: row[3],
594+
dataTypeOwner: row[4],
595+
packageName: row[5],
596+
oid: row[6]
597+
};
598+
this._addAttr(info.attributes, metaData);
599+
}
539600

540-
await this._parseTDS(result.outBinds.tds, info);
601+
}
602+
await this._parseTDS(result.outBinds.tds, info);
603+
}
541604
info.partial = false;
542605
return info;
543606

544607
}
545608

609+
//---------------------------------------------------------------------------
610+
// _addAttr()
611+
//
612+
// Populates "attributes" object present in "attrList".
613+
//---------------------------------------------------------------------------
614+
_addAttr(attributes, attrInfo) {
615+
const attr = { name: attrInfo.name };
616+
if (attrInfo.dataTypeOwner) {
617+
attr.type = types.DB_TYPE_OBJECT;
618+
attr.typeClass = this._getDbObjectType(
619+
attrInfo.dataTypeOwner,
620+
attrInfo.dataType,
621+
attrInfo.packageName,
622+
attrInfo.oid
623+
);
624+
625+
if (attr.typeClass.partial) {
626+
this._partialDbObjectTypes.push(attr.typeClass);
627+
}
628+
} else {
629+
attr.type = types.getTypeByColumnTypeName(attrInfo.dataType);
630+
attr.maxSize = attrInfo.maxSize;
631+
attr.precision = attrInfo.dataPrecision;
632+
attr.scale = attrInfo.dataScale;
633+
}
634+
attributes.push(attr);
635+
}
636+
546637
//---------------------------------------------------------------------------
547638
// _populatePartialDbObjectTypes()
548639
//

test/list.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5882,3 +5882,7 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
58825882

58835883
303. aq11.js
58845884
303.1 enqueue/dequeue with modes
5885+
5886+
304. plsqlRowtype.js
5887+
304.1 %ROWTYPE
5888+
304.2 %ROWTYPE collection

test/opts/.mocharc.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,4 @@ spec:
286286
- test/aq9.js
287287
- test/aq10.js
288288
- test/aq11.js
289+
- test/plsqlRowtype.js

test/plsqlRowtype.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/* Copyright 2024, Oracle and/or its affiliates. */
2+
3+
/******************************************************************************
4+
*
5+
* This software is dual-licensed to you under the Universal Permissive License
6+
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7+
* 2.0 as shown at https://www.apache.org/licenses/LICENSE-2.0. You may choose
8+
* either license.
9+
*
10+
* If you elect to accept the software under the Apache License, Version 2.0,
11+
* the following applies:
12+
*
13+
* Licensed under the Apache License, Version 2.0 (the "License");
14+
* you may not use this file except in compliance with the License.
15+
* You may obtain a copy of the License at
16+
*
17+
* https://www.apache.org/licenses/LICENSE-2.0
18+
*
19+
* Unless required by applicable law or agreed to in writing, software
20+
* distributed under the License is distributed on an "AS IS" BASIS,
21+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
* See the License for the specific language governing permissions and
23+
* limitations under the License.
24+
*
25+
* NAME
26+
* 301. plsqlRowtype.js
27+
*
28+
* DESCRIPTION
29+
* Test cases using %ROWTYPE in plsql.
30+
*
31+
*****************************************************************************/
32+
'use strict';
33+
34+
const oracledb = require('oracledb');
35+
const dbConfig = require('./dbconfig.js');
36+
const assert = require('assert');
37+
const testsUtil = require('./testsUtil.js');
38+
39+
describe('304. plSqlRowType.js', function() {
40+
let connection;
41+
const table = 'NODB_ROWTYPE';
42+
const typeName = `NODB_OBJ`;
43+
const stmts = [
44+
`CREATE OR REPLACE PACKAGE FOO_TEST AS
45+
TYPE NODB_ROWTYPE_ARRAY IS TABLE OF NODB_ROWTYPE%ROWTYPE
46+
INDEX BY BINARY_INTEGER;
47+
PROCEDURE prGetRecords(out_rec OUT FOO_TEST.NODB_ROWTYPE_ARRAY);
48+
END FOO_TEST;`,
49+
50+
`CREATE OR REPLACE PACKAGE BODY FOO_TEST IS
51+
PROCEDURE prGetRecords(out_rec OUT FOO_TEST.NODB_ROWTYPE_ARRAY)
52+
IS
53+
CURSOR c_NODB_ROWTYPE IS
54+
SELECT *
55+
FROM NODB_ROWTYPE;
56+
BEGIN
57+
OPEN c_NODB_ROWTYPE;
58+
FETCH c_NODB_ROWTYPE BULK COLLECT INTO out_rec;
59+
CLOSE c_NODB_ROWTYPE;
60+
END prGetRecords;
61+
END FOO_TEST;`
62+
];
63+
64+
const dropPackageSql = `DROP PACKAGE FOO_TEST`;
65+
66+
const ObjSql = `
67+
CREATE OR REPLACE TYPE ${typeName} AS OBJECT (
68+
id NUMBER,
69+
name NVARCHAR2(30)
70+
);`;
71+
72+
73+
const createTableSql = `CREATE TABLE ${table}(
74+
NUMBERVALUE NUMBER(12),
75+
STRINGVALUE VARCHAR2(2),
76+
FIXEDCHARVALUE CHAR(10),
77+
NSTRINGVALUE NVARCHAR2(60),
78+
NFIXEDCHARVALUE NCHAR(10),
79+
RAWVALUE RAW(15),
80+
INTVALUE INTEGER,
81+
SMALLINTVALUE SMALLINT,
82+
REALVALUE REAL,
83+
DOUBLEPRECISIONVALUE DOUBLE PRECISION,
84+
FLOATVALUE FLOAT,
85+
BINARYFLOATVALUE BINARY_FLOAT,
86+
BINARYDOUBLEVALUE BINARY_DOUBLE,
87+
DATEVALUE DATE,
88+
TIMESTAMPVALUE TIMESTAMP,
89+
TIMESTAMPTZVALUE TIMESTAMP WITH TIME ZONE,
90+
TIMESTAMPLTZVALUE TIMESTAMP WITH LOCAL TIME ZONE,
91+
CLOBVALUE CLOB,
92+
NCLOBVALUE NCLOB,
93+
BLOBVALUE BLOB,
94+
OBJECTVALUE NODB_OBJ,
95+
INVISIBLEVALUE NUMBER INVISIBLE)`;
96+
97+
let expectedTypes = {
98+
NUMBERVALUE: { type: oracledb.DB_TYPE_NUMBER, typeName: 'NUMBER' },
99+
STRINGVALUE: { type: oracledb.DB_TYPE_VARCHAR, typeName: 'VARCHAR2' },
100+
FIXEDCHARVALUE: { type: oracledb.DB_TYPE_CHAR, typeName: 'CHAR' },
101+
NSTRINGVALUE: { type: oracledb.DB_TYPE_NVARCHAR, typeName: 'NVARCHAR2' },
102+
NFIXEDCHARVALUE: { type: oracledb.DB_TYPE_NCHAR, typeName: 'NCHAR' },
103+
RAWVALUE: { type: oracledb.DB_TYPE_RAW, typeName: 'RAW' },
104+
INTVALUE: { type: oracledb.DB_TYPE_NUMBER, typeName: 'NUMBER' },
105+
SMALLINTVALUE: { type: oracledb.DB_TYPE_NUMBER, typeName: 'NUMBER' },
106+
REALVALUE: { type: oracledb.DB_TYPE_NUMBER, typeName: 'NUMBER' },
107+
DOUBLEPRECISIONVALUE: {
108+
type: oracledb.DB_TYPE_NUMBER,
109+
typeName: 'NUMBER'
110+
},
111+
FLOATVALUE: { type: oracledb.DB_TYPE_NUMBER, typeName: 'NUMBER' },
112+
BINARYFLOATVALUE: {
113+
type: oracledb.DB_TYPE_BINARY_FLOAT,
114+
typeName: 'BINARY_FLOAT'
115+
},
116+
BINARYDOUBLEVALUE: {
117+
type: oracledb.DB_TYPE_BINARY_DOUBLE,
118+
typeName: 'BINARY_DOUBLE'
119+
},
120+
DATEVALUE: { type: oracledb.DB_TYPE_DATE, typeName: 'DATE' },
121+
TIMESTAMPVALUE: {
122+
type: oracledb.DB_TYPE_TIMESTAMP,
123+
typeName: 'TIMESTAMP'
124+
},
125+
TIMESTAMPTZVALUE: {
126+
type: oracledb.DB_TYPE_TIMESTAMP_TZ,
127+
typeName: 'TIMESTAMP WITH TIME ZONE'
128+
},
129+
TIMESTAMPLTZVALUE: {
130+
type: oracledb.DB_TYPE_TIMESTAMP_LTZ,
131+
typeName: 'TIMESTAMP WITH LOCAL TIME ZONE'
132+
},
133+
CLOBVALUE: { type: oracledb.DB_TYPE_CLOB, typeName: 'CLOB' },
134+
NCLOBVALUE: { type: oracledb.DB_TYPE_NCLOB, typeName: 'NCLOB' },
135+
BLOBVALUE: { type: oracledb.DB_TYPE_BLOB, typeName: 'BLOB' },
136+
};
137+
138+
before(async function() {
139+
connection = await oracledb.getConnection(dbConfig);
140+
await testsUtil.createType(connection, typeName, ObjSql);
141+
await testsUtil.createTable(connection, table, createTableSql);
142+
for (const s of stmts) {
143+
await connection.execute(s);
144+
}
145+
const objType = await connection.getDbObjectClass("NODB_OBJ");
146+
const OBJECTVALUE = {
147+
type: oracledb.DB_TYPE_OBJECT,
148+
typeName: objType.prototype.fqn,
149+
typeClass: objType
150+
};
151+
expectedTypes = {...expectedTypes, OBJECTVALUE };
152+
});
153+
154+
after(async function() {
155+
await testsUtil.dropTable(connection, table);
156+
await testsUtil.dropType(connection, typeName);
157+
await connection.execute(dropPackageSql);
158+
await connection.close();
159+
});
160+
161+
it('304.1 %ROWTYPE', async function() {
162+
const name = "NODB_ROWTYPE%ROWTYPE";
163+
const objClass = await connection.getDbObjectClass(name);
164+
const types = objClass.prototype.attributes;
165+
assert.deepStrictEqual(expectedTypes, types);
166+
}); // 304.1
167+
168+
it('304.2 %ROWTYPE collection', async function() {
169+
const name = "FOO_TEST.NODB_ROWTYPE_ARRAY";
170+
const objClass = await connection.getDbObjectClass(name);
171+
const types = objClass.prototype.elementTypeClass.prototype.attributes;
172+
assert.deepStrictEqual(expectedTypes, types);
173+
}); // 304.2
174+
175+
}); // 304

0 commit comments

Comments
 (0)