UNPKG

oracledb

Version:

A Node.js module for Oracle Database access from JavaScript and TypeScript

598 lines (554 loc) 19.1 kB
// Copyright (c) 2022, 2025, 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('./constants.js'); const errors = require('./errors.js'); const util = require('util'); const dbTypeByNum = new Map(); const dbTypeByOraTypeNum = new Map(); const dbTypeByColumnTypeName = new Map(); const MAX_UINT32 = Math.pow(2, 32) - 1; // define class used for database types class DbType { constructor(num, name, columnTypeName, options) { this.num = num; this.name = name; this.columnTypeName = columnTypeName; this._bufferSizeFactor = options.bufferSizeFactor || 0; this._oraTypeNum = options.oraTypeNum || 0; this._csfrm = options.csfrm || 0; dbTypeByNum.set(num, this); const key = (options.csfrm || 0) * 256 + options.oraTypeNum; dbTypeByOraTypeNum.set(key, this); dbTypeByColumnTypeName.set(columnTypeName, this); } [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return this.num; default: return this.toString(); } } [util.inspect.custom]() { return this.toString(); } toString() { return `[DbType ${this.name}]`; } } //----------------------------------------------------------------------------- // 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() // // Return the type given the type number. If the type number is incorrect an // exception is thrown. //----------------------------------------------------------------------------- function getTypeByNum(num) { const dbType = dbTypeByNum.get(num); if (!dbType) errors.throwErr(errors.ERR_INVALID_TYPE_NUM, num); return dbType; } //----------------------------------------------------------------------------- // getTypeByOraTypeNum() // // Return the type given the Oracle type number and character set form. If the // Oracle type number and character set form are incorrect an exception is // thrown. //----------------------------------------------------------------------------- function getTypeByOraTypeNum(oraTypeNum, csfrm) { const key = (csfrm || 0) * 256 + oraTypeNum; const dbType = dbTypeByOraTypeNum.get(key); if (!dbType) errors.throwErr(errors.ERR_INVALID_ORACLE_TYPE_NUM, oraTypeNum, csfrm); return dbType; } const DB_TYPE_BFILE = new DbType(2020, "DB_TYPE_BFILE", "BFILE", { oraTypeNum: 114, bufferSizeFactor: 4000 }); const DB_TYPE_BINARY_DOUBLE = new DbType(2008, "DB_TYPE_BINARY_DOUBLE", "BINARY_DOUBLE", { oraTypeNum: 101, bufferSizeFactor: 8 }); const DB_TYPE_BINARY_FLOAT = new DbType(2007, "DB_TYPE_BINARY_FLOAT", "BINARY_FLOAT", { oraTypeNum: 100, bufferSizeFactor: 4 }); const DB_TYPE_BINARY_INTEGER = new DbType(2009, "DB_TYPE_BINARY_INTEGER", "BINARY_INTEGER", { oraTypeNum: 3, bufferSizeFactor: 22 }); const DB_TYPE_BLOB = new DbType(2019, "DB_TYPE_BLOB", "BLOB", { oraTypeNum: 113, bufferSizeFactor: 112 }); const DB_TYPE_BOOLEAN = new DbType(2022, "DB_TYPE_BOOLEAN", "BOOLEAN", { oraTypeNum: 252, bufferSizeFactor: 4 }); const DB_TYPE_CHAR = new DbType(2003, "DB_TYPE_CHAR", "CHAR", { oraTypeNum: 96, csfrm: constants.CSFRM_IMPLICIT, bufferSizeFactor: 4 }); const DB_TYPE_CLOB = new DbType(2017, "DB_TYPE_CLOB", "CLOB", { oraTypeNum: 112, csfrm: constants.CSFRM_IMPLICIT, bufferSizeFactor: 112 }); const DB_TYPE_CURSOR = new DbType(2021, "DB_TYPE_CURSOR", "CURSOR", { oraTypeNum: 102, bufferSizeFactor: 4 }); const DB_TYPE_DATE = new DbType(2011, "DB_TYPE_DATE", "DATE", { oraTypeNum: 12, bufferSizeFactor: 7 }); const DB_TYPE_INTERVAL_DS = new DbType(2015, "DB_TYPE_INTERVAL_DS", "INTERVAL DAY TO SECOND", { oraTypeNum: 183, bufferSizeFactor: 11 }); const DB_TYPE_INTERVAL_YM = new DbType(2016, "DB_TYPE_INTERVAL_YM", "INTERVAL YEAR TO MONTH", { oraTypeNum: 182, bufferSizeFactor: 5 }); const DB_TYPE_JSON = new DbType(2027, "DB_TYPE_JSON", "JSON", { oraTypeNum: 119 }); const DB_TYPE_LONG = new DbType(2024, "DB_TYPE_LONG", "LONG", { oraTypeNum: 8, csfrm: constants.CSFRM_IMPLICIT, bufferSizeFactor: 2 ** 31 - 1 }); const DB_TYPE_LONG_NVARCHAR = new DbType(2031, "DB_TYPE_LONG_NVARCHAR", "LONG", { oraTypeNum: 8, csfrm: constants.CSFRM_NCHAR, bufferSizeFactor: 2 ** 31 - 1 }); const DB_TYPE_LONG_RAW = new DbType(2025, "DB_TYPE_LONG_RAW", "LONG RAW", { oraTypeNum: 24, bufferSizeFactor: 2 ** 31 - 1 }); const DB_TYPE_NCHAR = new DbType(2004, "DB_TYPE_NCHAR", "NCHAR", { oraTypeNum: 96, csfrm: constants.CSFRM_NCHAR, bufferSizeFactor: 4 }); const DB_TYPE_NCLOB = new DbType(2018, "DB_TYPE_NCLOB", "NCLOB", { oraTypeNum: 112, csfrm: constants.CSFRM_NCHAR, bufferSizeFactor: 112 }); const DB_TYPE_NUMBER = new DbType(2010, "DB_TYPE_NUMBER", "NUMBER", { oraTypeNum: 2, bufferSizeFactor: 22 }); const DB_TYPE_NVARCHAR = new DbType(2002, "DB_TYPE_NVARCHAR", "NVARCHAR2", { oraTypeNum: 1, csfrm: constants.CSFRM_NCHAR, bufferSizeFactor: 4 }); const DB_TYPE_OBJECT = new DbType(2023, "DB_TYPE_OBJECT", "OBJECT", { oraTypeNum: 109 }); const DB_TYPE_RAW = new DbType(2006, "DB_TYPE_RAW", "RAW", { oraTypeNum: 23, bufferSizeFactor: 1 }); const DB_TYPE_ROWID = new DbType(2005, "DB_TYPE_ROWID", "ROWID", { oraTypeNum: 11, bufferSizeFactor: 18 }); const DB_TYPE_TIMESTAMP = new DbType(2012, "DB_TYPE_TIMESTAMP", "TIMESTAMP", { oraTypeNum: 180, bufferSizeFactor: 11 }); const DB_TYPE_TIMESTAMP_LTZ = new DbType(2014, "DB_TYPE_TIMESTAMP_LTZ", "TIMESTAMP WITH LOCAL TIME ZONE", { oraTypeNum: 231, bufferSizeFactor: 11 }); const DB_TYPE_TIMESTAMP_TZ = new DbType(2013, "DB_TYPE_TIMESTAMP_TZ", "TIMESTAMP WITH TIME ZONE", { oraTypeNum: 181, bufferSizeFactor: 13 }); const DB_TYPE_UROWID = new DbType(2030, "DB_TYPE_UROWID", "UROWID", { oraTypeNum: 208 }); const DB_TYPE_VARCHAR = new DbType(2001, "DB_TYPE_VARCHAR", "VARCHAR2", { oraTypeNum: 1, csfrm: constants.CSFRM_IMPLICIT, bufferSizeFactor: 4 }); const DB_TYPE_XMLTYPE = new DbType(2032, "DB_TYPE_XMLTYPE", "XMLTYPE", { oraTypeNum: 109, csfrm: constants.CSFRM_IMPLICIT, bufferSizeFactor: 2147483647 }); const DB_TYPE_VECTOR = new DbType(2033, "DB_TYPE_VECTOR", "VECTOR", { oraTypeNum: 127 }); // database type conversion map: the top level key refers to the database // type being fetched and the value is another map; this map's key is the // type requested by the user and its value is the actual type that will be // used in the define call; only entries are included where the database type // and the requested fetch type are different const DB_TYPE_CONVERSION_MAP = new Map([ [DB_TYPE_BINARY_DOUBLE, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] ])], [DB_TYPE_BINARY_FLOAT, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] ])], [DB_TYPE_BLOB, new Map([ [DB_TYPE_RAW, DB_TYPE_LONG_RAW], [DB_TYPE_LONG_RAW, DB_TYPE_LONG_RAW] ])], [DB_TYPE_CHAR, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] ])], [DB_TYPE_CLOB, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_LONG], [DB_TYPE_LONG, DB_TYPE_LONG] ])], [DB_TYPE_DATE, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR], [DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_LTZ] ])], [DB_TYPE_JSON, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] ])], [DB_TYPE_LONG, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_LONG] ])], [DB_TYPE_LONG_RAW, new Map([ [DB_TYPE_RAW, DB_TYPE_LONG_RAW] ])], [DB_TYPE_NCHAR, new Map([ [DB_TYPE_CHAR, DB_TYPE_NCHAR], [DB_TYPE_VARCHAR, DB_TYPE_NVARCHAR], [DB_TYPE_NVARCHAR, DB_TYPE_NVARCHAR] ])], [DB_TYPE_NCLOB, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_LONG_NVARCHAR], [DB_TYPE_NVARCHAR, DB_TYPE_LONG_NVARCHAR], [DB_TYPE_LONG, DB_TYPE_LONG_NVARCHAR], [DB_TYPE_LONG_NVARCHAR, DB_TYPE_LONG_NVARCHAR] ])], [DB_TYPE_NUMBER, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] ])], [DB_TYPE_NVARCHAR, new Map([ [DB_TYPE_CHAR, DB_TYPE_NCHAR], [DB_TYPE_NCHAR, DB_TYPE_NCHAR], [DB_TYPE_VARCHAR, DB_TYPE_NVARCHAR] ])], [DB_TYPE_RAW, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR] ])], [DB_TYPE_ROWID, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_ROWID] ])], [DB_TYPE_TIMESTAMP, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR], [DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_LTZ] ])], [DB_TYPE_TIMESTAMP_LTZ, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR], [DB_TYPE_TIMESTAMP_TZ, DB_TYPE_TIMESTAMP_TZ] ])], [DB_TYPE_TIMESTAMP_TZ, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR], [DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_LTZ] ])], [DB_TYPE_UROWID, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_ROWID] ])], [DB_TYPE_VECTOR, new Map([ [DB_TYPE_VARCHAR, DB_TYPE_LONG], [DB_TYPE_LONG, DB_TYPE_LONG], [DB_TYPE_CLOB, DB_TYPE_CLOB] ])], ]); // default fetch type map const DB_TYPE_FETCH_TYPE_MAP = new Map([ [DB_TYPE_BFILE, DB_TYPE_BFILE], [DB_TYPE_BINARY_DOUBLE, DB_TYPE_BINARY_DOUBLE], [DB_TYPE_BINARY_FLOAT, DB_TYPE_BINARY_FLOAT], [DB_TYPE_BINARY_INTEGER, DB_TYPE_BINARY_INTEGER], [DB_TYPE_BLOB, DB_TYPE_BLOB], [DB_TYPE_BOOLEAN, DB_TYPE_BOOLEAN], [DB_TYPE_CHAR, DB_TYPE_CHAR], [DB_TYPE_CLOB, DB_TYPE_CLOB], [DB_TYPE_CURSOR, DB_TYPE_CURSOR], [DB_TYPE_DATE, DB_TYPE_DATE], [DB_TYPE_INTERVAL_DS, DB_TYPE_INTERVAL_DS], [DB_TYPE_INTERVAL_YM, DB_TYPE_INTERVAL_YM], [DB_TYPE_JSON, DB_TYPE_JSON], [DB_TYPE_LONG, DB_TYPE_LONG], [DB_TYPE_LONG_NVARCHAR, DB_TYPE_LONG_NVARCHAR], [DB_TYPE_LONG_RAW, DB_TYPE_LONG_RAW], [DB_TYPE_NCHAR, DB_TYPE_NCHAR], [DB_TYPE_NCLOB, DB_TYPE_NCLOB], [DB_TYPE_NUMBER, DB_TYPE_NUMBER], [DB_TYPE_NVARCHAR, DB_TYPE_NVARCHAR], [DB_TYPE_OBJECT, DB_TYPE_OBJECT], [DB_TYPE_RAW, DB_TYPE_RAW], [DB_TYPE_ROWID, DB_TYPE_ROWID], [DB_TYPE_TIMESTAMP, DB_TYPE_TIMESTAMP], [DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_TZ], [DB_TYPE_TIMESTAMP_TZ, DB_TYPE_TIMESTAMP_TZ], [DB_TYPE_UROWID, DB_TYPE_UROWID], [DB_TYPE_VARCHAR, DB_TYPE_VARCHAR], [DB_TYPE_XMLTYPE, DB_TYPE_XMLTYPE], [DB_TYPE_VECTOR, DB_TYPE_VECTOR] ]); // 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); // It abstracts the autogenerated SODA Document key. class JsonId extends Uint8Array { toJSON() { return (Buffer.from(this.buffer).toString('hex')); } } // Represents the SparseVector. // indices must be an regular Array // values can be regular or typedArray. class SparseVector { constructor(input) { this._indices = new Uint32Array(0); this._values = new Float64Array(0); this._numDimensions = 0; if (!input) { return; } if (typeof input === 'object' && "numDimensions" in input && "indices" in input && "values" in input ) { // Object has valid properties for Sparse. this._fromObject(input); } else if (typeof input === 'string') { // initialize from string. this._fromString(input); } else if (this._validDenseArray(input)) { // dense array this._fromDense(input); } else { errors.throwErr(errors.ERR_VECTOR_SPARSE_INVALID_INPUT); } } _validDenseArray(value) { return (value instanceof Float32Array || value instanceof Float64Array || value instanceof Int8Array || (Object.getPrototypeOf(value) === Uint8Array.prototype) || Array.isArray(value)); } // Check if indexArray and valuesArray have the same length static _validateLengths(indices, values) { if (indices.length !== values.length) { if (!(values instanceof Uint8Array)) { // Skip for binary vector format errors.throwErr(errors.ERR_VECTOR_SPARSE_INDICES_VALUES_NOT_EQUAL); } } } _updateProperties(dims, indices, values) { if (!this._validDenseArray(values)) { errors.throwErr(errors.ERR_VECTOR_SPARSE_VALUES_IS_NOT_ARRAY); } if (!(indices instanceof Uint32Array) && !Array.isArray(indices)) { errors.throwErr(errors.ERR_VECTOR_SPARSE_INDICES_IS_NOT_ARRAY); } SparseVector._validateLengths(indices, values); if (!(typeof dims === 'number' && Number.isInteger(dims) && dims > 0)) { errors.throwErr(errors.ERR_VECTOR_SPARSE_DIMS_IS_NOT_INTEGER); } this._numDimensions = dims; this._indices = indices; this._values = values; this._convertToTypedArrays(); } _fromObject(input) { this._updateProperties(input.numDimensions, input.indices, input.values); } _convertToTypedArrays() { // convert to typed arrays. if (!(this._indices instanceof Uint32Array)) { // validate elements are valid uint32 type. const indices = new Uint32Array(this._indices.map((x, index) => { if (typeof x !== 'number' || !Number.isInteger(x)) { errors.throwErr(errors.ERR_VECTOR_SPARSE_INDICES_ELEM_IS_NOT_VALID, index); } if (x < 0 || x > MAX_UINT32 || x > (this.numDimensions - 1)) { errors.throwErr(errors.ERR_VECTOR_SPARSE_INDICES_ELEM_IS_NOT_VALID, index); } return x; })); this._indices = indices; } this._values = Array.isArray(this._values) ? new Float64Array(this._values) : this._values; } // Initialize the state using the dense array. // The length of input is assumed to be same as // dimensions of sparse Vector column. // values will be an array even if input is a typedArray. _fromDense(input) { this._indices = []; this._values = []; for (let i = 0; i < input.length; i++) { if (input[i] !== 0) { this._indices.push(i); this._values.push(input[i]); } } this._numDimensions = input.length; this._convertToTypedArrays(); } // parse a string input into a sparse vector _fromString(str) { let data; // verify it is a valid JSON try { // use simple tokenizer? data = JSON.parse(str); } catch (e) { errors.throwErr(errors.ERR_VECTOR_SPARSE_INVALID_JSON); } // Check if data is an array with exactly 3 elements if (!Array.isArray(data) || data.length !== 3) { errors.throwErr(errors.ERR_VECTOR_SPARSE_INVALID_STRING); } const [dims, indices, values] = data; this._updateProperties(dims, indices, values); } // Internal method to create an instance static create(sparseValue) { SparseVector._validateLengths(sparseValue.indices, sparseValue.values); const instance = Object.create(this.prototype); instance._numDimensions = sparseValue.numDimensions; instance._indices = sparseValue.indices; instance._values = sparseValue.values; return instance; } get indices() { return this._indices; } get values() { return this._values; } get numDimensions() { return this._numDimensions; } toJSON() { return { numDimensions: this._numDimensions, indices: this._indices, values: this._values, }; } // It always constructs a typedArray. _createEmptyArray(input, len) { if (Array.isArray(input)) { return new Float64Array(len); } else { // same typedArray of input type. return new input.constructor(len); } } // Convert sparse vector to a dense vector // It returns typed array. dense() { if (this._numDimensions === 0) { return null; } const dense = this._createEmptyArray(this._values, this._numDimensions); this._indices.forEach((index, i) => { dense[index] = this._values[i]; }); return dense; } } // Interval Year-to-Month Class class IntervalYM { constructor(obj) { if (obj) { errors.assertParamPropInt(obj, 1, "years"); errors.assertParamPropInt(obj, 1, "months"); } this.years = obj?.years || 0; this.months = obj?.months || 0; } } // Interval Day-to-Second Class class IntervalDS { constructor(obj) { if (obj) { errors.assertParamPropInt(obj, 1, "days"); errors.assertParamPropInt(obj, 1, "hours"); errors.assertParamPropInt(obj, 1, "minutes"); errors.assertParamPropInt(obj, 1, "seconds"); errors.assertParamPropInt(obj, 1, "fseconds"); } this.days = obj?.days || 0; this.hours = obj?.hours || 0; this.minutes = obj?.minutes || 0; this.seconds = obj?.seconds || 0; this.fseconds = obj?.fseconds || 0; } } module.exports = { DbType, DB_TYPE_BFILE, DB_TYPE_BINARY_DOUBLE, DB_TYPE_BINARY_FLOAT, DB_TYPE_BINARY_INTEGER, DB_TYPE_BLOB, DB_TYPE_BOOLEAN, DB_TYPE_CHAR, DB_TYPE_CLOB, DB_TYPE_CURSOR, DB_TYPE_DATE, DB_TYPE_INTERVAL_DS, DB_TYPE_INTERVAL_YM, DB_TYPE_JSON, DB_TYPE_LONG, DB_TYPE_LONG_NVARCHAR, DB_TYPE_LONG_RAW, DB_TYPE_NCHAR, DB_TYPE_NCLOB, DB_TYPE_NUMBER, DB_TYPE_NVARCHAR, DB_TYPE_OBJECT, DB_TYPE_RAW, DB_TYPE_ROWID, DB_TYPE_TIMESTAMP, DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_TZ, DB_TYPE_UROWID, DB_TYPE_VARCHAR, DB_TYPE_VECTOR, DB_TYPE_CONVERSION_MAP, DB_TYPE_FETCH_TYPE_MAP, DB_TYPE_XMLTYPE, getTypeByColumnTypeName, getTypeByNum, getTypeByOraTypeNum, JsonId, SparseVector, IntervalYM, IntervalDS };