Add DB Support objects and Pipelined Table tests

This commit is contained in:
Sharad Chandran R 2023-05-23 21:31:55 +05:30
parent 4c2336e54d
commit 3a9c0d9e82
42 changed files with 1660 additions and 208 deletions

View File

@ -37,22 +37,26 @@ Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
const dbConfig = require('./dbconfig.js');
// This example requires node-oracledb Thick mode.
// This example runs in both node-oracledb Thin and Thick modes.
//
// Thick mode requires Oracle Client or Oracle Instant Client libraries. On
// Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
async function run() {
let connection, binds, options, result, obj;

View File

@ -37,22 +37,26 @@ Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
const dbConfig = require('./dbconfig.js');
// This example requires node-oracledb Thick mode.
// This example runs in both node-oracledb Thin and Thick modes.
//
// Thick mode requires Oracle Client or Oracle Instant Client libraries. On
// Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
async function run() {
let connection;

View File

@ -37,20 +37,26 @@ Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
const dbConfig = require('./dbconfig.js');
// This example requires node-oracledb Thick mode.
// This example runs in both node-oracledb Thin and Thick modes.
//
// On Windows and macOS Intel, you can specify the directory containing the
// Oracle Client Libraries at runtime, or before Node.js starts. On other
// platforms the system library search path must always be set before Node.js
// is started. See the node-oracledb installation documentation. If the
// search path is not correct, you will get a DPI-1047 error.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
oracledb.initOracleClient(clientOpts);
// If each object's attributes are accessed multiple times, it may be more
// efficient to fetch as simple JavaScriptobjects.

View File

@ -38,22 +38,26 @@ Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
const dbConfig = require('./dbconfig.js');
// This example requires node-oracledb Thick mode.
// This example runs in both node-oracledb Thin and Thick modes.
//
// Thick mode requires Oracle Client or Oracle Instant Client libraries. On
// Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
// If each object's attributes are accessed multiple times, it may be more
// efficient to fetch as simple JavaScriptobjects.

View File

@ -37,22 +37,26 @@ Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
const dbConfig = require('./dbconfig.js');
// This example requires node-oracledb Thick mode.
// This example runs in both node-oracledb Thin and Thick modes.
//
// Thick mode requires Oracle Client or Oracle Instant Client libraries. On
// Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
if (process.platform === 'win32') { // Windows
clientOpts = { libDir: 'C:\\oracle\\instantclient_19_17' };
} else if (process.platform === 'darwin' && process.arch === 'x64') { // macOS Intel
clientOpts = { libDir: process.env.HOME + '/Downloads/instantclient_19_8' };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
async function run() {
let connection;

View File

@ -82,8 +82,7 @@ class Connection extends EventEmitter {
//---------------------------------------------------------------------------
_buildDbObjectClass(objType) {
const DbObject = function(initialValue) {
this._impl = new impl.DbObjectImpl();
this._impl._objType = objType;
this._impl = new impl.DbObjectImpl(objType);
if (this.isCollection) {
const proxy = new Proxy(this, BaseDbObject._collectionProxyHandler);
if (initialValue !== undefined) {
@ -169,6 +168,8 @@ class Connection extends EventEmitter {
// object class has already been built.
//---------------------------------------------------------------------------
_getDbObjectClass(objType) {
if (objType.prototype instanceof BaseDbObject)
return objType;
let cls = this._dbObjectClasses.get(objType);
if (!cls) {
cls = this._buildDbObjectClass(objType);

View File

@ -140,7 +140,7 @@ class BaseDbObject {
//---------------------------------------------------------------------------
get attributes() {
if (!this._attributes) {
const implAttrs = this._objType.attributes;
const implAttrs = this._objType.attributes || [];
const attrs = {};
for (let i = 0; i < implAttrs.length; i++) {
const implAttr = implAttrs[i];

View File

@ -126,6 +126,12 @@ const ERR_CALL_TIMEOUT_EXCEEDED = 123;
const ERR_EMPTY_CONNECTION_STRING = 125;
const ERR_OSON_VERSION_NOT_SUPPORTED = 126;
const ERR_UNKOWN_SERVER_SIDE_PIGGYBACK = 127;
const ERR_UNKNOWN_COLUMN_TYPE_NAME = 128;
const ERR_INVALID_OBJECT_TYPE_NAME = 129;
const ERR_TDS_TYPE_NOT_SUPPORTED = 130;
const ERR_INVALID_COLL_INDEX_SET = 131;
const ERR_INVALID_COLL_INDEX_GET = 132;
const ERR_DELETE_ELEMENTS_OF_VARRAY = 133;
// Oracle Net layer errors start from 500
const ERR_CONNECTION_CLOSED = 500;
@ -153,12 +159,16 @@ const ERR_CONNECTION_EOF = 521;
// define mapping for ODPI-C errors that need to be wrapped with NJS errors
const adjustErrorXref = new Map();
adjustErrorXref.set("DPI-1010", ERR_CONNECTION_CLOSED);
adjustErrorXref.set("DPI-1024", [ERR_INVALID_COLL_INDEX_GET, 'at index ([0-9]+) does']);
adjustErrorXref.set("DPI-1040", ERR_LOB_CLOSED);
adjustErrorXref.set("DPI-1044", ERR_ORACLE_NUMBER_NO_REPR);
adjustErrorXref.set("DPI-1055", ERR_NAN_VALUE);
adjustErrorXref.set("DPI-1063", ERR_EXEC_MODE_ONLY_FOR_DML);
adjustErrorXref.set("DPI-1067", [ERR_CALL_TIMEOUT_EXCEEDED, "call timeout of ([0-9]+) ms"]);
adjustErrorXref.set("DPI-1080", ERR_CONNECTION_CLOSED);
adjustErrorXref.set("OCI-22303", [ERR_INVALID_OBJECT_TYPE_NAME, 'type "([^"]*"."[^"]*)"']);
adjustErrorXref.set("OCI-22164", ERR_DELETE_ELEMENTS_OF_VARRAY);
adjustErrorXref.set("OCI-22165", [ERR_INVALID_COLL_INDEX_SET, /index \[([0-9]+)\] must be in the range of \[([0-9]+)\] to \[([0-9]+)\]/]);
adjustErrorXref.set("ORA-00028", ERR_CONNECTION_CLOSED);
adjustErrorXref.set("ORA-00600", ERR_CONNECTION_CLOSED);
adjustErrorXref.set("ORA-24338", ERR_INVALID_REF_CURSOR);
@ -359,6 +369,18 @@ messages.set(ERR_OSON_VERSION_NOT_SUPPORTED, // NJS-126
'OSON version %s is not supported');
messages.set(ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, // NJS-127
'internal error: unknown server side piggyback opcode %s');
messages.set(ERR_UNKNOWN_COLUMN_TYPE_NAME, // NJS-128
'internal error: unknown column type name "%s"');
messages.set(ERR_INVALID_OBJECT_TYPE_NAME, // NJS-129
'invalid object type name: "%s"');
messages.set(ERR_TDS_TYPE_NOT_SUPPORTED, // NJS-130
'Oracle TDS data type %d is not supported');
messages.set(ERR_INVALID_COLL_INDEX_SET, // NJS-131
'given index %d must be in the range of %d to %d');
messages.set(ERR_INVALID_COLL_INDEX_GET, // NJS-132
'element at index %d does not exist');
messages.set(ERR_DELETE_ELEMENTS_OF_VARRAY, // NJS-133
'cannot delete elements of a VARRAY');
// Oracle Net layer errors
@ -719,6 +741,12 @@ module.exports = {
ERR_CALL_TIMEOUT_EXCEEDED,
ERR_EMPTY_CONNECTION_STRING,
ERR_UNKOWN_SERVER_SIDE_PIGGYBACK,
ERR_UNKNOWN_COLUMN_TYPE_NAME,
ERR_INVALID_OBJECT_TYPE_NAME,
ERR_TDS_TYPE_NOT_SUPPORTED,
ERR_INVALID_COLL_INDEX_SET,
ERR_INVALID_COLL_INDEX_GET,
ERR_DELETE_ELEMENTS_OF_VARRAY,
ERR_CONNECTION_CLOSED_CODE: `${ERR_PREFIX}-${ERR_CONNECTION_CLOSED}`,
assert,
assertArgCount,

View File

@ -72,15 +72,34 @@ class ConnectionImpl {
// _getDbObjectType()
//
// Return the object identifying the object type. These are cached by fully
// qualified name.
// ---------------------------------------------------------------------------
_getDbObjectType(schema, name) {
const fqn = `${schema}.${name}`;
let dbObjectType = this._dbObjectTypes.get(fqn);
// qualified name and by OID (thin mode only).
//---------------------------------------------------------------------------
_getDbObjectType(schema, name, packageName, oid) {
let dbObjectType;
if (oid) {
dbObjectType = this._dbObjectTypes.get(oid);
if (dbObjectType)
return dbObjectType;
}
const fqn = (packageName) ? `${schema}.${packageName}.${name}` :
`${schema}.${name}`;
dbObjectType = this._dbObjectTypes.get(fqn);
if (!dbObjectType) {
dbObjectType = {schema: schema, name: name, fqn: fqn};
dbObjectType = {
oid: oid,
fqn: fqn,
schema: schema,
name: name,
packageName: packageName,
partial: true,
isXmlType: (schema === 'SYS' && name === 'XMLTYPE')
};
this._dbObjectTypes.set(fqn, dbObjectType);
}
if (oid && !dbObjectType.oid) {
dbObjectType.oid = oid;
this._dbObjectTypes.set(oid, dbObjectType);
}
return dbObjectType;
}

View File

@ -33,6 +33,10 @@ const errors = require('../errors.js');
// instantiated; a cache of these classes are maintained on each connection
class DbObjectImpl {
constructor(objType) {
this._objType = objType;
}
//---------------------------------------------------------------------------
// append()
//

View File

@ -100,6 +100,35 @@ class ResultSetImpl {
metadata.fetchType = actualFetchType;
}
// in thin mode, Oracle NUMBER values are internally fetched as string in
// order to preserve precision so must be converted to JavaScript Number
// when needed; other numeric and date types are fetched natively as
// JavaScript Number and Date values and are converted to string using
// toString() when desired
if (settings.thin) {
let converter;
const userConverter = metadata.converter;
if (metadata.dbType === types.DB_TYPE_NUMBER &&
metadata.fetchType === types.DB_TYPE_NUMBER) {
converter = (v) => (v === null) ? null : parseFloat(v);
} else if (metadata.fetchType === types.DB_TYPE_VARCHAR &&
(metadata.dbType === types.DB_TYPE_BINARY_DOUBLE ||
metadata.dbType === types.DB_TYPE_BINARY_FLOAT ||
metadata.dbType === types.DB_TYPE_DATE ||
metadata.dbType === types.DB_TYPE_TIMESTAMP ||
metadata.dbType === types.DB_TYPE_TIMESTAMP_LTZ ||
metadata.dbType === types.DB_TYPE_TIMESTAMP_TZ)) {
converter = (v) => (v === null) ? null : v.toString();
}
if (userConverter && converter) {
const internalConverter = converter;
converter = (v) => userConverter(internalConverter(v));
}
if (converter) {
metadata.converter = converter;
}
}
}
//---------------------------------------------------------------------------

View File

@ -30,12 +30,12 @@ const ConnectionImpl = require('../impl/connection.js');
const ThinResultSetImpl = require('./resultSet.js');
const ThinLobImpl = require("./lob.js");
const Protocol = require("./protocol/protocol.js");
const { BaseBuffer } = require('./protocol/buffer.js');
const {NetworkSession:nsi, getConnectionInfo} = require("./sqlnet/networkSession.js");
const { Statement } = require("./statement");
const thinUtil = require('./util');
const sqlNetConstants = require('./sqlnet/constants.js');
const constants = require('../constants.js');
const protocolConstants = require('./protocol/constants.js');
const constants = require('./protocol/constants.js');
const types = require('../types.js');
const errors = require("../errors.js");
const messages = require('./protocol/messages');
@ -44,6 +44,9 @@ const finalizationRegistry = new global.FinalizationRegistry((heldValue) => {
heldValue.disconnect();
});
class TDSBuffer extends BaseBuffer {
}
class ThinConnectionImpl extends ConnectionImpl {
/**
@ -80,6 +83,355 @@ class ThinConnectionImpl extends ConnectionImpl {
await this._protocol._processMessage(message);
}
//---------------------------------------------------------------------------
// _determineElementObjType()
//
// Determine the element type's object type. This is needed when processing
// collections with an object as the element type since this information is
// not available in the TDS.
//---------------------------------------------------------------------------
async _determineElementObjType(info) {
const binds = [
{
name: "owner",
type: types.DB_TYPE_VARCHAR,
dir: constants.BIND_IN,
maxSize: 128,
values: [info.schema]
},
{
name: "name",
type: types.DB_TYPE_VARCHAR,
dir: constants.BIND_IN,
maxSize: 128,
values: [info.name]
},
{
name: "package_name",
type: types.DB_TYPE_VARCHAR,
dir: constants.BIND_IN,
maxSize: 128,
values: [info.packageName]
}
];
let sql;
if (info.packageName) {
sql = `
select
elem_type_owner,
elem_type_name,
elem_type_package
from all_plsql_coll_types
where owner = :owner
and type_name = :name
and package_name = :package_name`;
} else {
binds.pop();
sql = `
select
elem_type_owner,
elem_type_name
from all_coll_types
where owner = :owner
and type_name = :name`;
}
const options = {
connection: { _impl: this },
prefetchRows: 2
};
const result = await this.execute(sql, 1, binds, options, false);
const rows = await result.resultSet.getRows(1, options);
await result.resultSet.close();
const row = rows[0];
info.elementTypeClass = this._getDbObjectType(row[0], row[1], row[2]);
if (info.elementTypeClass.partial) {
this._partialDbObjectTypes.push(info.elementTypeClass);
}
}
//---------------------------------------------------------------------------
// _parseElementType()
//
// Parses the element type from the TDS buffer.
//---------------------------------------------------------------------------
async _parseElementType(buf, info) {
let oraTypeNum, csfrm;
const attrType = buf.readUInt8();
switch (attrType) {
case constants.TNS_OBJ_TDS_TYPE_NUMBER:
case constants.TNS_OBJ_TDS_TYPE_FLOAT:
info.elementType = types.DB_TYPE_NUMBER;
break;
case constants.TNS_OBJ_TDS_TYPE_VARCHAR:
case constants.TNS_OBJ_TDS_TYPE_CHAR:
info.maxSize = buf.readUInt16BE();
oraTypeNum = (attrType === constants.TNS_OBJ_TDS_TYPE_VARCHAR) ?
constants.TNS_DATA_TYPE_VARCHAR : constants.TNS_DATA_TYPE_CHAR;
csfrm = buf.readUInt8();
info.elementType = types.getTypeByOraTypeNum(oraTypeNum, csfrm);
break;
case constants.TNS_OBJ_TDS_TYPE_RAW:
info.elementType = types.DB_TYPE_RAW;
break;
case constants.TNS_OBJ_TDS_TYPE_BINARY_FLOAT:
info.elementType = types.DB_TYPE_BINARY_FLOAT;
break;
case constants.TNS_OBJ_TDS_TYPE_BINARY_DOUBLE:
info.elementType = types.DB_TYPE_BINARY_DOUBLE;
break;
case constants.TNS_OBJ_TDS_TYPE_DATE:
info.elementType = types.DB_TYPE_DATE;
break;
case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP:
info.elementType = types.DB_TYPE_TIMESTAMP;
break;
case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP_LTZ:
info.elementType = types.DB_TYPE_TIMESTAMP_LTZ;
break;
case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP_TZ:
info.elementType = types.DB_TYPE_TIMESTAMP_TZ;
break;
case constants.TNS_OBJ_TDS_TYPE_BOOLEAN:
info.elementType = types.DB_TYPE_BOOLEAN;
break;
case constants.TNS_OBJ_TDS_TYPE_CLOB:
this._determineElementTypeCharsetForm(info);
break;
case constants.TNS_OBJ_TDS_TYPE_BLOB:
info.elementType = types.DB_TYPE_BLOB;
break;
case constants.TNS_OBJ_TDS_TYPE_OBJ:
info.elementType = types.DB_TYPE_OBJECT;
await this._determineElementObjType(info);
break;
default:
errors.throwErr(errors.ERR_TDS_TYPE_NOT_SUPPORTED, attrType);
}
}
//---------------------------------------------------------------------------
// _parseTDS()
//
// Parses the TDS for the type. This is only needed for collection types, so
// if the TDS is determined to be for an object type, the remaining
// information is ignored.
//---------------------------------------------------------------------------
async _parseTDS(tds, info) {
// parse initial TDS bytes
const buf = new TDSBuffer(tds);
buf.skipBytes(4); // end offset
buf.skipBytes(2); // version op code and version
buf.skipBytes(2); // unknown
// if the number of attributes exceeds 1, the type cannot refer to a
// collection, so nothing further needs to be done
const numAttrs = buf.readUInt16BE();
if (numAttrs > 1) {
info.isCollection = false;
return;
}
// continue parsing TDS bytes to discover if type refers to a collection
buf.skipBytes(1); // TDS attributes?
buf.skipBytes(1); // start ADT op code
buf.skipBytes(2); // ADT number (always zero)
buf.skipBytes(4); // offset to index table
// if type of first attribute is not a collection, nothing further needs
// to be done
const attrType = buf.readUInt8();
info.isCollection = (attrType === constants.TNS_OBJ_TDS_TYPE_COLL);
if (!info.isCollection)
return;
// continue parsing TDS to determine element type
const elementPos = buf.readUInt32BE();
info.maxNumElements = buf.readUInt32BE();
info.collectionType = buf.readUInt8();
if (info.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
info.collectionFlags = constants.TNS_OBJ_HAS_INDEXES;
}
buf.pos = elementPos;
await this._parseElementType(buf, info);
}
//---------------------------------------------------------------------------
// _populateDbObjectTypeInfo()
//
// Poplates type information given the name of the type.
//---------------------------------------------------------------------------
async _populateDbObjectTypeInfo(name) {
// get type information from the database
const sql = `
declare
t_Instantiable varchar2(3);
t_SuperTypeOwner varchar2(128);
t_SuperTypeName varchar2(128);
t_SubTypeRefCursor sys_refcursor;
t_Pos pls_integer;
begin
:ret_val := dbms_pickler.get_type_shape(:full_name, :oid,
:version, :tds, t_Instantiable, t_SuperTypeOwner,
t_SuperTypeName, :attrs_rc, t_SubTypeRefCursor);
:package_name := null;
if substr(:full_name, length(:full_name) - 7) = '%ROWTYPE' then
t_Pos := instr(:full_name, '.');
:schema := substr(:full_name, 1, t_Pos - 1);
:name := substr(:full_name, t_Pos + 1);
else
begin
select owner, type_name
into :schema, :name
from all_types
where type_oid = :oid;
exception
when no_data_found then
begin
select owner, package_name, type_name
into :schema, :package_name, :name
from all_plsql_types
where type_oid = :oid;
exception
when no_data_found then
null;
end;
end;
end if;
end;`;
const binds = [
{
name: "full_name",
type: types.DB_TYPE_VARCHAR,
dir: constants.BIND_INOUT,
maxSize: 500,
values: [name]
},
{
name: "ret_val",
type: types.DB_TYPE_BINARY_INTEGER,
dir: constants.BIND_OUT,
values: []
},
{
name: "oid",
type: types.DB_TYPE_RAW,
maxSize: 16,
dir: constants.BIND_OUT,
values: []
},
{
name: "version",
type: types.DB_TYPE_BINARY_INTEGER,
dir: constants.BIND_OUT,
values: []
},
{
name: "tds",
type: types.DB_TYPE_RAW,
maxSize: 2000,
dir: constants.BIND_OUT,
values: []
},
{
name: "attrs_rc",
type: types.DB_TYPE_CURSOR,
dir: constants.BIND_OUT,
values: []
},
{
name: "package_name",
type: types.DB_TYPE_VARCHAR,
maxSize: 128,
dir: constants.BIND_OUT,
values: []
},
{
name: "schema",
type: types.DB_TYPE_VARCHAR,
maxSize: 128,
dir: constants.BIND_OUT,
values: []
},
{
name: "name",
type: types.DB_TYPE_VARCHAR,
maxSize: 128,
dir: constants.BIND_OUT,
values: []
}
];
const options = {
connection: { _impl: this },
nullifyInvalidCursor: true
};
const result = await this.execute(sql, 1, binds, options, false);
if (result.outBinds.ret_val !== 0) {
errors.throwErr(errors.ERR_INVALID_OBJECT_TYPE_NAME, name);
}
// check cache; if already present, nothing more to do!
const info = this._getDbObjectType(result.outBinds.schema,
result.outBinds.name, result.outBinds.package_name, result.outBinds.oid);
if (!info.partial)
return info;
// process TDS and attributes cursor
info.version = result.outBinds.version;
await this._parseTDS(result.outBinds.tds, info);
const attrRows = await result.outBinds.attrs_rc.getRows(1000, {});
if (attrRows.length > 0) {
info.attributes = [];
for (const row of attrRows) {
const attr = { name: row[1] };
if (row[4]) {
attr.type = types.DB_TYPE_OBJECT;
attr.typeClass = this._getDbObjectType(row[4], row[3], row[5], row[6]);
if (attr.typeClass.partial) {
this._partialDbObjectTypes.push(attr.typeClass);
}
} else {
attr.type = types.getTypeByColumnTypeName(row[3]);
}
info.attributes.push(attr);
}
}
info.partial = false;
return info;
}
//---------------------------------------------------------------------------
// _populatePartialDbObjectTypes()
//
// Populates partial types that were discovered earlier. Since populating an
// object type might result in additional object types being discovered,
// object types are popped from the partial types list until the list is
// empty.
//---------------------------------------------------------------------------
async _populatePartialDbObjectTypes() {
while (this._partialDbObjectTypes.length > 0) {
const info = this._partialDbObjectTypes.pop();
let suffix = "%ROWTYPE";
let name = info.name;
if (name.endsWith(suffix)) {
name = name.substring(0, name.length - suffix.length);
} else {
suffix = "";
}
let fullName;
if (info.packageName) {
fullName = `"${info.schema}"."${info.packageName}"."${name}"${suffix}`;
} else {
fullName = `"${info.schema}"."${name}"${suffix}`;
}
await this._populateDbObjectTypeInfo(fullName);
}
}
async commit() {
const message = new messages.CommitMessage(this);
await this._protocol._processMessage(message);
@ -190,6 +542,8 @@ class ThinConnectionImpl extends ConnectionImpl {
throw err;
}
// maintain a list of partially populated database object types
this._partialDbObjectTypes = [];
if (params.debugJDWP) {
this.jdwpData = Buffer.from(params.debugJDWP);
@ -283,7 +637,7 @@ class ThinConnectionImpl extends ConnectionImpl {
if (variable.type === types.DB_TYPE_RAW ||
variable.type === types.DB_TYPE_LONG_RAW) {
variable.type = types.DB_TYPE_BLOB;
} else if (variable.type._csfrm === protocolConstants.TNS_CS_NCHAR) {
} else if (variable.type._csfrm === constants.CSFRM_NCHAR) {
variable.type = types.DB_TYPE_NCLOB;
} else {
variable.type = types.DB_TYPE_CLOB;
@ -388,6 +742,17 @@ class ThinConnectionImpl extends ConnectionImpl {
this._cursorsToClose.clear();
}
//---------------------------------------------------------------------------
// getDbObjectClass()
//
// Returns a database object class given its name.
//---------------------------------------------------------------------------
async getDbObjectClass(name) {
const info = await this._populateDbObjectTypeInfo(name);
await this._populatePartialDbObjectTypes();
return info;
}
//---------------------------------------------------------------------------
// Parses the sql given by User
// calls the OAL8 RPC that parses the SQL statement and returns the metadata
@ -489,7 +854,7 @@ class ThinConnectionImpl extends ConnectionImpl {
message.numExecs = numIters;
message.arrayDmlRowCounts = options.dmlRowCounts;
message.batchErrors = options.batchErrors;
if (statement.isPlSql && statement.cursorId === 0) {
if (statement.isPlSql && statement.requiresFullExecute) {
message.numExecs = 1;
await this._protocol._processMessage(message);
if (statement.plsqlMultipleExecs) {

637
lib/thin/dbObject.js Normal file
View File

@ -0,0 +1,637 @@
// Copyright (c) 2023, Oracle and/or its affiliates.
//-----------------------------------------------------------------------------
//
// This software is dual-licensed to you under the Universal Permissive License
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
// either license.
//
// If you elect to accept the software under the Apache License, Version 2.0,
// the following applies:
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-----------------------------------------------------------------------------
'use strict';
const constants = require('./protocol/constants.js');
const errors = require('../errors.js');
const types = require('../types.js');
const DbObjectImpl = require('../impl/dbObject.js');
const { GrowableBuffer } = require('./protocol/buffer.js');
class DbObjectPickleBuffer extends GrowableBuffer {
//---------------------------------------------------------------------------
// getIsAtomicNull()
//
// Reads the next byte and checks to see if the value is atomically null. If
// not, the byte is returned to the buffer for further processing.
//---------------------------------------------------------------------------
getIsAtomicNull() {
const value = this.readUInt8();
if (value === constants.TNS_OBJ_ATOMIC_NULL ||
value === constants.TNS_NULL_LENGTH_INDICATOR) {
return true;
}
this.pos -= 1;
return false;
}
//---------------------------------------------------------------------------
// readHeader()
//
// Reads the header of the pickled data.
//---------------------------------------------------------------------------
readHeader(obj) {
obj.imageFlags = this.readUInt8();
obj.imageVersion = this.readUInt8();
this.readLength();
if ((obj.imageFlags & constants.TNS_OBJ_NO_PREFIX_SEG) === 0) {
const prefixSegLength = this.readLength();
this.skipBytes(prefixSegLength);
}
}
//---------------------------------------------------------------------------
// readLength()
//
// Read the length from the buffer. This will be a single byte, unless the
// value meets or exeeds TNS_LONG_LENGTH_INDICATOR. In that case, the value
// is stored as a 4-byte integer.
//---------------------------------------------------------------------------
readLength() {
const shortLength = this.readUInt8();
if (shortLength !== constants.TNS_LONG_LENGTH_INDICATOR) {
return shortLength;
}
return this.readUInt32BE();
}
//---------------------------------------------------------------------------
// writeHeader()
//
// Writes the header of the pickled data. Since the size is unknown at this
// point, zero is written initially and the actual size is written later.
//---------------------------------------------------------------------------
writeHeader(obj) {
this.writeUInt8(obj.imageFlags);
this.writeUInt8(obj.imageVersion);
this.writeUInt8(constants.TNS_LONG_LENGTH_INDICATOR);
this.writeUInt32BE(0);
if (obj._objType.isCollection) {
this.writeUInt8(1); // length of prefix segment
this.writeUInt8(1); // prefix segment contents
}
}
//---------------------------------------------------------------------------
// writeLength()
//
// Writes the length to the buffer.
//---------------------------------------------------------------------------
writeLength(length) {
if (length <= constants.TNS_OBJ_MAX_SHORT_LENGTH) {
this.writeUInt8(length);
} else {
this.writeUInt8(constants.TNS_LONG_LENGTH_INDICATOR);
this.writeUInt32BE(length);
}
}
}
class ThinDbObjectImpl extends DbObjectImpl {
constructor(objType, packedData) {
if (typeof objType === 'function') {
objType = objType.prototype._objType;
}
super(objType);
this.packedData = packedData;
this.unpackedAttrs = new Map();
if (packedData) {
this.unpackedAssocArray = new Map();
this.unpackedAssocKeys = undefined;
} else {
const prefix = Buffer.from([0, 0x22, constants.TNS_OBJ_NON_NULL_OID,
constants.TNS_OBJ_HAS_EXTENT_OID]);
this.toid = Buffer.concat([prefix, objType.oid, constants.TNS_EXTENT_OID]);
this.flags = constants.TNS_OBJ_TOP_LEVEL;
this.imageFlags = constants.TNS_OBJ_IS_VERSION_81;
this.imageVersion = constants.TNS_OBJ_IMAGE_VERSION;
if (objType.isCollection) {
this.imageFlags |= constants.TNS_OBJ_IS_COLLECTION;
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
this.unpackedAssocArray = new Map();
} else {
this.unpackedArray = [];
}
} else {
this.imageFlags |= constants.TNS_OBJ_NO_PREFIX_SEG;
}
}
}
//---------------------------------------------------------------------------
// _ensureAssocKeys()
//
// Ensure that the keys for the associative array have been calculated.
// PL/SQL associative arrays keep their keys in sorted order so this must be
// calculated when indices are required.
//---------------------------------------------------------------------------
_ensureAssocKeys() {
if (!this.unpackedAssocKeys) {
this.unpackedAssocKeys = [...this.unpackedAssocArray.keys()].sort();
}
}
//---------------------------------------------------------------------------
// _ensureUnpacked()
//
// Ensure that the data has been unpacked.
//---------------------------------------------------------------------------
_ensureUnpacked() {
if (this.packedData) {
this._unpackData();
}
}
//---------------------------------------------------------------------------
// _getPackedData()
//
// Returns the packed data for the object. This will either be the value
// retrieved from the database or generated packed data (for new objects and
// those that have had their data unpacked already).
//---------------------------------------------------------------------------
_getPackedData() {
if (this.packedData)
return this.packedData;
const buf = new DbObjectPickleBuffer();
buf.writeHeader(this);
this._packData(buf);
const size = buf.pos;
buf.pos = 3;
buf.writeUInt32BE(size);
return buf.buf.subarray(0, size);
}
//---------------------------------------------------------------------------
// _packData()
//
// Packs the data from the object into the buffer.
//---------------------------------------------------------------------------
_packData(buf) {
const objType = this._objType;
if (objType.isCollection) {
buf.writeUInt8(objType.collectionFlags);
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
this._ensureAssocKeys();
buf.writeLength(this.unpackedAssocKeys.length);
for (const index of this.unpackedAssocKeys) {
buf.writeInt32BE(index);
this._packValue(buf, objType.elementType, objType.elementTypeClass,
this.unpackedAssocArray.get(index));
}
} else {
buf.writeLength(this.unpackedArray.length);
for (const value of this.unpackedArray) {
this._packValue(buf, objType.elementType, objType.elementTypeClass,
value);
}
}
} else {
for (const attr of objType.attributes) {
this._packValue(buf, attr.type, attr.typeClass,
this.unpackedAttrs.get(attr.name));
}
}
}
//---------------------------------------------------------------------------
// _packValue()
//
// Packs a value into the buffer. At this point it is assumed that the value
// matches the correct type.
//---------------------------------------------------------------------------
_packValue(buf, type, typeClass, value) {
if (value === null || value === undefined) {
if (typeClass && !typeClass.prototype.isCollection) {
buf.writeUInt8(constants.TNS_OBJ_ATOMIC_NULL);
} else {
buf.writeUInt8(constants.TNS_NULL_LENGTH_INDICATOR);
}
} else {
switch (type) {
case types.DB_TYPE_CHAR:
case types.DB_TYPE_VARCHAR:
buf.writeBytesWithLength(Buffer.from(value));
break;
case types.DB_TYPE_NCHAR:
case types.DB_TYPE_NVARCHAR:
buf.writeBytesWithLength(Buffer.From(value, 'utf16-le').swap16());
break;
case types.DB_TYPE_NUMBER:
buf.writeOracleNumber(value.toString());
break;
case types.DB_TYPE_BINARY_INTEGER:
case types.DB_TYPE_BOOLEAN:
buf.writeUInt8(4);
buf.writeUInt32BE(value);
break;
case types.DB_TYPE_RAW:
buf.writeBytesWithLength(value);
break;
case types.DB_TYPE_BINARY_DOUBLE:
buf.writeBinaryDouble(value);
break;
case types.DB_TYPE_BINARY_FLOAT:
buf.writeBinaryFloat(value);
break;
case types.DB_TYPE_DATE:
case types.DB_TYPE_TIMESTAMP:
case types.DB_TYPE_TIMESTAMP_LTZ:
case types.DB_TYPE_TIMESTAMP_TZ:
buf.writeOracleDate(value, type);
break;
case types.DB_TYPE_BLOB:
case types.DB_TYPE_CLOB:
case types.DB_TYPE_NCLOB:
buf.writeLob(value);
break;
case types.DB_TYPE_OBJECT:
if (this._objType.isCollection || value._objType.isCollection) {
buf.writeBytesWithLength(value._getPackedData());
} else {
value._packData(buf);
}
break;
default:
errors.throwErr(errors.ERR_NOT_IMPLEMENTED, type);
}
}
}
//---------------------------------------------------------------------------
// _unpackData()
//
// Unpacks the packed data into a map of JavaScript values.
//---------------------------------------------------------------------------
_unpackData() {
const buf = new DbObjectPickleBuffer(this.packedData);
buf.readHeader(this);
this._unpackDataFromBuf(buf);
this.packedData = undefined;
}
//---------------------------------------------------------------------------
// _unpackDataFromBuf()
//
// Unpacks the data in the buffer into a map of JavaScript values.
//---------------------------------------------------------------------------
_unpackDataFromBuf(buf) {
let unpackedArray, unpackedAssocArray, assocIndex, unpackedAttrs;
const objType = this._objType;
if (objType.isCollection) {
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
unpackedAssocArray = new Map();
} else {
unpackedArray = [];
}
this.collectionFlags = buf.readUInt8();
const numElements = buf.readLength();
for (let i = 0; i < numElements; i++) {
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
assocIndex = buf.readUInt32BE();
}
const value = this._unpackValue(buf, objType.elementType,
objType.elementTypeClass);
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
unpackedAssocArray.set(assocIndex, value);
} else {
unpackedArray.push(value);
}
}
} else {
unpackedAttrs = new Map();
for (const attr of objType.attributes) {
const value = this._unpackValue(buf, attr.type, attr.typeClass);
unpackedAttrs.set(attr.name, value);
}
}
this.unpackedAttrs = unpackedAttrs;
this.unpackedArray = unpackedArray;
this.unpackedAssocArray = unpackedAssocArray;
}
//---------------------------------------------------------------------------
// _unpackValue()
//
// Unpacks a single value and returns it.
//---------------------------------------------------------------------------
_unpackValue(buf, type, typeClass) {
let isNull, obj, value;
switch (type) {
case types.DB_TYPE_NUMBER:
value = buf.readOracleNumber();
if (value !== null)
value = parseFloat(value);
return value;
case types.DB_TYPE_BINARY_INTEGER:
return buf.readBinaryInteger();
case types.DB_TYPE_VARCHAR:
case types.DB_TYPE_CHAR:
return buf.readStr(constants.TNS_CS_IMPLICIT);
case types.DB_TYPE_NVARCHAR:
case types.DB_TYPE_NCHAR:
return buf.readStr(constants.TNS_CS_NCHAR);
case types.DB_TYPE_RAW:
return buf.readBytes();
case types.DB_TYPE_BINARY_DOUBLE:
return buf.readBinaryDouble();
case types.DB_TYPE_BINARY_FLOAT:
return buf.readBinaryFloat();
case types.DB_TYPE_DATE:
case types.DB_TYPE_TIMESTAMP:
return buf.readOracleDate(true);
case types.DB_TYPE_TIMESTAMP_LTZ:
case types.DB_TYPE_TIMESTAMP_TZ:
return buf.readOracleDate(false);
case types.DB_TYPE_BLOB:
case types.DB_TYPE_CLOB:
case types.DB_TYPE_NCLOB:
return buf.readLob(this.type.typeClass._connection, type);
case types.DB_TYPE_BOOLEAN:
return buf.readBool();
case types.DB_TYPE_OBJECT:
isNull = buf.getIsAtomicNull();
if (isNull)
return null;
obj = new ThinDbObjectImpl(typeClass);
if (obj._objType.isCollection || this._objType.isCollection) {
obj.packedData = buf.readBytesWithLength();
} else {
obj._unpackDataFromBuf(buf);
}
return obj;
default:
errors.throwErr(errors.ERR_NOT_IMPLEMENTED, type);
}
}
//---------------------------------------------------------------------------
// append()
//
// Appends an element to the collection.
//---------------------------------------------------------------------------
append(value) {
this._ensureUnpacked();
if (this.unpackedArray) {
const objType = this._objType;
if (objType.maxNumElements > 0 &&
this.unpackedArray.length >= objType.maxNumElements) {
errors.throwErr(errors.ERR_INVALID_COLL_INDEX_SET,
this.unpackedArray.length, 0, objType.maxNumElements);
}
this.unpackedArray.push(value);
} else {
this._ensureAssocKeys();
let newIndex;
if (this.unpackedAssocKeys.length === 0) {
newIndex = 0;
} else {
const keyIndex = this.unpackedAssocKeys.length - 1;
newIndex = this.unpackedAssocKeys[keyIndex] + 1;
}
this.unpackedAssocArray.set(newIndex, value);
this.unpackedAssocKeys.push(newIndex);
}
}
//---------------------------------------------------------------------------
// deleteElement()
//
// Deletes an element from a collection.
//---------------------------------------------------------------------------
deleteElement(index) {
this._ensureUnpacked();
if (this.unpackedArray) {
if (this._objType.collectionType == constants.TNS_OBJ_VARRAY) {
errors.throwErr(errors.ERR_DELETE_ELEMENTS_OF_VARRAY);
}
this.unpackedArray.splice(index, 1);
} else {
this._unpackedAssocKeys = undefined;
this.unpackedAssocArray.delete(index);
}
}
//---------------------------------------------------------------------------
// getAttrValue()
//
// Returns the value of the given attribute on the object.
//---------------------------------------------------------------------------
getAttrValue(attr) {
this._ensureUnpacked();
const value = this.unpackedAttrs.get(attr.name);
if (value === undefined)
return null;
return value;
}
//---------------------------------------------------------------------------
// getElement()
//
// Returns an element from the collection.
//---------------------------------------------------------------------------
getElement(index) {
let value;
this._ensureUnpacked();
if (this.unpackedArray) {
value = this.unpackedArray[index];
} else {
value = this.unpackedAssocArray.get(index);
}
if (value === undefined) {
errors.throwErr(errors.ERR_INVALID_COLL_INDEX_GET, index);
}
return value;
}
//---------------------------------------------------------------------------
// getFirstIndex()
//
// Returns the first index in a collection.
//---------------------------------------------------------------------------
getFirstIndex() {
this._ensureUnpacked();
if (this.unpackedArray) {
return 0;
} else if (this.unpackedAssocArray) {
this._ensureAssocKeys();
return this.unpackedAssocKeys[0];
}
}
//---------------------------------------------------------------------------
// getKeys()
//
// Returns the keys of the collection in a JavaScript array.
//---------------------------------------------------------------------------
getKeys() {
this._ensureUnpacked();
if (this.unpackedArray) {
return Array.from(this.unpackedArray.keys());
} else if (this.unpackedAssocArray) {
this._ensureAssocKeys();
return Array.from(this.unpackedAssocKeys);
}
return [];
}
//---------------------------------------------------------------------------
// getLastIndex()
//
// Returns the last index in a collection.
//---------------------------------------------------------------------------
getLastIndex() {
this._ensureUnpacked();
if (this.unpackedArray) {
if (this.unpackedArray.length > 0)
return this.unpackedArray.length - 1;
} else if (this.unpackedAssocArray) {
this._ensureAssocKeys();
return this.unpackedAssocKeys[this.unpackedAssocKeys.length - 1];
}
}
//---------------------------------------------------------------------------
// getNextIndex()
//
// Returns the next index in a collection.
//---------------------------------------------------------------------------
getNextIndex(index) {
this._ensureUnpacked();
if (this.unpackedArray) {
if (index + 1 < this.unpackedArray.length) {
return index + 1;
}
} else if (this.unpackedAssocArray) {
this._ensureAssocKeys();
for (const key of this.unpackedAssocKeys) {
if (key > index)
return key;
}
}
}
//---------------------------------------------------------------------------
// getPrevIndex()
//
// Returns the previous index in a collection.
//---------------------------------------------------------------------------
getPrevIndex(index) {
this._ensureUnpacked();
if (this.unpackedArray) {
if (index > 0) {
return index - 1;
}
} else if (this.unpackedAssocArray) {
this._ensureAssocKeys();
for (const key of this.unpackedAssocKeys.reverse()) {
if (key < index)
return key;
}
}
}
//---------------------------------------------------------------------------
// getValues()
//
// Returns the values of the collection in a JavaScript array.
//---------------------------------------------------------------------------
getValues() {
const result = [];
this._ensureUnpacked();
if (this.unpackedArray) {
return Array.from(this.unpackedArray);
} else if (this.unpackedAssocArray) {
this._ensureAssocKeys();
for (const key of this.unpackedAssocKeys) {
result.push(this.unpackedAssocArray.get(key));
}
}
return result;
}
//---------------------------------------------------------------------------
// hasElement()
//
// Returns whether an element exists at the given index.
//---------------------------------------------------------------------------
hasElement(index) {
this._ensureUnpacked();
if (this.unpackedArray) {
return (index >= 0 && index < this.unpackedArray.length);
}
return this.unpackedAssocArray.has(index);
}
//---------------------------------------------------------------------------
// setAttrValue()
//
// Sets the value of the attribute on the object to the given value.
//---------------------------------------------------------------------------
setAttrValue(attr, value) {
this._ensureUnpacked();
this.unpackedAttrs.set(attr.name, value);
}
//---------------------------------------------------------------------------
// setElement()
//
// Sets an entry in a collection that is indexed by integers.
//---------------------------------------------------------------------------
setElement(index, value) {
this._ensureUnpacked();
if (this.unpackedArray) {
const maxIndex = Math.max(this.unpackedArray.length - 1, 0);
if (index > maxIndex) {
errors.throwErr(errors.ERR_INVALID_COLL_INDEX_SET, index, 0, maxIndex);
}
this.unpackedArray[index] = value;
} else {
if (!this.unpackedAssocArray.has(index))
this.unpackedAssocKeys = undefined;
this.unpackedAssocArray.set(index, value);
}
}
//---------------------------------------------------------------------------
// trim()
//
// Trim the specified number of elements from the end of the collection.
//---------------------------------------------------------------------------
trim(numToTrim) {
this._ensureUnpacked();
if (numToTrim > 0) {
this.unpackedArray = this.unpackedArray.slice(0,
this.unpackedArray.length - numToTrim);
}
}
}
module.exports = ThinDbObjectImpl;

View File

@ -30,9 +30,11 @@ const ThinConnectionImpl = require('./connection.js');
const ThinResultSetImpl = require('./resultSet.js');
const ThinPoolImpl = require('./pool.js');
const ThinLobImpl = require('./lob.js');
const ThinDbObjectImpl = require('./dbObject.js');
const impl = require('../impl');
impl.ConnectionImpl = ThinConnectionImpl;
impl.ResultSetImpl = ThinResultSetImpl;
impl.PoolImpl = ThinPoolImpl;
impl.LobImpl = ThinLobImpl;
impl.DbObjectImpl = ThinDbObjectImpl;

View File

@ -194,7 +194,7 @@ class BaseBuffer {
// is assumed at this point that the buffer only contains the encoded numeric
// data.
//---------------------------------------------------------------------------
parseOracleNumber(buf, desiredType) {
parseOracleNumber(buf) {
// the first byte is the exponent; positive numbers have the highest
// order bit set, whereas negative numbers have the highest order bit
@ -211,9 +211,9 @@ class BaseBuffer {
// of -1e126 (if negative)
if (buf.length === 1) {
if (isPositive) {
return 0;
return "0";
}
return -1e126;
return "-1e126";
}
// check for the trailing 102 byte for negative numbers and, if present,
@ -289,11 +289,7 @@ class BaseBuffer {
}
// convert result to a Number
const text = chars.join("");
if (desiredType === types.DB_TYPE_VARCHAR) {
return text;
}
return parseFloat(text);
return chars.join("");
}
//---------------------------------------------------------------------------
@ -302,16 +298,12 @@ class BaseBuffer {
// Reads a binary double value from the buffer and returns a Number or a
// String, depending on the desired type.
//---------------------------------------------------------------------------
readBinaryDouble(desiredType) {
readBinaryDouble() {
const buf = this.readBytesWithLength();
if (!buf) {
return null;
}
const val = this.parseBinaryDouble(buf);
if (desiredType === types.DB_TYPE_VARCHAR) {
return val.toString();
}
return val;
return this.parseBinaryDouble(buf);
}
//---------------------------------------------------------------------------
@ -320,16 +312,12 @@ class BaseBuffer {
// Reads a binary float value from the buffer and returns a Number or a
// String, depending on the desired type.
//---------------------------------------------------------------------------
readBinaryFloat(desiredType) {
readBinaryFloat() {
const buf = this.readBytesWithLength();
if (!buf) {
return null;
}
const val = this.parseBinaryFloat(buf);
if (desiredType === types.DB_TYPE_VARCHAR) {
return val.toString();
}
return val;
return this.parseBinaryFloat(buf);
}
//---------------------------------------------------------------------------
@ -376,6 +364,31 @@ class BaseBuffer {
return this._readBytesWithLength(numBytes);
}
//---------------------------------------------------------------------------
// readDbObject()
//
// Reads a database object from the buffer and returns the implementation
// object (or null, if the object is atomically null).
//---------------------------------------------------------------------------
readDbObject() {
const obj = {};
let numBytes = this.readUB4();
if (numBytes > 0)
obj.toid = this.readBytesWithLength();
numBytes = this.readUB4();
if (numBytes > 0)
obj.oid = this.readBytesWithLength();
numBytes = this.readUB4();
if (numBytes > 0)
obj.snapshot = this.readBytesWithLength();
this.skipUB2(); // version
numBytes = this.readUB4();
this.skipUB2(); // flags
if (numBytes > 0)
obj.packedData = this.readBytesWithLength();
return obj;
}
//---------------------------------------------------------------------------
// readInt8()
//
@ -392,16 +405,12 @@ class BaseBuffer {
// Reads an Oracle date from the buffer and returns a Date or a String,
// depending on the desired type.
//---------------------------------------------------------------------------
readOracleDate(desiredType, useLocalTime) {
readOracleDate(useLocalTime) {
const buf = this.readBytesWithLength();
if (!buf) {
return null;
}
const val = this.parseOracleDate(buf, useLocalTime);
if (desiredType === types.DB_TYPE_VARCHAR) {
return val.toString();
}
return val;
return this.parseOracleDate(buf, useLocalTime);
}
//---------------------------------------------------------------------------
@ -410,12 +419,12 @@ class BaseBuffer {
// Reads an Oracle number from the buffer and returns a Number or a String,
// depending on the desired type.
//---------------------------------------------------------------------------
readOracleNumber(desiredType) {
readOracleNumber() {
const buf = this.readBytesWithLength();
if (!buf) {
return null;
}
return this.parseOracleNumber(buf, desiredType);
return this.parseOracleNumber(buf);
}
//---------------------------------------------------------------------------
@ -692,6 +701,28 @@ class BaseBuffer {
}
}
//---------------------------------------------------------------------------
// writeDbObject()
//
// Writes a database object to the buffer.
//---------------------------------------------------------------------------
writeDbObject(obj) {
this.writeUB4(obj.toid.length);
this.writeBytesWithLength(obj.toid);
if (obj.oid) {
this.writeUB4(obj.oid.length);
this.writeBytesWithLength(obj.oid);
} else {
this.writeUB4(0);
}
this.writeUB4(0); // snapshot
this.writeUB4(0); // version
const packedData = obj._getPackedData();
this.writeUB4(packedData.length);
this.writeUB4(obj.flags);
this.writeBytesWithLength(packedData);
}
//---------------------------------------------------------------------------
// writeOracleDate()
//
@ -858,6 +889,16 @@ class BaseBuffer {
this.writeBytes(Buffer.from(s));
}
//---------------------------------------------------------------------------
// writeInt32BE()
//
// Writes a signed 32-bit integer to the buffer in big endian order.
//---------------------------------------------------------------------------
writeInt32BE(n) {
const buf = this.reserveBytes(4);
buf.writeInt32BE(n);
}
//---------------------------------------------------------------------------
// writeUB4()
//
@ -965,9 +1006,13 @@ class GrowableBuffer extends BaseBuffer {
//
// Initializes the buffer with an initial fixed chunk size.
//---------------------------------------------------------------------------
constructor() {
super(constants.BUFFER_CHUNK_SIZE);
this.size = this.maxSize;
constructor(initializer) {
if (initializer) {
super(initializer);
} else {
super(constants.BUFFER_CHUNK_SIZE);
this.size = this.maxSize;
}
}
//---------------------------------------------------------------------------

View File

@ -33,6 +33,8 @@ module.exports = {
// constants from upper level exposed here in order to avoid having multiple
// files containing constants
BIND_IN: constants.BIND_IN,
BIND_INOUT: constants.BIND_INOUT,
BIND_OUT: constants.BIND_OUT,
CLIENT_VERSION:
constants.VERSION_MAJOR << 24 |
constants.VERSION_MINOR << 20 |
@ -696,15 +698,52 @@ module.exports = {
TZ_HOUR_OFFSET: 20,
TZ_MINUTE_OFFSET: 60,
// Preferred Num Types
NUM_TYPE_FLOAT: 0,
NUM_TYPE_INT: 1,
NUM_TYPE_DECIMAL: 2,
NUM_TYPE_STR: 3,
// drcp release mode
DRCP_DEAUTHENTICATE: 0x00000002,
// database object image flags
TNS_OBJ_IS_VERSION_81: 0x80,
TNS_OBJ_IS_DEGENERATE: 0x10,
TNS_OBJ_IS_COLLECTION: 0x08,
TNS_OBJ_NO_PREFIX_SEG: 0x04,
TNS_OBJ_IMAGE_VERSION: 1,
// database object flags
TNS_OBJ_MAX_SHORT_LENGTH: 245,
TNS_OBJ_ATOMIC_NULL: 253,
TNS_OBJ_NON_NULL_OID: 0x02,
TNS_OBJ_HAS_EXTENT_OID: 0x08,
TNS_OBJ_TOP_LEVEL: 0x01,
TNS_OBJ_HAS_INDEXES: 0x10,
// database object collection types
TNS_OBJ_PLSQL_INDEX_TABLE: 1,
TNS_OBJ_NESTED_TABLE: 2,
TNS_OBJ_VARRAY: 3,
// database object TDS type codes
TNS_OBJ_TDS_TYPE_CHAR: 1,
TNS_OBJ_TDS_TYPE_DATE: 2,
TNS_OBJ_TDS_TYPE_FLOAT: 5,
TNS_OBJ_TDS_TYPE_NUMBER: 6,
TNS_OBJ_TDS_TYPE_VARCHAR: 7,
TNS_OBJ_TDS_TYPE_BOOLEAN: 8,
TNS_OBJ_TDS_TYPE_RAW: 19,
TNS_OBJ_TDS_TYPE_TIMESTAMP: 21,
TNS_OBJ_TDS_TYPE_TIMESTAMP_TZ: 23,
TNS_OBJ_TDS_TYPE_OBJ: 27,
TNS_OBJ_TDS_TYPE_COLL: 28,
TNS_OBJ_TDS_TYPE_CLOB: 29,
TNS_OBJ_TDS_TYPE_BLOB: 30,
TNS_OBJ_TDS_TYPE_TIMESTAMP_LTZ: 33,
TNS_OBJ_TDS_TYPE_BINARY_FLOAT: 37,
TNS_OBJ_TDS_TYPE_BINARY_DOUBLE: 45,
// xml type constants
TNS_XML_TYPE_LOB: 0x0001,
TNS_XML_TYPE_STRING: 0x0004,
TNS_XML_TYPE_FLAG_SKIP_NEXT_4: 0x100000,
// errors
TNS_ERR_VAR_NOT_IN_SELECT_LIST: 1007,
TNS_ERR_INBAND_MESSAGE: 12573,
@ -720,5 +759,6 @@ module.exports = {
BUFFER_CHUNK_SIZE: 65536,
CHUNKED_BYTES_CHUNK_SIZE: 65536,
TNS_BASE64_ALPHABET_ARRAY: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
TNS_BASE64_ALPHABET_ARRAY: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'),
TNS_EXTENT_OID: Buffer.from('00000000000000000000000000010001', 'hex')
};

View File

@ -29,6 +29,7 @@
const utils = require("../utils");
const constants = require("../constants.js");
const Message = require("./base.js");
const ThinDbObjectImpl = require("../../dbObject.js");
const ThinLobImpl = require("../../lob.js");
const errors = require('../../../errors');
const types = require('../../../types.js');
@ -54,6 +55,7 @@ class MessageWithData extends Message {
this.outVariables = [];
this.inFetch = false;
this.parseOnly = false;
this.resultSetsToSetup = [];
}
/**
@ -128,11 +130,11 @@ class MessageWithData extends Message {
if (statement.numQueryVars > 0) {
buf.skipUB1();
}
const metadata = [];
resultSet.metadata = [];
for (let i = 0; i < statement.numQueryVars; i++) {
const variable = this.processColumnInfo(buf, i + 1);
statement.queryVars.push(variable);
metadata.push(variable.fetchInfo);
resultSet.metadata.push(variable.fetchInfo);
}
let numBytes = buf.readUB4();
@ -148,25 +150,7 @@ class MessageWithData extends Message {
buf.skipBytes(numBytes + 1);
}
resultSet._setup(this.options, metadata);
for (let i = 0; i < metadata.length; i++) {
const variable = statement.queryVars[i];
// LOBs always require define and they change the type that is actually
// returned by the server
if (variable.type === types.DB_TYPE_CLOB ||
variable.type === types.DB_TYPE_NCLOB ||
variable.type === types.DB_TYPE_BLOB ||
variable.type === types.DB_TYPE_JSON) {
if (variable.type !== variable.fetchInfo.fetchType) {
variable.type = variable.fetchInfo.fetchType;
variable.maxSize = constants.TNS_MAX_LONG_LENGTH;
}
statement.requiresDefine = true;
}
}
this.resultSetsToSetup.push(resultSet);
}
processColumnInfo(buf, columnNum) {
@ -186,13 +170,14 @@ class MessageWithData extends Message {
const maxSize = buf.readUB4();
buf.skipUB4(); // max number of array elements
buf.skipUB4(); // cont flags
let oid;
let numBytes = buf.readUB4(); // OID
if (numBytes > 0) {
buf.skipBytes(numBytes + 1);
oid = buf.readBytesWithLength();
}
buf.skipUB2(); // version
buf.skipUB2(); // character set id
const csfrm = buf.readUInt8(); // character set form
const csfrm = buf.readUInt8(); // character set form
let size = buf.readUB4();
if (dataType === constants.TNS_DATA_TYPE_RAW) {
size = maxSize;
@ -207,15 +192,15 @@ class MessageWithData extends Message {
if (numBytes > 0) {
name = buf.readStr(constants.TNS_CS_IMPLICIT);
}
let schema;
numBytes = buf.readUB4();
if (numBytes > 0) {
buf.skipUB1(); // skip repeated length
buf.skipBytes(numBytes); // schema name
schema = buf.readStr(constants.TNS_CS_IMPLICIT);
}
numBytes = buf.readUB4();
let typeName;
if (numBytes > 0) {
buf.skipUB1(); // skip repeated length
buf.skipBytes(numBytes); // type name
typeName = buf.readStr(constants.TNS_CS_IMPLICIT);
}
buf.skipUB2(); // column position
buf.skipUB4(); // uds flag
@ -242,6 +227,13 @@ class MessageWithData extends Message {
case types.DB_TYPE_TIMESTAMP_LTZ:
fetchInfo.precision = scale;
break;
case types.DB_TYPE_OBJECT:
fetchInfo.dbTypeClass = this.connection._getDbObjectType(schema,
typeName, undefined, oid);
if (fetchInfo.dbTypeClass.partial) {
this.connection._partialDbObjectTypes.push(fetchInfo.dbTypeClass);
}
break;
default:
break;
}
@ -356,8 +348,6 @@ class MessageWithData extends Message {
const oraTypeNum = dbType._oraTypeNum;
const csfrm = dbType._csfrm;
const maxSize = variable.maxSize;
const outputType = (variable.fetchInfo) ? variable.fetchInfo.fetchType :
variable.type;
let colValue = null;
if (maxSize === 0 && oraTypeNum !== constants.TNS_DATA_TYPE_LONG
@ -380,7 +370,9 @@ class MessageWithData extends Message {
colValue = Buffer.from(colValue);
}
} else if (oraTypeNum === constants.TNS_DATA_TYPE_NUMBER) {
colValue = buf.readOracleNumber(outputType);
colValue = buf.readOracleNumber();
if (!this.inFetch && colValue !== null)
colValue = parseFloat(colValue);
} else if (
oraTypeNum === constants.TNS_DATA_TYPE_DATE ||
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP ||
@ -389,7 +381,7 @@ class MessageWithData extends Message {
) {
const useLocalTime = (oraTypeNum === constants.TNS_DATA_TYPE_DATE ||
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP);
colValue = buf.readOracleDate(outputType, useLocalTime);
colValue = buf.readOracleDate(useLocalTime);
} else if (oraTypeNum === constants.TNS_DATA_TYPE_ROWID) {
if (!this.inFetch) {
colValue = buf.readStr(constants.TNS_CS_IMPLICIT);
@ -409,11 +401,13 @@ class MessageWithData extends Message {
colValue = buf.readURowID();
}
} else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_DOUBLE) {
colValue = buf.readBinaryDouble(outputType);
colValue = buf.readBinaryDouble();
} else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_FLOAT) {
colValue = buf.readBinaryFloat(outputType);
colValue = buf.readBinaryFloat();
} else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_INTEGER) {
colValue = buf.readOracleNumber(constants.NUM_TYPE_INT);
colValue = buf.readOracleNumber();
if (colValue !== null)
colValue = parseFloat(colValue);
} else if (oraTypeNum === constants.TNS_DATA_TYPE_CURSOR) {
let numBytes = buf.readUInt8();
if (isNullLength(numBytes)) {
@ -424,7 +418,11 @@ class MessageWithData extends Message {
// If the cursor ID is 0 for the returned ref cursor then
// it is an invalid cursor
if (colValue.statement.cursorId === 0 && variable.dir !== constants.BIND_IN) {
errors.throwErr(errors.ERR_INVALID_REF_CURSOR);
if (this.options.nullifyInvalidCursor) {
colValue = null;
} else {
errors.throwErr(errors.ERR_INVALID_REF_CURSOR);
}
}
}
} else if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) {
@ -440,6 +438,15 @@ class MessageWithData extends Message {
}
} else if (oraTypeNum === constants.TNS_DATA_TYPE_JSON) {
colValue = buf.readOson();
} else if (oraTypeNum === constants.TNS_DATA_TYPE_INT_NAMED) {
const obj = buf.readDbObject();
if (obj.packedData) {
const objType = (variable.fetchInfo) ? variable.fetchInfo.dbTypeClass :
variable.typeClass;
colValue = new ThinDbObjectImpl(objType, obj.packedData);
colValue.toid = obj.toid;
colValue.oid = obj.oid;
}
} else {
errors.throwErr(errors.ERR_UNSUPPORTED_DATA_TYPE, dbType.num,
variable.columnNum);
@ -526,6 +533,24 @@ class MessageWithData extends Message {
}
}
}
await this.connection._populatePartialDbObjectTypes();
for (const resultSet of this.resultSetsToSetup) {
resultSet._setup(this.options, resultSet.metadata);
// LOBs always require define and they change the type that is actually
// returned by the server
for (const variable of resultSet.statement.queryVars) {
if (variable.type === types.DB_TYPE_CLOB ||
variable.type === types.DB_TYPE_NCLOB ||
variable.type === types.DB_TYPE_BLOB ||
variable.type === types.DB_TYPE_JSON) {
if (variable.type !== variable.fetchInfo.fetchType) {
variable.type = variable.fetchInfo.fetchType;
variable.maxSize = constants.TNS_MAX_LONG_LENGTH;
}
resultSet.statement.requiresDefine = true;
}
}
}
}
preProcess() {
@ -640,8 +665,15 @@ class MessageWithData extends Message {
buf.writeUB4(0); // max num elements
}
buf.writeUB4(contFlag);
buf.writeUB4(0); // OID
buf.writeUB4(0); // version
if (variable.objType) {
const objType = variable.objType;
buf.writeUB4(objType.oid.length);
buf.writeBytesWithLength(objType.oid);
buf.writeUB4(objType.version);
} else {
buf.writeUB4(0); // OID
buf.writeUB4(0); // version
}
if (variable.type._csfrm !== 0) {
buf.writeUB4(constants.TNS_CHARSET_UTF8);
} else {
@ -697,6 +729,13 @@ class MessageWithData extends Message {
if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) {
buf.writeUInt8(constants.TNS_ESCAPE_CHAR);
buf.writeUInt8(1);
} else if (oraTypeNum === constants.TNS_DATA_TYPE_INT_NAMED) {
buf.writeUB4(0); // TOID
buf.writeUB4(0); // OID
buf.writeUB4(0); // snapshot
buf.writeUB4(0); // version
buf.writeUB4(0); // packed data length
buf.writeUB4(constants.TNS_OBJ_TOP_LEVEL); // flags
} else {
buf.writeUInt8(0);
}
@ -757,6 +796,8 @@ class MessageWithData extends Message {
buf.writeBytesWithLength(Buffer.from(value));
} else if (oraTypeNum === constants.TNS_DATA_TYPE_JSON) {
buf.writeOson(value);
} else if (oraTypeNum === constants.TNS_DATA_TYPE_INT_NAMED) {
buf.writeDbObject(value);
} else {
const message = `Binding data of type ${variable.type}`;
errors.throwErr(errors.ERR_NOT_IMPLEMENTED, message);

View File

@ -139,7 +139,7 @@ class OsonDecoder extends BaseBuffer {
} else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT32) {
return this.readBytes(this.readUInt32BE()).toString();
} else if (nodeType === constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8) {
return this.readOracleNumber(types.DB_TYPE_NUMBER);
return parseFloat(this.readOracleNumber());
} else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16) {
return Buffer.from(this.readBytes(this.readUInt16BE()));
} else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32) {
@ -150,13 +150,12 @@ class OsonDecoder extends BaseBuffer {
const typeBits = nodeType & 0xf0;
if (typeBits === 0x20 || typeBits === 0x60) {
const len = nodeType & 0x0f;
return this.parseOracleNumber(this.readBytes(len + 1),
types.DB_TYPE_NUMBER);
return parseFloat(this.parseOracleNumber(this.readBytes(len + 1)));
// handle integer with length stored inside the node itself
} else if (typeBits === 0x40 || typeBits === 0x50) {
const len = nodeType & 0x0f;
return this.parseOracleNumber(this.readBytes(len), types.DB_TYPE_NUMBER);
return parseFloat(this.parseOracleNumber(this.readBytes(len)));
// handle string with length stored inside the node itself
} else if ((nodeType & 0xe0) == 0) {

View File

@ -32,6 +32,7 @@ const util = require('util');
const dbTypeByNum = new Map();
const dbTypeByOraTypeNum = new Map();
const dbTypeByColumnTypeName = new Map();
// define class used for database types
class DbType {
@ -46,6 +47,7 @@ class DbType {
dbTypeByNum.set(num, this);
const key = (options.csfrm || 0) * 256 + options.oraTypeNum;
dbTypeByOraTypeNum.set(key, this);
dbTypeByColumnTypeName.set(columnTypeName, this);
}
[Symbol.toPrimitive](hint) {
@ -67,6 +69,19 @@ class DbType {
}
//-----------------------------------------------------------------------------
// getTypeByColumnTypeName()
//
// Return the type given a column type name. If the column type name cannot be
// found an exception is thrown.
//-----------------------------------------------------------------------------
function getTypeByColumnTypeName(name) {
const dbType = dbTypeByColumnTypeName.get(name);
if (!dbType)
errors.throwErr(errors.ERR_UNKNOWN_COLUMN_TYPE_NAME, name);
return dbType;
}
//-----------------------------------------------------------------------------
// getTypeByNum()
//
@ -292,6 +307,18 @@ const DB_TYPE_FETCH_TYPE_MAP = new Map([
[DB_TYPE_VARCHAR, DB_TYPE_VARCHAR]
]);
// additional aliases for types by column type name
dbTypeByColumnTypeName.set("DOUBLE PRECISION", DB_TYPE_NUMBER);
dbTypeByColumnTypeName.set("FLOAT", DB_TYPE_NUMBER);
dbTypeByColumnTypeName.set("INTEGER", DB_TYPE_NUMBER);
dbTypeByColumnTypeName.set("PL/SQL BOOLEAN", DB_TYPE_BOOLEAN);
dbTypeByColumnTypeName.set("PL/SQL BINARY INTEGER", DB_TYPE_BINARY_INTEGER);
dbTypeByColumnTypeName.set("PL/SQL PLS INTEGER", DB_TYPE_BINARY_INTEGER);
dbTypeByColumnTypeName.set("REAL", DB_TYPE_NUMBER);
dbTypeByColumnTypeName.set("SMALLINT", DB_TYPE_NUMBER);
dbTypeByColumnTypeName.set("TIMESTAMP WITH LOCAL TZ", DB_TYPE_TIMESTAMP_LTZ);
dbTypeByColumnTypeName.set("TIMESTAMP WITH TZ", DB_TYPE_TIMESTAMP_TZ);
module.exports = {
DbType,
DB_TYPE_BFILE,
@ -324,6 +351,7 @@ module.exports = {
DB_TYPE_VARCHAR,
DB_TYPE_CONVERSION_MAP,
DB_TYPE_FETCH_TYPE_MAP,
getTypeByColumnTypeName,
getTypeByNum,
getTypeByOraTypeNum
};

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('200. dbObject1.js', () => {
describe('200. dbObject1.js', () => {
let conn;
const TYPE = 'NODB_TYP_OBJ_1';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('209. dbObject10.js', () => {
describe('209. dbObject10.js', () => {
let conn;
const TYPE = 'NODB_PERSON_TYP';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('210. dbObject11.js', () => {
describe('210. dbObject11.js', () => {
let conn;
const TYPE = 'NODB_RV_FIELD_TYP';

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('211. dbObject12.js', function() {
describe('211. dbObject12.js', function() {
let isRunnable = false;
let conn;

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('212. dbObject13.js', function() {
describe('212. dbObject13.js', function() {
let isRunnable = false;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('213. dbObject14.js', () => {
describe('213. dbObject14.js', () => {
let conn;
const TABLE = 'NODB_TAB_SPORTS';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2021, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2021, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -35,7 +35,7 @@ const oracledb = require('oracledb');
const assert = require('assert');
const dbConfig = require('./dbconfig.js');
(!oracledb.thin ? describe : describe.skip)('214. dbObject15.js', () => {
describe('214. dbObject15.js', () => {
let conn, FrisbeeTeam;
@ -104,12 +104,9 @@ const dbConfig = require('./dbconfig.js');
it('214.3 Negative - delete the collection element directly', function() {
assert.throws(
function() {
delete FrisbeeTeam[1];
},
/OCI-22164/
() => delete FrisbeeTeam[1],
/NJS-133:/
);
// OCI-22164: delete element operation is not allowed for variable-length array
}); // 214.3
it('214.4 Negative - collection.deleteElement()', function() {
@ -118,7 +115,7 @@ const dbConfig = require('./dbconfig.js');
let firstIndex = FrisbeeTeam.getFirstIndex();
FrisbeeTeam.deleteElement(firstIndex);
},
/OCI-22164/
/NJS-133:/
);
}); // 214.4
});

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('215. dbObject16.js', () => {
describe('215. dbObject16.js', () => {
let conn;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -37,7 +37,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('216. dbObject17.js', () => {
describe('216. dbObject17.js', () => {
let conn;

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('242. dbObject18.js', () => {
describe('242. dbObject18.js', () => {
describe('242.1 set oracledb.dbObjectAsPojo', () => {
@ -418,10 +418,14 @@ const testsUtil = require('./testsUtil.js');
const row = result.rows[0];
assert.strictEqual(row.SPORTNAME, 'Frisbee');
assert.throws(
() => row.TEAM[0],
/NJS-500:/
);
if (oracledb.thin) {
assert.strictEqual(row.TEAM[0].SHIRTNUMBER, 11);
} else {
assert.throws(
() => row.TEAM[0],
/NJS-500:/
);
}
// restore the connection
conn = await oracledb.getConnection(dbConfig);

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('243. dbObject19.js', () => {
describe('243. dbObject19.js', () => {
let conn;
const TYPE = 'NODB_PERSON_T';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('201. dbObject2.js', () => {
describe('201. dbObject2.js', () => {
let conn;
const TYPE = 'NODB_TYP_OBJ_2';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('202. dbObject3.js', () => {
describe('202. dbObject3.js', () => {
let conn;
const TYPE = 'NODB_TYP_OBJ_3';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('203. dbObject4.js', () => {
describe('203. dbObject4.js', () => {
let conn;
const TYPE = 'NODB_TYP_OBJ_4';
const TABLE = 'NODB_TAB_OBJ4';
@ -87,6 +87,9 @@ const testsUtil = require('./testsUtil.js');
)`;
let plsql = testsUtil.sqlCreateTable(TABLE, sql);
await conn.execute(plsql);
await conn.execute(proc1);
await conn.execute(proc2);
await conn.execute(proc3);
}); // before()
after(async () => {
@ -193,9 +196,6 @@ const testsUtil = require('./testsUtil.js');
}); // 203.4
it('203.5 call procedure with 2 OUT binds of DbObject', async function() {
await conn.execute(proc1);
await conn.execute(proc2);
await conn.execute(proc3);
let result = await conn.execute(
`BEGIN nodb_getDataCursor3(p_cur1 => :p_cur1,

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('204. dbObject5.js', () => {
describe('204. dbObject5.js', () => {
let conn;
const TYPE = 'NODB_TYP_OBJ_4';
const TABLE = 'NODB_TAB_OBJ4';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('205. dbObject6.js', () => {
describe('205. dbObject6.js', () => {
let conn;
const TABLE = 'NODB_TAB_TESTGEOMETRY';

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2019, 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2019, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('206. dbObject7.js', () => {
describe('206. dbObject7.js', () => {
let conn;
const TYPE = 'NODB_PERSON_T';

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('207. dbObject8.js', () => {
describe('207. dbObject8.js', () => {
let conn;
const TYPE1 = 'NODB_HARVEST_T';

View File

@ -36,7 +36,7 @@ const assert = require('assert');
const dbConfig = require('./dbconfig.js');
const testsUtil = require('./testsUtil.js');
(!oracledb.thin ? describe : describe.skip)('208. dbObject9.js', function() {
describe('208. dbObject9.js', function() {
let isRunnable = false;
let conn;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2022, Oracle and/or its affiliates. */
/* Copyright (c) 2022, 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
@ -35,7 +35,7 @@ const oracledb = require('oracledb');
const assert = require('assert');
const dbConfig = require('./dbconfig.js');
(!oracledb.thin ? describe : describe.skip)('262. dbObjectOutBind.js', function() {
describe('262. dbObjectOutBind.js', function() {
let conn = null;
let proc1 =
`create or replace procedure nodb_getDataCursor1(p_cur out sys_refcursor) is

View File

@ -35,7 +35,7 @@ const oracledb = require ('oracledb');
const assert = require ('assert');
const dbConfig = require ('./dbconfig.js');
(!oracledb.thin ? describe : describe.skip) ('197. dbObjectsNestedTable.js', () => {
describe('197. dbObjectsNestedTable.js', () => {
let connection = null;
before (async () => {

View File

@ -57,6 +57,7 @@ describe('162. getStmtInfo.js', function() {
it('162.1 SELECT', async function() {
const sql = "select 1 as col from dual";
const info = await conn.getStatementInfo(sql);
delete info.metaData[0].converter;
assert.deepStrictEqual(info,
{ bindNames: [], statementType: oracledb.STMT_TYPE_SELECT,
metaData: [

190
test/pipelinedTables.js Normal file
View File

@ -0,0 +1,190 @@
/* Copyright (c) 2023, Oracle and/or its affiliates. */
/******************************************************************************
*
* This software is dual-licensed to you under the Universal Permissive License
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
* 2.0 as shown at https://www.apache.org/licenses/LICENSE-2.0. You may choose
* either license.
*
* If you elect to accept the software under the Apache License, Version 2.0,
* the following applies:
*
* Licensed under the Apache License, Version 2.0 (the `License`);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an `AS IS` BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NAME
* 280. pipelinedTables.js
*
* DESCRIPTION
* Testing Pipelined Table Functions
*
*****************************************************************************/
'use strict';
const oracledb = require('oracledb');
const assert = require('assert');
const dbConfig = require('./dbconfig.js');
describe('280. pipelinedTables.js', function() {
let connection = null;
before('Get connection', async function() {
connection = await oracledb.getConnection(dbConfig);
});
after('Release connection', async function() {
await connection.close();
});
it('280.1 Creating and Invoking Pipelined Table Function', async function() {
await connection.execute(`CREATE OR REPLACE PACKAGE pkg1 AUTHID DEFINER AS
TYPE numset_t IS TABLE OF NUMBER;
FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED;
END pkg1;`);
await connection.execute(`CREATE OR REPLACE PACKAGE BODY pkg1 AS
FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED IS
BEGIN
FOR i IN 1..x LOOP
PIPE ROW(i);
END LOOP;
RETURN;
END f1;
END pkg1;`);
let result = await connection.execute(`SELECT * FROM TABLE (pkg1.f1(5))`);
assert.deepEqual(result.rows, [[1], [2], [3], [4], [5]]);
result = await connection.execute(`SELECT * FROM TABLE (pkg1.f1(2))`);
assert.deepEqual(result.rows, [[1], [2]]);
});
it('280.2 Invoking Pipelined Table Function with invalid syntax', async function() {
await connection.execute(`CREATE OR REPLACE PACKAGE pkg1 AUTHID DEFINER AS
TYPE numset_t IS TABLE OF NUMBER;
FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED;
END pkg1;
/`);
await connection.execute(`CREATE OR REPLACE PACKAGE BODY pkg1 AS
FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED IS
BEGIN
FOR i IN 1..x LOOP
PIPE ROW(j);
END LOOP;
RETURN;
END f1;
END pkg1;
`);
await assert.rejects(
async () => await connection.execute(`SELECT * FROM TABLE (pkg1.f1(5))`),
/ORA-06575:/ //ORA-06575: Package or function PKG1 is in an invalid state
);
});
it('280.3 Invoking normal Table followed by Pipelined table', async function() {
// create a schema-level nested table type of strings
await connection.execute(`CREATE OR REPLACE TYPE strings_t IS TABLE OF VARCHAR2 (100)`);
// compile a table function that returns a nested table of that type with a single string inside it
await connection.execute(`CREATE OR REPLACE FUNCTION strings
RETURN strings_t
AUTHID DEFINER
IS
l_strings strings_t := strings_t ('abc');
BEGIN
RETURN l_strings;
END;`);
// call the table function
let result = await connection.execute(`SELECT COLUMN_VALUE my_string FROM TABLE (strings ())`);
assert.deepEqual(result.rows, [['abc']]);
// create a pipelined version of that same table function
await connection.execute(`CREATE OR REPLACE FUNCTION strings_pl
RETURN strings_t
PIPELINED
AUTHID DEFINER
IS
BEGIN
PIPE ROW ('abc');
RETURN;
END;`);
result = await connection.execute(`SELECT COLUMN_VALUE my_string FROM TABLE (strings ())`);
assert.strictEqual(result.rows[0][0], 'abc');
});
it('280.4 Pipelined Table Functions with types', async function() {
// Create the types to support the Pipelined table function
await connection.execute(`CREATE TYPE ptf_row AS OBJECT (
id NUMBER,
description VARCHAR2(50)
)`);
await connection.execute(`CREATE TYPE ptf_tab IS TABLE OF ptf_row`);
// Build a pipelined table function
await connection.execute(`CREATE OR REPLACE FUNCTION get_tab_ptf (p_rows IN NUMBER) RETURN ptf_tab PIPELINED AS
BEGIN
FOR i IN 1 .. p_rows LOOP
PIPE ROW(ptf_row(i, 'Description for ' || i));
END LOOP;
RETURN;
END;`);
let result = await connection.execute(`SELECT *
FROM TABLE(get_tab_ptf(10))
ORDER BY id DESC`);
assert.deepEqual(result.rows, [[10, "Description for 10"],
[9, "Description for 9"],
[8, "Description for 8"],
[7, "Description for 7"],
[6, "Description for 6"],
[5, "Description for 5"],
[4, "Description for 4"],
[3, "Description for 3"],
[2, "Description for 2"],
[1, "Description for 1"]]);
await connection.execute(`DROP FUNCTION get_tab_ptf`);
await connection.execute(`DROP TYPE ptf_tab`);
await connection.execute(`DROP TYPE ptf_row`);
});
it('280.5 Parallel Enabled Pipelined Table Functions', async function() {
await connection.execute(`CREATE TABLE parallel_test (
id NUMBER(10),
country_code VARCHAR2(5),
description VARCHAR2(50)
)`);
await connection.execute(`INSERT /*+ APPEND */ INTO parallel_test
SELECT level AS id,
(CASE TRUNC(MOD(level, 4))
WHEN 1 THEN 'IN'
WHEN 2 THEN 'UK'
ELSE 'US'
END) AS country_code,
'Description or ' || level AS description
FROM dual
CONNECT BY level <= 100000`);
await connection.commit();
let result = await connection.execute(`SELECT country_code, count(*) FROM parallel_test GROUP BY country_code ORDER BY country_code ASC`);
assert.deepEqual(result.rows, [["IN", 25000], ["UK", 25000], ["US", 50000]]);
await connection.execute(`drop table parallel_test PURGE`);
});
});