// Copyright (c) 2016, 2022, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // // You may not use the identified files except in compliance with the Apache // License, Version 2.0 (the "License.") // // You may obtain a copy of the License at // http://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 AqQueue = require('./aqQueue.js'); const BaseDbObject = require('./dbObject.js'); const Lob = require('./lob.js'); const ResultSet = require('./resultset.js'); const SodaDatabase = require('./sodaDatabase.js'); const EventEmitter = require('events'); const QueryStream = require('./queryStream.js'); const errors = require('./errors.js'); const nodbUtil = require('./util.js'); const impl = require('./impl'); const util = require('util'); const constants = require('./constants.js'); const settings = require('./settings.js'); // define class class Connection extends EventEmitter { constructor() { super(); this._dbObjectClasses = new Map(); this._closing = false; } //--------------------------------------------------------------------------- // _addDefaultsToExecOpts() // // Add values to the execute options from the global settings, if needed. //--------------------------------------------------------------------------- _addDefaultsToExecOpts(options) { if (options.autoCommit === undefined) options.autoCommit = settings.autoCommit; if (options.dbObjectAsPojo === undefined) options.dbObjectAsPojo = settings.dbObjectAsPojo; if (options.fetchArraySize === undefined) options.fetchArraySize = settings.fetchArraySize; if (options.fetchAsBuffer === undefined) options.fetchAsBuffer = settings.fetchAsBuffer; if (options.fetchAsString === undefined) options.fetchAsString = settings.fetchAsString; if (options.fetchInfo === undefined) options.fetchInfo = []; if (options.keepInStmtCache === undefined) options.keepInStmtCache = true; if (options.maxRows === undefined) options.maxRows = settings.maxRows; if (options.outFormat === undefined) options.outFormat = settings.outFormat; if (options.prefetchRows === undefined) options.prefetchRows = settings.prefetchRows; } //--------------------------------------------------------------------------- // _buildDbObjectClass() // // Builds and returns a database object class given the object type // information supplied by the implementation. //--------------------------------------------------------------------------- _buildDbObjectClass(objType) { const DbObject = function(initialValue) { this._impl = new impl.DbObjectImpl(); this._impl._objType = objType; if (this.isCollection) { const proxy = new Proxy(this, BaseDbObject._collectionProxyHandler); if (initialValue !== undefined) { for (let i = 0; i < initialValue.length; i++) { this.append(initialValue[i]); } } return (proxy); } else if (initialValue !== undefined) { Object.assign(this, initialValue); } }; DbObject.prototype = Object.create(BaseDbObject.prototype); DbObject.prototype.constructor = DbObject; DbObject.prototype._objType = objType; if (objType.elementTypeClass) { const cls = this._getDbObjectClass(objType.elementTypeClass); objType.elementTypeClass = cls; } if (objType.attributes) { const props = {}; for (const attr of objType.attributes) { if (attr.typeClass) { attr.typeClass = this._getDbObjectClass(attr.typeClass); } const prop = { get() { return this._getAttrValue(attr); }, set(value) { this._setAttrValue(attr, value); } }; props[attr.name] = prop; } Object.defineProperties(DbObject.prototype, props); } DbObject.toString = function() { return ('DbObjectClass [' + objType.fqn + ']'); }; return (DbObject); } //--------------------------------------------------------------------------- // _checkBindType() // // Check that the bind type matches one of the given bind types. If the bind // type has not been specified yet, the first bind type is assumed to be the // correct one. //--------------------------------------------------------------------------- _checkBindType(bindInfo, pos) { if (bindInfo.type === undefined && arguments.length > 2) { bindInfo.type = arguments[2]; } else { let matches = false; for (let i = 2; i < arguments.length; i++) { if (bindInfo.type === arguments[i]) { matches = true; break; } } if (!matches) { if (bindInfo.isArray && bindInfo.name) { errors.throwErr(errors.ERR_INCOMPATIBLE_TYPE_ARRAY_BIND, pos, bindInfo.name); } else if (bindInfo.isArray) { errors.throwErr(errors.ERR_INCOMPATIBLE_TYPE_ARRAY_INDEX_BIND, pos, bindInfo.pos); } else { errors.throwErr(errors.ERR_BIND_VALUE_AND_TYPE_MISMATCH); } } } } //--------------------------------------------------------------------------- // _getDbObjectClass() // // Returns the database object class given the object type information // supplied by the implementation. The cache is searched first to see if an // object class has already been built. //--------------------------------------------------------------------------- _getDbObjectClass(objType) { let cls = this._dbObjectClasses.get(objType); if (!cls) { cls = this._buildDbObjectClass(objType); cls._connection = this; cls._objType = objType; this._dbObjectClasses.set(objType, cls); } return (cls); } //--------------------------------------------------------------------------- // _getDbObjectClassForName() // // Returns the database object class given the name of the database object // type. The cache is searched first to see if an object class has already // been built. //--------------------------------------------------------------------------- async _getDbObjectClassForName(name) { let cls = this._dbObjectClasses.get(name); if (!cls) { const objType = await this._impl.getDbObjectClass(name); cls = this._getDbObjectClass(objType); this._dbObjectClasses.set(name, cls); } return cls; } //--------------------------------------------------------------------------- // _isBindDir() // // Returns a boolean indicating if the supplied value is a valid bind // direction. //--------------------------------------------------------------------------- _isBindDir(value) { return ( value === constants.BIND_IN || value === constants.BIND_OUT || value === constants.BIND_INOUT ); } //--------------------------------------------------------------------------- // _isBindType() // // Returns a boolean indicating if the supplied value is a valid bind // type. //--------------------------------------------------------------------------- _isBindType(value) { return ( value === constants.DB_TYPE_BINARY_DOUBLE || value === constants.DB_TYPE_BINARY_FLOAT || value === constants.DB_TYPE_BINARY_INTEGER || value === constants.DB_TYPE_BLOB || value === constants.DB_TYPE_BOOLEAN || value === constants.DB_TYPE_CHAR || value === constants.DB_TYPE_CLOB || value === constants.DB_TYPE_CURSOR || value === constants.DB_TYPE_DATE || value === constants.DB_TYPE_JSON || value === constants.DB_TYPE_LONG || value === constants.DB_TYPE_LONG_RAW || value === constants.DB_TYPE_NCHAR || value === constants.DB_TYPE_NCLOB || value === constants.DB_TYPE_NUMBER || value === constants.DB_TYPE_NVARCHAR || value === constants.DB_TYPE_RAW || value === constants.DB_TYPE_TIMESTAMP || value === constants.DB_TYPE_TIMESTAMP_LTZ || value === constants.DB_TYPE_TIMESTAMP_TZ || value === constants.DB_TYPE_VARCHAR ); } //--------------------------------------------------------------------------- // _isBindValue() // // Returns a boolean indicating if the supplied value is one that can be // bound. //--------------------------------------------------------------------------- _isBindValue(value) { return ( value === null || value === undefined || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean' || Array.isArray(value) || Buffer.isBuffer(value) || util.isDate(value) || value instanceof Lob || value instanceof ResultSet || value instanceof BaseDbObject ); } //--------------------------------------------------------------------------- // _processBindUnit() // // Processes a bind unit (object) supplied by the user and returns the value // stored in it (if one is). //--------------------------------------------------------------------------- async _processBindUnit(bindInfo, bindUnit, inExecuteMany) { let okBindUnit = false; // get and validate bind direction; if not specified, IN is assumed if (bindUnit.dir === undefined) { bindInfo.dir = constants.BIND_IN; } else { errors.assert(this._isBindDir(bindUnit.dir), errors.ERR_INVALID_BIND_DIRECTION); bindInfo.dir = bindUnit.dir; okBindUnit = true; } // get and validate bind type; it must be one of the integer constants // identifying types, a string identifying an object type or a constructor // function identifying an object type if (bindUnit.type !== undefined) { if (typeof bindUnit.type === 'string') { bindInfo.type = constants.DB_TYPE_OBJECT; bindInfo.typeClass = await this._getDbObjectClassForName(bindUnit.type); bindInfo.objType = bindInfo.typeClass._objType; } else if (bindUnit.type.prototype instanceof BaseDbObject) { bindInfo.type = constants.DB_TYPE_OBJECT; bindInfo.typeClass = bindUnit.type; bindInfo.objType = bindInfo.typeClass._objType; } else { errors.assert(this._isBindType(bindUnit.type), errors.ERR_INVALID_BIND_DATA_TYPE, 2); bindInfo.type = bindUnit.type; } okBindUnit = true; // when calling executeMany(), bind type is mandatory } else if (inExecuteMany) { if (bindInfo.name) errors.throwErr(errors.ERR_MISSING_TYPE_BY_NAME, bindInfo.name); errors.throwErr(errors.ERR_MISSING_TYPE_BY_POS, bindInfo.pos); } // get and validate the maximum size for strings/buffers; this value is // used for IN/OUT and OUT binds in execute() and at all times for // executeMany() if (bindInfo.dir !== constants.BIND_IN || inExecuteMany) { if (bindUnit.maxSize !== undefined) { errors.assertParamPropValue(Number.isInteger(bindUnit.maxSize) && bindUnit.maxSize > 0, 2, "maxSize"); bindInfo.maxSize = bindUnit.maxSize; bindInfo.checkSize = true; okBindUnit = true; } else if (inExecuteMany) { if (bindInfo.type === constants.DB_TYPE_VARCHAR || bindInfo.type === constants.DB_TYPE_RAW) { if (bindInfo.name) errors.throwErr(errors.ERR_MISSING_MAX_SIZE_BY_NAME, bindInfo.name); errors.throwErr(errors.ERR_MISSING_MAX_SIZE_BY_POS, bindInfo.pos); } } else { bindInfo.maxSize = constants.DEFAULT_MAX_SIZE_FOR_OUT_BINDS; } } // get max array size (for array binds, not possible in executeMany()) bindInfo.isArray = false; if (!inExecuteMany) { if (bindUnit.maxArraySize !== undefined) { errors.assertParamPropValue(Number.isInteger(bindUnit.maxArraySize) && bindUnit.maxArraySize > 0, 2, "maxArraySize"); bindInfo.maxArraySize = bindUnit.maxArraySize; bindInfo.isArray = true; } } // get the value, if specified (not used in executeMany()) if (!inExecuteMany && bindUnit.val !== undefined) { return bindUnit.val; } if (!okBindUnit) errors.throwErr(errors.ERR_INVALID_BIND_UNIT); } //--------------------------------------------------------------------------- // _processBindValue() // // Processes the bind value supplied by the caller. This performs all checks // on the value and normalizes it for use by the implementation class. If no // bind info has been defined yet, the value defines that. //--------------------------------------------------------------------------- async _processBindValue(bindInfo, value, iterNum, allowArray) { // null and undefined can always be bound so nothing needs to be done if (value === undefined || value === null) { bindInfo.values[iterNum] = undefined; return; } // handle binding plain JS values to database objects if (bindInfo.type === constants.DB_TYPE_OBJECT) { let obj = value; if (!(value instanceof BaseDbObject)) { obj = new bindInfo.typeClass(value); } bindInfo.values[iterNum] = obj._impl; // handle binding plain JS values to JSON } else if (bindInfo.type === constants.DB_TYPE_JSON) { bindInfo.values[iterNum] = this._processJsonValue(value); // handle binding strings } else if (typeof value === 'string') { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_VARCHAR, constants.DB_TYPE_NVARCHAR, constants.DB_TYPE_CHAR, constants.DB_TYPE_NCHAR, constants.DB_TYPE_CLOB, constants.DB_TYPE_NCLOB); bindInfo.values[iterNum] = Buffer.from(value); if (bindInfo.type !== constants.DB_TYPE_CLOB && bindInfo.type !== constants.DB_TYPE_NCLOB && (bindInfo.maxSize === undefined || value.length > bindInfo.maxSize)) { if (bindInfo.checkSize) { errors.throwErr(errors.ERR_MAX_SIZE_TOO_SMALL, bindInfo.maxSize, value.length, iterNum); } bindInfo.maxSize = value.length; } // handle binding numbers } else if (typeof value === 'number') { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_NUMBER, constants.DB_TYPE_BINARY_INTEGER, constants.DB_TYPE_BINARY_FLOAT, constants.DB_TYPE_BINARY_DOUBLE); bindInfo.values[iterNum] = value; // handle binding booleans } else if (typeof value === 'boolean') { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_BOOLEAN); bindInfo.values[iterNum] = value; // handle binding dates } else if (util.isDate(value)) { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_TIMESTAMP_LTZ, constants.DB_TYPE_TIMESTAMP_TZ, constants.DB_TYPE_TIMESTAMP, constants.DB_TYPE_DATE); bindInfo.values[iterNum] = value; // handle binding buffers } else if (Buffer.isBuffer(value)) { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_RAW, constants.DB_TYPE_BLOB); bindInfo.values[iterNum] = value; if (bindInfo.type === constants.DB_TYPE_RAW && (bindInfo.maxSize === undefined || value.length > bindInfo.maxSize)) { if (bindInfo.checkSize) { errors.throwErr(errors.ERR_MAX_SIZE_TOO_SMALL, bindInfo.maxSize, value.length, iterNum); } bindInfo.maxSize = value.length; } // handle binding result sets } else if (value instanceof ResultSet) { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_CURSOR); bindInfo.values[iterNum] = value._impl; // handle binding LOBs } else if (value instanceof Lob) { this._checkBindType(bindInfo, iterNum, value.type); bindInfo.values[iterNum] = value._impl; // handle binding database objects } else if (value instanceof BaseDbObject) { this._checkBindType(bindInfo, iterNum, constants.DB_TYPE_OBJECT); if (!bindInfo.typeClass) { bindInfo.typeClass = await this._getDbObjectClass(value._objType); bindInfo.objType = bindInfo.typeClass._objType; } bindInfo.values[iterNum] = value._impl; // handle binding arrays } else if (allowArray && Array.isArray(value)) { bindInfo.isArray = true; if (bindInfo.dir === constants.BIND_IN) { bindInfo.maxArraySize = value.length || 1; } else if (bindInfo.maxArraySize === undefined) { errors.throwErr(errors.ERR_REQUIRED_MAX_ARRAY_SIZE); } else if (value.length > bindInfo.maxArraySize) { errors.throwErr(errors.ERR_INVALID_ARRAY_SIZE); } for (let i = 0; i < value.length; i++) { await this._processBindValue(bindInfo, value[i], i, false); } // no suitable bind value found } else { if (bindInfo.type === undefined) errors.throwErr(errors.ERR_INVALID_BIND_DATA_TYPE, 2); this._checkBindType(bindInfo, iterNum); } } //--------------------------------------------------------------------------- // _processExecuteBind() // // Processes a single execute bind supplied by the caller. This performs all // checks on the bind and normalizes it for use by the implementation class. //--------------------------------------------------------------------------- async _processExecuteBind(bindInfo, bindData) { // setup defaults bindInfo.isArray = false; // if bind data is a value that can be bound directly, use it; otherwise, // scan the bind unit for bind information and its value let bindValue; if (this._isBindValue(bindData)) { bindInfo.dir = constants.BIND_IN; bindValue = bindData; } else { bindValue = await this._processBindUnit(bindInfo, bindData, false); } // for IN and IN/OUT binds, process the value if (bindInfo.dir !== constants.BIND_OUT) { await this._processBindValue(bindInfo, bindValue, 0, true); } // if only null values were found (or an OUT bind was specified), type // information may not be set, so complete bind information as a string // and set the maxSize to 1 if it has not already been set if (bindInfo.type === undefined) { bindInfo.type = constants.DB_TYPE_VARCHAR; if (bindInfo.maxSize === undefined) bindInfo.maxSize = 1; } // check valid bind type for array binds if (bindInfo.isArray && bindInfo.type !== constants.DB_TYPE_VARCHAR && bindInfo.type !== constants.DB_TYPE_NVARCHAR && bindInfo.type !== constants.DB_TYPE_CHAR && bindInfo.type !== constants.DB_TYPE_NCHAR && bindInfo.type !== constants.DB_TYPE_NUMBER && bindInfo.type !== constants.DB_TYPE_BINARY_FLOAT && bindInfo.type !== constants.DB_TYPE_BINARY_DOUBLE && bindInfo.type !== constants.DB_TYPE_DATE && bindInfo.type !== constants.DB_TYPE_TIMESTAMP && bindInfo.type !== constants.DB_TYPE_TIMESTAMP_LTZ && bindInfo.type !== constants.DB_TYPE_TIMESTAMP_TZ && bindInfo.type !== constants.DB_TYPE_RAW) { errors.throwErr(errors.ERR_INVALID_TYPE_FOR_ARRAY_BIND); } } //--------------------------------------------------------------------------- // _processExecuteBinds() // // Processes the binds supplied by the caller. This performs all checks on // the binds and normalizes them for use by the implementation class. //--------------------------------------------------------------------------- async _processExecuteBinds(binds) { const normBinds = []; if (Array.isArray(binds)) { for (let i = 0; i < binds.length; i++) { const bindInfo = normBinds[i] = {pos: i + 1, values: []}; await this._processExecuteBind(bindInfo, binds[i]); } } else { errors.assertParamValue(nodbUtil.isObject(binds), 2); const bindNames = Object.getOwnPropertyNames(binds); for (let i = 0; i < bindNames.length; i++) { const bindInfo = normBinds[i] = {name: bindNames[i], values: []}; await this._processExecuteBind(bindInfo, binds[bindNames[i]]); } } return normBinds; } //--------------------------------------------------------------------------- // _processExecuteManyBinds() // // Processes the binds supplied by the caller. This performs all checks on // the binds and normalizes them for use by the implementation class. //--------------------------------------------------------------------------- async _processExecuteManyBinds(binds, bindDefs) { const normBinds = []; let byPosition; // transform bindDefs into normalized binds, if available if (bindDefs !== undefined) { if (Array.isArray(bindDefs)) { byPosition = true; for (let i = 0; i < bindDefs.length; i++) { const bindInfo = normBinds[i] = {pos: i + 1, values: []}; await this._processBindUnit(bindInfo, bindDefs[i], true); } } else { byPosition = false; const bindNames = Object.getOwnPropertyNames(bindDefs); for (let i = 0; i < bindNames.length; i++) { const bindInfo = normBinds[i] = {name: bindNames[i], values: []}; await this._processBindUnit(bindInfo, bindDefs[bindNames[i]], true); } } // otherwise, use the first row to determine the binds to use } else { const row = binds[0]; errors.assertParamValue(nodbUtil.isObjectOrArray(row), 2); if (Array.isArray(row)) { byPosition = true; for (let i = 0; i < row.length; i++) { normBinds[i] = {pos: i + 1}; } } else { byPosition = false; const bindNames = Object.getOwnPropertyNames(row); for (let i = 0; i < bindNames.length; i++) { normBinds[i] = {name: bindNames[i]}; } } for (let i = 0; i < normBinds.length; i++) { normBinds[i].dir = constants.BIND_IN; normBinds[i].isArray = false; normBinds[i].values = []; } } // process each of the rows for (let i = 0; i < binds.length; i++) { const row = binds[i]; errors.assert((byPosition && Array.isArray(row)) || (!byPosition && nodbUtil.isObject(row)), errors.ERR_MIXED_BIND); for (let j = 0; j < normBinds.length; j++) { const bindInfo = normBinds[j]; const value = (byPosition) ? row[j] : row[bindInfo.name]; await this._processBindValue(bindInfo, value, i, false); } } // set bind type and size to a string of size 1 if no bind type was // specified (and all values are null) for (let i = 0; i < normBinds.length; i++) { const bindInfo = normBinds[i]; if (bindInfo.type === undefined) { bindInfo.type = constants.DB_TYPE_VARCHAR; bindInfo.maxSize = 1; } } return normBinds; } //--------------------------------------------------------------------------- // _processJsonValue() // // Returns a normalized JSON value. Scalar values are returned unchanged. // Arrays are returned as a new array with processed JSON values. Objects are // returned as new objects with keys "fields" and "values", both of which // are arrays with processes JSON values. //--------------------------------------------------------------------------- _processJsonValue(value) { // handle simple scalars if (value === undefined || value === null || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean' || Buffer.isBuffer(value) || util.isDate(value)) return value; // arrays are transformed to a new array with processed values if (Array.isArray(value)) { const outValue = new Array(value.length); for (let i = 0; i < value.length; i++) { outValue[i] = this._processJsonValue(value[i]); } return outValue; } // database objects are treated as empty objects if (value instanceof BaseDbObject) return {fields: [], values: []}; // all other objects are transformed to an object with two arrays (fields // and values) const outValue = {}; outValue.fields = Object.getOwnPropertyNames(value); outValue.values = new Array(outValue.fields.length); for (let i = 0; i < outValue.fields.length; i++) { outValue.values[i] = this._processJsonValue(value[outValue.fields[i]]); } return outValue; } //--------------------------------------------------------------------------- // _transformOutBind() // // Transform an output bind value from an implementation value to a user // facing value (for result sets and LOBs). DML returning output variables // are always an array of values. //--------------------------------------------------------------------------- _transformOutBind(val, options) { let outVal = val; if (Array.isArray(val)) { outVal = []; for (let i = 0; i < val.length; i++) outVal.push(this._transformOutBind(val[i], options)); } else if (val instanceof impl.ResultSetImpl) { outVal = new ResultSet(); outVal._setup(this, val, options); } else if (val instanceof impl.LobImpl) { outVal = new Lob(); outVal._setup(val, true); } else if (val instanceof impl.DbObjectImpl) { const cls = this._dbObjectClasses.get(val._objType); outVal = Object.create(cls.prototype); outVal._impl = val; if (options.dbObjectAsPojo) { outVal = outVal._toPojo(); } else if (outVal.isCollection) { outVal = new Proxy(outVal, BaseDbObject._collectionProxyHandler); } } return outVal; } //--------------------------------------------------------------------------- // _verifyExecOpts // // Verify that the value passed by the user for binds is acceptable. Perform // any transformations necessary. //--------------------------------------------------------------------------- _verifyExecOpts(options, inExecuteMany) { // define normalized options (value returned to caller) const outOptions = {}; // handle common options errors.assertParamValue(nodbUtil.isObject(options), 3); // autoCommit must be a boolean value if (options.autoCommit !== undefined) { errors.assertParamPropValue(typeof options.autoCommit === 'boolean', 3, "autoCommit"); outOptions.autoCommit = options.autoCommit; } // dbObjectAsPojo must be a boolean value if (options.dbObjectAsPojo !== undefined) { errors.assertParamPropValue(typeof options.dbObjectAsPojo === 'boolean', 3, "dbObjectAsPojo"); outOptions.dbObjectAsPojo = options.dbObjectAsPojo; } // keepInStmtCache must be a boolean value if (options.keepInStmtCache !== undefined) { errors.assertParamPropValue(typeof options.keepInStmtCache === 'boolean', 3, "keepInStmtCache"); outOptions.keepInStmtCache = options.keepInStmtCache; } // handle options specific to executeMany() if (inExecuteMany) { // bindDefs must be an object or array if (options.bindDefs !== undefined) { errors.assertParamPropValue(nodbUtil.isObjectOrArray(options.bindDefs), 3, "bindDefs"); outOptions.bindDefs = options.bindDefs; } // batchErrors must be a boolean value if (options.batchErrors !== undefined) { errors.assertParamPropValue(typeof options.batchErrors === 'boolean', 3, "batchErrors"); outOptions.batchErrors = options.batchErrors; } // dmlRowCounts must be a boolean value if (options.dmlRowCounts !== undefined) { errors.assertParamPropValue(typeof options.dmlRowCounts === 'boolean', 3, "dmlRowCounts"); outOptions.dmlRowCounts = options.dmlRowCounts; } // handle options specific to execute() } else { // fetchArraySize must be a positive integer if (options.fetchArraySize !== undefined) { errors.assertParamPropValue(Number.isInteger(options.fetchArraySize) && options.fetchArraySize > 0, 3, "fetchArraySize"); outOptions.fetchArraySize = options.fetchArraySize; } // fetchInfo must be an object with keys containing an object with a // "type" property; these are converted to an array of objects for ease // of processing by the implementation if (options.fetchInfo !== undefined) { errors.assertParamPropValue(nodbUtil.isObject(options.fetchInfo), 3, "fetchInfo"); const keys = Object.getOwnPropertyNames(options.fetchInfo); outOptions.fetchInfo = new Array(keys.length); for (let i = 0; i < keys.length; i++) { const info = options.fetchInfo[keys[i]]; if (info.type === undefined) errors.throwErr(errors.ERR_NO_TYPE_FOR_CONVERSION); if (info.type !== constants.DEFAULT && info.type !== constants.DB_TYPE_VARCHAR && info.type !== constants.DB_TYPE_RAW) { errors.throwErr(errors.ERR_INVALID_TYPE_FOR_CONVERSION); } outOptions.fetchInfo[i] = {name: keys[i], type: info.type}; } } // maxRows must be a positive integer (or 0) if (options.maxRows !== undefined) { errors.assertParamPropValue(Number.isInteger(options.maxRows) && options.maxRows >= 0, 3, "maxRows"); outOptions.maxRows = options.maxRows; } // outFormat must be one of the two possible constants if (options.outFormat !== undefined) { errors.assertParamPropValue( options.outFormat === constants.OUT_FORMAT_ARRAY || options.outFormat === constants.OUT_FORMAT_OBJECT, 3, "outFormat"); outOptions.outFormat = options.outFormat; } // prefetchRows must be a positive integer (or 0) if (options.prefetchRows !== undefined) { errors.assertParamPropValue(Number.isInteger(options.prefetchRows) && options.prefetchRows >= 0, 3, "prefetchRows"); outOptions.prefetchRows = options.prefetchRows; } // resultSet must be a boolean value if (options.resultSet !== undefined) { errors.assertParamPropValue(typeof options.resultSet === 'boolean', 3, "resultSet"); outOptions.resultSet = options.resultSet; } } return outOptions; } //--------------------------------------------------------------------------- // action // // Property for end-to-end tracing attribute. //--------------------------------------------------------------------------- get action() { return null; } set action(value) { errors.assertPropValue(typeof value === 'string', "action"); this._impl.setAction(value); } //--------------------------------------------------------------------------- // breakExecution() // // Breaks execution of a running statement. //--------------------------------------------------------------------------- async breakExecution() { errors.assertArgCount(arguments, 0, 0); await this._impl.breakExecution(); } //--------------------------------------------------------------------------- // callTimeout // // Property for round-trip timeouts. //--------------------------------------------------------------------------- get callTimeout() { return this._impl.getCallTimeout(); } set callTimeout(value) { errors.assertPropValue(Number.isInteger(value) && value >= 0, "callTimeout"); this._impl.setCallTimeout(value); } //--------------------------------------------------------------------------- // changePassword() // // Changes the password of the specified user. //--------------------------------------------------------------------------- async changePassword(user, password, newPassword) { errors.assertArgCount(arguments, 3, 3); errors.assertParamValue(typeof user === 'string', 1); errors.assertParamValue(typeof password === 'string', 2); errors.assertParamValue(typeof newPassword === 'string', 3); await this._impl.changePassword(user, password, newPassword); } //--------------------------------------------------------------------------- // clientId // // Property for end-to-end tracing attribute. //--------------------------------------------------------------------------- get clientId() { return null; } set clientId(value) { errors.assertPropValue(typeof value === 'string', "clientId"); this._impl.setClientId(value); } //--------------------------------------------------------------------------- // clientInfo // // Property for end-to-end tracing attribute. //--------------------------------------------------------------------------- get clientInfo() { return null; } set clientInfo(value) { errors.assertPropValue(typeof value === 'string', "clientInfo"); this._impl.setClientInfo(value); } //--------------------------------------------------------------------------- // close() // // Closes the connection and makes it unusable for further work. //--------------------------------------------------------------------------- async close(a1) { let options = {}; errors.assertArgCount(arguments, 0, 1); if (arguments.length == 1) { errors.assertParamValue(nodbUtil.isObject(a1), 1); options = a1; } // If already in the process of closing, throw an error instead of doing // a roundtrip if (this._closing) { errors.throwErr(errors.ERR_INVALID_CONNECTION); } this._closing = true; try { await this._impl.close(options); } finally { this._closing = false; } this._dbObjectClasses.clear(); this.emit('_afterConnClose'); } //--------------------------------------------------------------------------- // commit() // // Commits the current transaction. //--------------------------------------------------------------------------- async commit() { errors.assertArgCount(arguments, 0, 0); await this._impl.commit(); } //--------------------------------------------------------------------------- // createLob() // // Creates a temporary LOB and returns it to the caller. //--------------------------------------------------------------------------- async createLob(type) { errors.assertArgCount(arguments, 1, 1); errors.assertParamValue(type === constants.DB_TYPE_CLOB || type === constants.DB_TYPE_BLOB || type === constants.DB_TYPE_NCLOB, 1); const lob = new Lob(); lob._setup(await this._impl.createLob(type), false); return lob; } //--------------------------------------------------------------------------- // currentSchema // // Property for identifying the current schema to use in the database. //--------------------------------------------------------------------------- get currentSchema() { return this._impl.getCurrentSchema(); } set currentSchema(value) { errors.assertPropValue(typeof value === 'string', "currentSchema"); this._impl.setCurrentSchema(value); } //--------------------------------------------------------------------------- // dbOp // // Property for end-to-end tracing attribute. //--------------------------------------------------------------------------- get dbOp() { return null; } set dbOp(value) { errors.assertPropValue(typeof value === 'string', "dbOp"); this._impl.setDbOp(value); } //--------------------------------------------------------------------------- // ecid // // Property for end-to-end tracing attribute. //--------------------------------------------------------------------------- get ecid() { return null; } set ecid(value) { errors.assertPropValue(typeof value === 'string', "ecid"); this._impl.setECID(value); } //--------------------------------------------------------------------------- // execute() // // Executes a SQL statement and returns the results. //--------------------------------------------------------------------------- async execute(sql, a2, a3) { const numIters = 1; let binds = []; let options = {}; // process arguments errors.assertArgCount(arguments, 1, 3); errors.assertParamValue(typeof sql === 'string', 1); if (arguments.length >= 2) { binds = await this._processExecuteBinds(a2); } if (arguments.length == 3) { options = this._verifyExecOpts(a3, false); } this._addDefaultsToExecOpts(options); // perform actual execute const result = await this._impl.execute(sql, numIters, binds, options, false); // process queries; if a result set is not desired, fetch all of the rows // from the result set and then destroy the result set if (result.resultSet !== undefined) { const resultSet = new ResultSet(); resultSet._setup(this, result.resultSet, options); if (options.resultSet) { result.resultSet = resultSet; } else { result.rows = await resultSet._getAllRows(); result.metaData = resultSet._setupData.metaData; delete result.resultSet; } } // process output binds if (result.outBinds !== undefined) { for (const key in result.outBinds) { const val = this._transformOutBind(result.outBinds[key], options); result.outBinds[key] = val; } } // process implicit results; ensure all implicit results have their fetch // array size fixed, or, if a result set is not requested, that all rows // are fetched if (result.implicitResults) { for (const key in result.implicitResults) { const resultSetImpl = result.implicitResults[key]; const resultSet = new ResultSet(); resultSet._setup(this, resultSetImpl, options); if (options.resultSet) { result.implicitResults[key] = resultSet; } else { result.implicitResults[key] = await resultSet._getAllRows(); } } } return (result); } //--------------------------------------------------------------------------- // executeMany() // // Executes a SQL statement multiple times and returns the results. //--------------------------------------------------------------------------- async executeMany(sql, bindsOrNumIters, a3) { let options = {}; let binds = []; let numIters; errors.assertArgCount(arguments, 2, 3); errors.assertParamValue(typeof sql === 'string', 1); if (arguments.length == 3) { options = this._verifyExecOpts(a3, true); } this._addDefaultsToExecOpts(options); if (typeof bindsOrNumIters === 'number') { errors.assertParamValue(Number.isInteger(bindsOrNumIters) && bindsOrNumIters > 0, 2); numIters = bindsOrNumIters; if (options.bindDefs !== undefined) { binds = await this._processExecuteManyBinds([], options.bindDefs); } } else { errors.assertParamValue(Array.isArray(bindsOrNumIters) && bindsOrNumIters.length > 0, 2); numIters = bindsOrNumIters.length; binds = await this._processExecuteManyBinds(bindsOrNumIters, options.bindDefs); } const result = await this._impl.execute(sql, numIters, binds, options, true); // process output binds if (result.outBinds !== undefined) { for (let i = 0; i < result.outBinds.length; i++) { const outBind = result.outBinds[i]; for (const key in outBind) { outBind[key] = this._transformOutBind(outBind[key], options); } } } return result; } //--------------------------------------------------------------------------- // externalName // // Property for identifying the external name to use in TPC logging. //--------------------------------------------------------------------------- get externalName() { return this._impl.getExternalName(); } set externalName(value) { errors.assertPropValue(typeof value === 'string', "externalName"); this._impl.setExternalName(value); } //--------------------------------------------------------------------------- // getDbObjectClass() // // Returns a database object class given its name. The cache is searched // first, but if not found, the database is queried and the result is cached // using the type information (as well as the name for easier lookup later). //--------------------------------------------------------------------------- async getDbObjectClass(name) { errors.assertArgCount(arguments, 1, 1); errors.assertParamValue(typeof name === 'string', 1); return await this._getDbObjectClassForName(name); } //--------------------------------------------------------------------------- // getQueue() // // Returns a queue with the specified name. //--------------------------------------------------------------------------- async getQueue(name, a2) { let options = {}; errors.assertArgCount(arguments, 1, 2); errors.assertParamValue(typeof name === 'string', 1); if (arguments.length == 2) { errors.assertParamValue(nodbUtil.isObject(a2), 2); options = {...a2}; } const queue = new AqQueue(); await queue.create(this, name, options); return queue; } //--------------------------------------------------------------------------- // getSodaDatabase() // // Returns a SodaDatabase object (high-level SODA object associated with // the current connection). //--------------------------------------------------------------------------- getSodaDatabase() { errors.assertArgCount(arguments, 0, 0); const sodaDb = new SodaDatabase(); sodaDb._impl = this._impl.getSodaDatabase(); return sodaDb; } //--------------------------------------------------------------------------- // getStatementInfo() // // Returns information about the statement. //--------------------------------------------------------------------------- async getStatementInfo(sql) { errors.assertArgCount(arguments, 1, 1); errors.assertParamValue(typeof sql === 'string', 1); return (await this._impl.getStatementInfo(sql)); } //--------------------------------------------------------------------------- // internalName // // Property for identifying the internal name to use in TPC logging. //--------------------------------------------------------------------------- get internalName() { return this._impl.getInternalName(); } set internalName(value) { errors.assertPropValue(typeof value === 'string', "internalName"); this._impl.setInternalName(value); } //-------------------------------------------------------------------------- // isHealthy() // // Returns the health status of the connection. If this function returns // false, the caller should close the connection. // --------------------------------------------------------------------------- isHealthy() { return (!this._closing && this._impl.isHealthy()); } //--------------------------------------------------------------------------- // module // // Property for end-to-end tracing attribute. //--------------------------------------------------------------------------- get module() { return null; } set module(value) { errors.assertPropValue(typeof value === 'string', "module"); this._impl.setModule(value); } //--------------------------------------------------------------------------- // oracleServerVersion // // Returns an integer identifying the Oracle Server version. //--------------------------------------------------------------------------- get oracleServerVersion() { return this._impl.getOracleServerVersion(); } //--------------------------------------------------------------------------- // oracleServerVersionString // // Returns a string identifying the Oracle Server version. //--------------------------------------------------------------------------- get oracleServerVersionString() { return this._impl.getOracleServerVersionString(); } //--------------------------------------------------------------------------- // ping() // // Sends a "ping" to the database to see if it is "alive". //--------------------------------------------------------------------------- async ping() { errors.assertArgCount(arguments, 0, 0); await this._impl.ping(); } //-------------------------------------------------------------------------- // queryStream() // // Similar to execute() except that it immediately returns a QueryStream // object. // --------------------------------------------------------------------------- queryStream(sql, binds, options) { errors.assertArgCount(arguments, 1, 3); errors.assertParamValue(typeof sql === 'string', 1); if (arguments.length == 3) { errors.assertParamValue(nodbUtil.isObject(options), 3); options = {...options}; } else { options = {}; } options.resultSet = true; const stream = new QueryStream(); // calling execute() via nextTick to ensure that handlers are registered // prior to the events being emitted process.nextTick(async () => { try { const result = await this.execute(sql, binds || [], options); if (!result.resultSet) errors.throwErr(errors.ERR_NOT_A_QUERY); stream._open(result.resultSet); } catch (err) { stream.destroy(err); return; } }); return (stream); } //--------------------------------------------------------------------------- // rollback() // // Rolls back the current transaction. //--------------------------------------------------------------------------- async rollback() { errors.assertArgCount(arguments, 0, 0); await this._impl.rollback(); } //--------------------------------------------------------------------------- // shutdown() // Shuts down the database instance. //--------------------------------------------------------------------------- async shutdown(a1) { let mode = constants.SHUTDOWN_MODE_DEFAULT; errors.assertArgCount(arguments, 0, 1); if (a1 !== undefined) { errors.assertParamValue(typeof mode === 'number', 1); mode = a1; } await this._impl.shutdown(mode); } //--------------------------------------------------------------------------- // startup() // Starts up the database instance. //--------------------------------------------------------------------------- async startup(a1) { let opts = {}; errors.assertArgCount(arguments, 0, 1); if (arguments.length == 1) { errors.assertParamValue(typeof opts === 'object', 1); opts = a1; } await this._impl.startup(opts); } //--------------------------------------------------------------------------- // stmtCacheSize // // Property for statement cache size. //--------------------------------------------------------------------------- get stmtCacheSize() { return this._impl.getStmtCacheSize(); } set stmtCacheSize(value) { errors.assertPropValue(Number.isInteger(value) && value >= 0, "stmtCacheSize"); this._impl.setStmtCacheSize(value); } //--------------------------------------------------------------------------- // subscribe() // // Creates a subscription which can be used to get notifications of database // changes or of AQ messages available to dequeue. //--------------------------------------------------------------------------- async subscribe(name, options) { errors.assertArgCount(arguments, 2, 2); errors.assertParamValue(typeof name === 'string', 1); errors.assertParamValue(nodbUtil.isObject(options), 2); await this._impl.subscribe(name, options); } //--------------------------------------------------------------------------- // tag // // Property for tag to associate with the connection. //--------------------------------------------------------------------------- get tag() { return this._impl.getTag(); } set tag(value) { errors.assertPropValue(typeof value === 'string', "tag"); this._impl.setTag(value); } //--------------------------------------------------------------------------- // tpcBegin() // // Starts a two-phase-commit transaction. //-------------------------------------------------------------------------- async tpcBegin(xid, flag, timeout) { errors.assertArgCount(arguments, 1, 3); errors.assertParamValue(nodbUtil.isXid(xid), 1); if (arguments.length < 3) { timeout = 60; // seconds } else { errors.assertParamValue(typeof timeout === 'number', 3); } if (arguments.length < 2) { flag = constants.TPC_BEGIN_NEW; } else { errors.assertParamValue(typeof flag === 'number', 2); } await this._impl.tpcBegin(xid, flag, timeout); } //--------------------------------------------------------------------------- // tpcCommit() // // Commits a two-phase-commit transaction. //--------------------------------------------------------------------------- async tpcCommit(xid, onePhase) { errors.assertArgCount(arguments, 0, 2); if (arguments.length < 2) { onePhase = false; } else { errors.assertParamValue(typeof onePhase === 'boolean', 2); } if (arguments.length >= 1) { errors.assertParamValue(nodbUtil.isXid(xid), 1); } await this._impl.tpcCommit(xid, onePhase); } //--------------------------------------------------------------------------- // tpcEnd() // // Ends a two-phase-commit transaction. //--------------------------------------------------------------------------- async tpcEnd(xid, flag) { errors.assertArgCount(arguments, 0, 2); if (arguments.length < 2) { flag = constants.TPC_END_NORMAL; } else { errors.assertParamValue(typeof flag === 'number', 2); } if (arguments.length >= 1) { errors.assertParamValue(nodbUtil.isXid(xid), 1); } await this._impl.tpcEnd(xid, flag); } //--------------------------------------------------------------------------- // tpcForget() // // Causes the server to forget a heuristically completed two-phase-commit // transaction. // --------------------------------------------------------------------------- async tpcForget(xid) { errors.assertArgCount(arguments, 1, 1); errors.assertParamValue(nodbUtil.isXid(xid), 1); await this._impl.tpcForget(xid); } //--------------------------------------------------------------------------- // tpcPrepare() // // Prepares a two-phase-commit transaction for commit. //--------------------------------------------------------------------------- async tpcPrepare(xid) { errors.assertArgCount(arguments, 0, 1); if (arguments.length >= 1) { errors.assertParamValue(nodbUtil.isXid(xid), 1); } return await this._impl.tpcPrepare(xid); } //--------------------------------------------------------------------------- // tpcRecover() // // Returns a list of pending two-phase-commit transactions. //--------------------------------------------------------------------------- async tpcRecover(asString) { errors.assertArgCount(arguments, 0, 1); if (arguments.length == 1) { errors.assertParamValue(typeof asString === 'boolean', 1); } else { asString = true; } const sqlStr = ` SELECT formatid as "formatId", UTL_RAW.CAST_TO_VARCHAR2(globalid) as "globalTransactionId", UTL_RAW.CAST_TO_VARCHAR2(branchid) as "branchQualifier" FROM DBA_PENDING_TRANSACTIONS`; const sqlBuf = ` SELECT formatid as "formatId", globalid as "globalTransactionId", branchid as "branchQualifier" FROM DBA_PENDING_TRANSACTIONS`; const options = { outFormat: constants.OUT_FORMAT_OBJECT, resultSet: false }; const result = await this.execute(asString ? sqlStr : sqlBuf, {}, options); return result.rows; } //--------------------------------------------------------------------------- // tpcRollback() // // Rolls back the current changes in a two-phase-commit transaction. //--------------------------------------------------------------------------- async tpcRollback(xid) { errors.assertArgCount(arguments, 0, 1); if (arguments.length == 1) { errors.assertParamValue(nodbUtil.isXid(xid), 1); } await this._impl.tpcRollback(xid); } //--------------------------------------------------------------------------- // unsubscribe() // // Destroy a subscription which was earlier created using subscribe(). //--------------------------------------------------------------------------- async unsubscribe(name) { errors.assertArgCount(arguments, 1, 1); errors.assertParamValue(typeof name === 'string', 1); await this._impl.unsubscribe(name); } } // adjust functions to support the old callback style and to serialize calls // that cannot take place concurrently // NOTE: breakExecution() should not be serialized Connection.prototype.break = nodbUtil.callbackify(Connection.prototype.breakExecution); nodbUtil.wrap_fns(Connection.prototype, "changePassword", "close", "commit", "createLob", "execute", "executeMany", "getDbObjectClass", "getQueue", "getStatementInfo", "ping", "rollback", "shutdown", "startup", "subscribe", "tpcBegin", "tpcCommit", "tpcEnd", "tpcForget", "tpcPrepare", "tpcRecover", "tpcRollback", "unsubscribe"); // add alias for release() Connection.prototype.release = Connection.prototype.close; // export just the Connection class module.exports = Connection;