UNPKG

oracledb

Version:

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

1,348 lines (1,241 loc) 46.4 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 { Buffer } = require('buffer'); 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('../impl/datahandlers/buffer.js'); const {NetworkSession: nsi} = require("./sqlnet/networkSession.js"); const { Statement } = require("./statement"); const thinUtil = require('./util'); const sqlNetConstants = require('./sqlnet/constants.js'); const constants = require('./protocol/constants.js'); const process = require('process'); const types = require('../types.js'); const errors = require("../errors.js"); const messages = require('./protocol/messages'); const StatementCache = require('./statementCache.js'); const finalizationRegistry = new global.FinalizationRegistry((heldValue) => { heldValue.disconnect(); }); class TDSBuffer extends BaseBuffer { } class ThinConnectionImpl extends ConnectionImpl { /** * Terminates the connection * * @return {Promise} */ async close() { try { if (this._protocol.txnInProgress) { if (this.tpcContext) { const message = this.createTpcRollbackMessage(); await this._protocol._processMessage(message); } else { await this.rollback(); } this.tpcContext = null; } if (this._drcpEnabled) { await this._sessRelease(); this._drcpEstablishSession = true; } if (this._pool && !this._dropSess) { await this._pool.release(this); } else { if (!this._drcpEnabled) { const message = new messages.LogOffMessage(this); await this._protocol._processMessage(message); } this.nscon.disconnect(); } } catch (err) { // immediate close of open socket on failure // exception won't be thrown to user this.nscon.disconnect(sqlNetConstants.NSFIMM); } } async _sessRelease() { const message = new messages.SessionReleaseMessage(this); if (!this.isPooled()) { message.sessReleaseMode = constants.DRCP_DEAUTHENTICATE; } await this._protocol._processMessage(message); } //--------------------------------------------------------------------------- // _getElementTypeObj() // // Get 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 _getElementTypeObj(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); } } //--------------------------------------------------------------------------- // _execute() // // Calls the RPC that executes a SQL statement and returns the results. //--------------------------------------------------------------------------- async _execute(statement, numIters, binds, options, executeManyFlag) { // Throw error if executeMany is not called on a DML statement or PL/SQL if (executeManyFlag && statement.isQuery) { errors.throwErr(errors.ERR_EXECMANY_NOT_ALLOWED_ON_QUERIES); } // perform binds const numStmtBinds = statement.bindInfoList.length; const numUserBinds = binds.length; if (numStmtBinds !== numUserBinds) { if (!binds[0]?.name) { // positional bind mismatch or zero binds errors.throwErr(errors.ERR_WRONG_NUMBER_OF_BINDS, numStmtBinds, numUserBinds); } // named bind mismatch // Find the number of distinct named bind placeholders and see // if they match the bind values const numDistinctStmtBinds = statement.bindInfoDict.size; if (numDistinctStmtBinds !== numUserBinds) errors.throwErr(errors.ERR_WRONG_NUMBER_OF_BINDS, numStmtBinds, numUserBinds); } for (let i = 0; i < binds.length; i++) { await this._bind(statement, binds[i], i + 1); } if (statement.isPlSql && (options.batchErrors || options.dmlRowCounts)) { errors.throwErr(errors.ERR_EXEC_MODE_ONLY_FOR_DML); } // send database request const message = new messages.ExecuteMessage(this, statement, options); message.numExecs = numIters; message.arrayDmlRowCounts = options.dmlRowCounts; message.batchErrors = options.batchErrors; // if a PL/SQL statement requires a full execute, perform only a single // iteration in order to allow the determination of input/output binds // to be completed; after that, an execution of the remaining iterations // can be performed. if (statement.isPlSql && (statement.cursorId === 0 || statement.requiresFullExecute)) { message.numExecs = 1; message.noImplicitRelease = true; await this._protocol._processMessage(message); statement.requiresFullExecute = false; message.numExecs = numIters - 1; message.offset = 1; message.noImplicitRelease = false; } if (message.numExecs > 0) { await this._protocol._processMessage(message); statement.requiresFullExecute = false; } // if a define is required, send an additional request to the database if (statement.requiresDefine && statement.sql) { statement.requiresFullExecute = true; await this._protocol._processMessage(message); statement.requiresFullExecute = false; statement.requiresDefine = false; } // process results const result = {}; if (message.warning) { result.warning = message.warning; } if (statement.numQueryVars > 0) { result.resultSet = message.resultSet; } else { statement.bufferRowIndex = 0; const outBinds = thinUtil.getOutBinds(statement, numIters, executeManyFlag); if (outBinds) { result.outBinds = outBinds; } if (executeManyFlag) { if (!statement.isPlSql) { result.rowsAffected = statement.rowCount; delete statement.rowCount; } if (options.dmlRowCounts) { result.dmlRowCounts = options.dmlRowCounts; } if (options.batchErrors) { result.batchErrors = options.batchErrors; } } else { if (statement.isPlSql && options.implicitResultSet) { result.implicitResults = options.implicitResultSet; } if (statement.lastRowid) { result.lastRowid = statement.lastRowid; delete statement.lastRowid; } if (statement.isPlSql) { if (statement.rowCount) { result.rowsAffected = statement.rowCount; } } else { result.rowsAffected = statement.rowCount || 0; } if (statement.rowCount) { delete statement.rowCount; } } this._returnStatement(statement); } return result; } //--------------------------------------------------------------------------- // _parseTDSAttr() // // Returns the DB type and fills metadata from the TDS buffer. //--------------------------------------------------------------------------- _parseTDSAttr(buf, metaData) { let oraTypeNum, csfrm, attrType; // skip until a type code that is of interest for (;;) { attrType = buf.readUInt8(); if (attrType === constants.TNS_OBJ_TDS_TYPE_EMBED_ADT_INFO) { buf.skipBytes(1); // flags } else if (attrType !== constants.TNS_OBJ_TDS_TYPE_SUBTYPE_MARKER) { break; } } // process the type code. switch (attrType) { case constants.TNS_OBJ_TDS_TYPE_NUMBER: metaData.precision = buf.readInt8(); metaData.scale = buf.readInt8(); return types.DB_TYPE_NUMBER; case constants.TNS_OBJ_TDS_TYPE_FLOAT: metaData.precision = buf.readInt8(); return types.DB_TYPE_NUMBER; case constants.TNS_OBJ_TDS_TYPE_VARCHAR: case constants.TNS_OBJ_TDS_TYPE_CHAR: metaData.maxSize = buf.readUInt16BE(); // maximum length oraTypeNum = (attrType === constants.TNS_OBJ_TDS_TYPE_VARCHAR) ? constants.TNS_DATA_TYPE_VARCHAR : constants.TNS_DATA_TYPE_CHAR; csfrm = buf.readUInt8(); csfrm = csfrm & 0x7f; buf.skipBytes(2); // character set return types.getTypeByOraTypeNum(oraTypeNum, csfrm); case constants.TNS_OBJ_TDS_TYPE_RAW: metaData.maxSize = buf.readUInt16BE(); // maximum length return types.DB_TYPE_RAW; case constants.TNS_OBJ_TDS_TYPE_BINARY_FLOAT: return types.DB_TYPE_BINARY_FLOAT; case constants.TNS_OBJ_TDS_TYPE_BINARY_DOUBLE: return types.DB_TYPE_BINARY_DOUBLE; case constants.TNS_OBJ_TDS_TYPE_DATE: return types.DB_TYPE_DATE; case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP: buf.skipBytes(1); // precision return types.DB_TYPE_TIMESTAMP; case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP_LTZ: buf.skipBytes(1); // precision return types.DB_TYPE_TIMESTAMP_LTZ; case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP_TZ: buf.skipBytes(1); // precision return types.DB_TYPE_TIMESTAMP_TZ; case constants.TNS_OBJ_TDS_TYPE_BOOLEAN: return types.DB_TYPE_BOOLEAN; case constants.TNS_OBJ_TDS_TYPE_CLOB: return types.DB_TYPE_CLOB; case constants.TNS_OBJ_TDS_TYPE_BLOB: return types.DB_TYPE_BLOB; case constants.TNS_OBJ_TDS_TYPE_OBJ: buf.skipBytes(5); return types.DB_TYPE_OBJECT; case constants.TNS_OBJ_TDS_TYPE_START_EMBED_ADT: // loop until end type, TNS_OBJ_TDS_TYPE_END_EMBED_ADT // is received. while (this._parseTDSAttr(buf, {}) !== 0) { continue; } return types.DB_TYPE_OBJECT; case constants.TNS_OBJ_TDS_TYPE_END_EMBED_ADT: return 0; default: errors.throwErr(errors.ERR_TDS_TYPE_NOT_SUPPORTED, attrType); } } //--------------------------------------------------------------------------- // _parseTDS() // // Parses the TDS (type descriptor segment) for the type. //--------------------------------------------------------------------------- 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(); // 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 // check to see if type refers to a collection (only one attribute is // present in that case). info.isCollection = false; if (numAttrs === 1) { const pos = buf.pos; const attrType = buf.readUInt8(); if (attrType === constants.TNS_OBJ_TDS_TYPE_COLL) { info.isCollection = true; } else { buf.pos = pos; } } // Handle collections if (info.isCollection) { // 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; info.elementTypeInfo = {}; info.elementType = this._parseTDSAttr(buf, info.elementTypeInfo); if (info.elementType === types.DB_TYPE_OBJECT) { await this._getElementTypeObj(info); if (info.elementTypeClass.isXmlType) { info.elementType = types.DB_TYPE_XMLTYPE; } } } else { if (info.attributes) { // skip for XML type as it has no attributes. for (const attr of info.attributes) { this._parseTDSAttr(buf, attr); } } } } //--------------------------------------------------------------------------- // _populateRowTypeInfo() // // Get column and datatype information in case of %ROWTYPE handling. //--------------------------------------------------------------------------- async _populateRowTypeInfo(info, options, result) { const getColumnsSQL = ` SELECT column_name, data_type, data_type_owner, case when data_type in ('CHAR', 'NCHAR', 'VARCHAR2', 'NVARCHAR2', 'RAW') then data_length else 0 end, nvl(data_precision, 0), nvl(data_scale, 0) from all_tab_cols where owner = :owner and table_name = :name and hidden_column != 'YES' order by column_id`; const bindVal = [ { name: "owner", type: types.DB_TYPE_VARCHAR, maxSize: 128, dir: constants.BIND_IN, values: [result.outBinds.schema], }, { name: "name", type: types.DB_TYPE_VARCHAR, maxSize: 128, dir: constants.BIND_IN, values: [info.name.substring(0, info.name.length - 8)] } ]; const val = await this.execute( getColumnsSQL, 1, bindVal, options, false ); try { const attrRows = await val.resultSet._getAllRows(); info.attributes = []; for (const row of attrRows) { const metaData = { name: row[0], dataType: row[1], dataTypeOwner: row[2], maxSize: row[3], dataPrecision: row[4], dataScale: row[5], }; if (!metaData.dataTypeOwner) { const startPos = row[1].indexOf('('); const endPos = row[1].indexOf(')'); if (endPos > startPos) { metaData.dataType = metaData.dataType.substring(0, startPos) + metaData.dataType.substring( endPos + 1, metaData.dataType.length ); } } this._addAttr(info.attributes, metaData); } } finally { val.resultSet.close(); } } //--------------------------------------------------------------------------- // _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); } try { // 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 if (info.name.endsWith('%ROWTYPE')) { await this._populateRowTypeInfo(info, options, result); } else { info.version = result.outBinds.version; const attrRows = await result.outBinds.attrs_rc._getAllRows(); if (attrRows.length > 0) { // Its an object not a collection. info.attributes = []; for (const row of attrRows) { const metaData = { name: row[1], dataType: row[3], dataTypeOwner: row[4], packageName: row[5], oid: row[6] }; this._addAttr(info.attributes, metaData); } } await this._parseTDS(result.outBinds.tds, info); } info.partial = false; return info; } finally { result.outBinds.attrs_rc.close(); } } //--------------------------------------------------------------------------- // _addAttr() // // Populates "attributes" object present in "attrList". //--------------------------------------------------------------------------- _addAttr(attributes, attrInfo) { const attr = { name: attrInfo.name }; if (attrInfo.dataTypeOwner) { attr.type = types.DB_TYPE_OBJECT; attr.typeClass = this._getDbObjectType( attrInfo.dataTypeOwner, attrInfo.dataType, attrInfo.packageName, attrInfo.oid ); if (attr.typeClass.isXmlType) { attr.type = types.DB_TYPE_XMLTYPE; } if (attr.typeClass.partial) { this._partialDbObjectTypes.push(attr.typeClass); } } else { attr.type = types.getTypeByColumnTypeName(attrInfo.dataType); attr.maxSize = attrInfo.maxSize; attr.precision = attrInfo.dataPrecision; attr.scale = attrInfo.dataScale; } attributes.push(attr); } //--------------------------------------------------------------------------- // _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); } async breakExecution() { await this._protocol.breakMessage(); } isCompressionEnabled() { return this.nscon.compressionEnabled; } isHealthy() { try { if (this.nscon.recvInbandNotif() === 0) return true; return false; } catch { return false; } } isPooled() { return (this._pool) ? true : false; } /** * * @param {object} params Configuration of the connection * * @return {Promise} */ async connect(params) { if (!params.connectString) { errors.throwErr(errors.ERR_EMPTY_CONNECT_STRING); } thinUtil.checkCredentials(params); this.sessionID = 0; this.serialNum = 0; this.autoCommit = false; this.serverVersion = ""; this.statementCache = null; this.currentSchema = ""; this.invokeSessionCallback = true; this.statementCacheSize = params.stmtCacheSize; this._currentSchemaModified = false; this._tempLobsToClose = []; this._tempLobsTotalSize = 0; this._drcpEstablishSession = false; this._cclass = null; this._clientIdentifier = ""; this._clientIdentifierModified = false; this._action = ""; this._actionModified = false; this._dbOp = ""; this._dbOpModified = false; this._clientInfo = ""; this._clientInfoModified = false; this._module = ""; this._moduleModified = false; this._drcpEnabled = false; this.serviceName = ''; this.remoteAddress = ''; this.comboKey = null; // used in changePassword API this.tpcContext = null; this.nscon = new nsi(); finalizationRegistry.register(this, this.nscon); await this.nscon.connect(params); let serverType; if (this.isPooled()) { serverType = params._connInfo[0]; this.serviceName = params._connInfo[2]; this.purity = params._connInfo[3] | constants.PURITY_DEFAULT; this.sid = params._connInfo[4]; } else { serverType = this.nscon.getOption(sqlNetConstants.SERVERTYPE); this.serviceName = this.nscon.getOption(sqlNetConstants.SVCNAME); this.sid = this.nscon.getOption(sqlNetConstants.SID); this.purity = this.nscon.getOption(sqlNetConstants.PURITY) | constants.PURITY_DEFAULT; } if (serverType) { this._drcpEnabled = serverType.toLowerCase() === 'pooled'; } this.remoteAddress = this.nscon.getOption(sqlNetConstants.REMOTEADDR); this.connectionClass = params.connectionClass; /* * if drcp is used, use purity = NEW as the default purity for * standalone connections and purity = SELF for connections that belong * to a pool */ if (this.purity === constants.PURITY_DEFAULT && this._drcpEnabled) { if (this.isPooled()) { this.purity = constants.PURITY_SELF; } else { this.purity = constants.PURITY_NEW; } } this._protocol = new Protocol(this); // check if the protocol version supported by the database is high // enough; if not, reject the connection immediately if (this._protocol.caps.protocolVersion < constants.TNS_VERSION_MIN_ACCEPTED) { errors.throwErr(errors.ERR_SERVER_VERSION_NOT_SUPPORTED); } try { const protocolMessage = new messages.ProtocolMessage(this); const dataTypeMessage = new messages.DataTypeMessage(this); const authMessage = new messages.AuthMessage(this, params); if (this.nscon.supportsFastAuth) { const fastAuthMessage = new messages.FastAuthMessage(this); fastAuthMessage.protocolMessage = protocolMessage; fastAuthMessage.dataTypeMessage = dataTypeMessage; fastAuthMessage.authMessage = authMessage; await this._protocol._processMessage(fastAuthMessage); if (fastAuthMessage.reNegotiate) { // Fast Authentication failed. await this._protocol._processMessage(dataTypeMessage); await this._protocol._processMessage(authMessage); } } else { // When Fast Auth is explicitly disabled on servers > 23ai, // we dont rely on message type TNS_MSG_TYPE_END_OF_REQUEST too // for protocolMessage and dataTypeMessage. const endOfRequestSupport = this.nscon.endOfRequestSupport; this.nscon.endOfRequestSupport = false; await this._protocol._processMessage(protocolMessage); await this._protocol._processMessage(dataTypeMessage); this.nscon.endOfRequestSupport = endOfRequestSupport; await this._protocol._processMessage(authMessage); } if (!params.externalAuth) { // non-token Authentication await this._protocol._processMessage(authMessage); // OAUTH } } catch (err) { this.nscon.disconnect(); throw err; } this.statementCache = new StatementCache(this.statementCacheSize); // maintain a list of partially populated database object types this._partialDbObjectTypes = []; if (params.debugJDWP) { this.jdwpData = Buffer.from(params.debugJDWP); } else if (process.env.ORA_DEBUG_JDWP) { this.jdwpData = Buffer.from(process.env.ORA_DEBUG_JDWP); } this._protocol.connInProgress = false; } //--------------------------------------------------------------------------- // Return the statement to the statement cache, if applicable //--------------------------------------------------------------------------- _returnStatement(statement) { this.statementCache.returnStatement(statement); } //--------------------------------------------------------------------------- // Parses the sql statement and puts it into cache if keepInStmtCache // option is true //--------------------------------------------------------------------------- _prepare(sql, options) { const statement = this._getStatement(sql, options.keepInStmtCache); statement.bufferRowIndex = 0; statement.bufferRowCount = 0; statement.lastRowIndex = 0; statement.moreRowsToFetch = true; return statement; } //--------------------------------------------------------------------------- // Binds the values by user to the statement object //--------------------------------------------------------------------------- async _bind(stmt, variable, pos = 0) { const bindInfoDict = stmt.bindInfoDict; const bindInfoList = stmt.bindInfoList; /* * For PL/SQL blocks, if the size of a string or bytes object exceeds * 32,767 bytes it is converted to a BLOB/CLOB; and conversion * needs to be established as well to return the string in the way that * the user expects to get it */ if (stmt.isPlSql && variable.maxSize > 32767) { 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 === constants.CSFRM_NCHAR) { variable.type = types.DB_TYPE_NCLOB; } else { variable.type = types.DB_TYPE_CLOB; } const maxSize = variable.maxSize; delete variable.maxSize; variable.outConverter = async function(val) { if (val === null) { return null; } const data = await val.getData(); const len = val._length; if (data && len > maxSize) { errors.throwErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS); } return data; }; } if (variable.type === types.DB_TYPE_CLOB || variable.type === types.DB_TYPE_NCLOB || variable.type === types.DB_TYPE_BLOB) { for (const [index, val] of variable.values.entries()) { if (!(val instanceof ThinLobImpl)) { if (val && val.length > 0) { const lobImpl = new ThinLobImpl(); await lobImpl.create(this, variable.type); await lobImpl.write(1, val); variable.values[index] = lobImpl; } else { variable.values[index] = null; } } } } if (variable.name) { let normalizedName; if (variable.name.startsWith('"') && variable.name.endsWith('"')) { normalizedName = variable.name.substring(1, variable.name.length - 1); } else { normalizedName = variable.name.toUpperCase(); } if (normalizedName.startsWith(':')) { normalizedName = variable.name.substring(1); } if (!bindInfoDict.has(normalizedName)) { errors.throwErr(errors.ERR_INVALID_BIND_NAME, normalizedName); } bindInfoDict.get(normalizedName).forEach((bindInfo) => { stmt._setVariable(bindInfo, variable); }); } else { const bindInfo = bindInfoList[pos - 1]; stmt._setVariable(bindInfo, variable); } } //--------------------------------------------------------------------------- // _createResultSet() // // Creates a result set and performs any necessary initialization. //--------------------------------------------------------------------------- _createResultSet(options, statement) { const resultSet = new ThinResultSetImpl(); if (!statement) { statement = new Statement(); } resultSet._resultSetNew(this, statement, options); if (statement.queryVars.length > 0) { const metadata = thinUtil.getMetadataMany(statement.queryVars); resultSet._setup(options, metadata); } return resultSet; } //--------------------------------------------------------------------------- // getDbObjectClass() // // Returns a database object class given its name. //--------------------------------------------------------------------------- async getDbObjectClass(name) { const info = await this._populateDbObjectTypeInfo(name); await this._populatePartialDbObjectTypes(); return info; } //--------------------------------------------------------------------------- // getStatementInfo() // // Parses the SQL statement and returns information about the statement. //--------------------------------------------------------------------------- async getStatementInfo(sql) { const options = {}; const result = {}; const statement = this._prepare(sql, options); options.connection = this; try { if (!statement.isDdl) { const message = new messages.ExecuteMessage(this, statement, options); message.parseOnly = true; await this._protocol._processMessage(message); } if (statement.numQueryVars > 0) { result.metaData = thinUtil.getMetadataMany(statement.queryVars); } result.bindNames = Array.from(statement.bindInfoDict.keys()); result.statementType = statement.statementType; return result; } finally { this._returnStatement(statement); } } //--------------------------------------------------------------------------- // execute() // // Calls the RPC that executes a SQL statement and returns the results. //--------------------------------------------------------------------------- async execute(sql, numIters, binds, options, executeManyFlag) { const statement = this._prepare(sql, options); try { return await this._execute(statement, numIters, binds, options, executeManyFlag); } catch (err) { this._returnStatement(statement); throw err; } } //--------------------------------------------------------------------------- // Get the statement object from the statement cache for the SQL if it exists // else prepare a new statement object for the SQL. If a statement is already // in use a copy will be made and returned (and will not be returned to the // cache). If a statement is being executed for the first time after releasing // a DRCP session, a copy will also be made (and will not be returned to the // cache) since it is unknown at this point whether the original session or a // new session is going to be used. //--------------------------------------------------------------------------- _getStatement(sql, cacheStatement = false) { return this.statementCache.getStatement(sql, cacheStatement, this._drcpEstablishSession); } //--------------------------------------------------------------------------- // Calls the ping RPC for Oracle Database //--------------------------------------------------------------------------- async ping() { const message = new messages.PingMessage(this); await this._protocol._processMessage(message); } //--------------------------------------------------------------------------- // Calls the Rollback RPC for Oracle Database //--------------------------------------------------------------------------- async rollback() { const message = new messages.RollbackMessage(this); await this._protocol._processMessage(message); } //--------------------------------------------------------------------------- // Returns the Oracle Server version //--------------------------------------------------------------------------- getOracleServerVersion() { return this.serverVersion; } //--------------------------------------------------------------------------- // Returns the Oracle Server version string //--------------------------------------------------------------------------- getOracleServerVersionString() { return this.serverVersionString; } setCurrentSchema(schema) { this._currentSchemaModified = true; this.currentSchema = schema; } getCurrentSchema() { return this.currentSchema; } setClientId(clientId) { this._clientIdentifierModified = true; this._clientIdentifier = clientId; } setDbOp(dbOp) { this._dbOpModified = true; this._dbOp = dbOp; } setExternalName(value) { this.externalName = value; } setInternalName(value) { this.internalName = value; } setClientInfo(clientInfo) { this._clientInfoModified = true; this._clientInfo = clientInfo; } setModule(module) { this._moduleModified = true; this._module = module; /* * setting the module by itself results in an error so always force * action to be set as well (which eliminates this error) */ this._actionModified = true; } setAction(action) { this._actionModified = true; this._action = action; } async changePassword(user, password, newPassword) { const config = { user: user, newPassword: newPassword, password: password, changePassword: true }; const message = new messages.AuthMessage(this, config); await this._protocol._processMessage(message); // OAUTH } async createLob(dbType) { const lobImpl = new ThinLobImpl(); await lobImpl.create(this, dbType); return lobImpl; } // Check the state returned by the tpcCommit() call. checkTpcCommitState(state, onePhase) { if ((onePhase && state !== constants.TNS_TPC_TXN_STATE_READ_ONLY && state !== constants.TNS_TPC_TXN_STATE_COMMITTED) || (!onePhase && state !== constants.TNS_TPC_TXN_STATE_FORGOTTEN)) { errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, state); } } // Creates a two-phase commit message suitable for committing a transaction. createTpcCommitMessage(xid, onePhase) { const message = new messages.TransactionChangeStateMessage(this); message.operation = constants.TNS_TPC_TXN_COMMIT; message.state = (onePhase == 0) ? constants.TNS_TPC_TXN_STATE_COMMITTED : constants.TNS_TPC_TXN_STATE_READ_ONLY; message.xid = xid; message.context = this.tpcContext; return message; } // Creates a two-phase commit rollback message suitable for use in both // the close() method and explicitly by the user. createTpcRollbackMessage(xid = null) { const message = new messages.TransactionChangeStateMessage(this); message.operation = constants.TNS_TPC_TXN_ABORT; message.state = constants.TNS_TPC_TXN_STATE_ABORTED; message.xid = xid; message.context = this.tpcContext; return message; } //--------------------------------------------------------------------------- // tpcBegin() //--------------------------------------------------------------------------- async tpcBegin(xid, flags, timeout) { const message = new messages.TransactionSwitchMessage(this); message.operation = constants.TNS_TPC_TXN_START; message.xid = xid; message.flags = flags; message.timeout = timeout; await this._protocol._processMessage(message); this.tpcContext = message.context; } //--------------------------------------------------------------------------- // tpcCommit() //--------------------------------------------------------------------------- async tpcCommit(xid, onePhase) { const message = this.createTpcCommitMessage(xid, onePhase); await this._protocol._processMessage(message); this.checkTpcCommitState(message.state, onePhase); } //--------------------------------------------------------------------------- // tpcEnd() //--------------------------------------------------------------------------- async tpcEnd(xid, flags) { const message = new messages.TransactionSwitchMessage(this); message.operation = constants.TNS_TPC_TXN_DETACH; message.xid = xid; message.context = this.tpcContext; message.flags = flags; await this._protocol._processMessage(message); this.tpcContext = null; } //--------------------------------------------------------------------------- // tpcPrepare() //--------------------------------------------------------------------------- async tpcPrepare(xid) { const message = new messages.TransactionChangeStateMessage(this); message.operation = constants.TNS_TPC_TXN_PREPARE; message.xid = xid; message.context = this.tpcContext; await this._protocol._processMessage(message); if (message.state === constants.TNS_TPC_TXN_STATE_REQUIRES_COMMIT) { return true; } else if (message.state === constants.TNS_TPC_TXN_STATE_READ_ONLY) { return false; } errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, message.state); } //--------------------------------------------------------------------------- // tpcRollback() //--------------------------------------------------------------------------- async tpcRollback(xid) { const message = this.createTpcRollbackMessage(xid); await this._protocol._processMessage(message); if (message.state !== constants.TNS_TPC_TXN_STATE_ABORTED) { errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, message.state); } } //--------------------------------------------------------------------------- // Returns the statement cache size for the statement cache maintained by // the connection object //--------------------------------------------------------------------------- getStmtCacheSize() { return this.statementCache._maxSize; } setCallTimeout(timeout) { this._protocol.callTimeout = timeout; } getCallTimeout() { return this._protocol.callTimeout; } //--------------------------------------------------------------------------- // Returns getTag. Actual tag returned by db must be a string. //--------------------------------------------------------------------------- getTag() { return ''; } getExternalName() { return this.externalName; } //--------------------------------------------------------------------------- // Returns the Oracle Database instance name associated with the connection. //--------------------------------------------------------------------------- getInstanceName() { return this.instanceName; } getInternalName() { return this.internalName; } //--------------------------------------------------------------------------- // Returns the Oracle Database domain name associated with the connection. //--------------------------------------------------------------------------- getDbDomain() { return this.dbDomain; } //--------------------------------------------------------------------------- // Returns the Oracle Database host name associated with the connection. //--------------------------------------------------------------------------- getHostName() { return this.nscon.ntAdapter.hostName; } //--------------------------------------------------------------------------- // Returns the Oracle Database port number associated with the connection. //--------------------------------------------------------------------------- getPort() { return this.nscon.ntAdapter.port; } //--------------------------------------------------------------------------- // Returns the protocol associated with the connection. //--------------------------------------------------------------------------- getProtocol() { return (this.nscon.ntAdapter.secure) ? 'TCPS' : 'TCP'; } //--------------------------------------------------------------------------- // Returns the Oracle Database name associated with the connection. //--------------------------------------------------------------------------- getDbName() { return this.dbName; } //--------------------------------------------------------------------------- // Returns maximum number of cursors that can be opened in one session. //--------------------------------------------------------------------------- getMaxOpenCursors() { return this.maxOpenCursors; } //--------------------------------------------------------------------------- // Returns the maximum length of identifiers supported by the database to // which this connection has been established. //--------------------------------------------------------------------------- getMaxIdentifierLength() { return this.maxIdentifierLength; } //--------------------------------------------------------------------------- // Returns the Oracle Database service name associated with the connection. //--------------------------------------------------------------------------- getServiceName() { return this.serviceName; } //--------------------------------------------------------------------------- // Returns boolean based on this._protocol.txnInProgress value. //--------------------------------------------------------------------------- getTransactionInProgress() { return this._protocol.txnInProgress; } //--------------------------------------------------------------------------- // Returns the warning object. //--------------------------------------------------------------------------- getWarning() { return this.warning; } } module.exports = ThinConnectionImpl;