UNPKG

oracledb

Version:

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

921 lines (881 loc) 34.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 utils = require("../utils"); const constants = require("../constants.js"); const Message = require("./base.js"); const { ThinDbObjectImpl, readXML } = require("../../dbObject.js"); const ThinLobImpl = require("../../lob.js"); const errors = require('../../../errors'); const types = require('../../../types.js'); /** * Handles data like row header, rowdata , ... recevied from an RPC Execute * * @class MessageWithData * @extends {Message} */ class MessageWithData extends Message { constructor(connection, statement = null, options = null) { super(connection); this.statement = statement; this.options = options; this.offset = 0; this.numExecs = 1; this.arrayDmlRowCounts = false; this.requiresDefine = false; this.rowIndex = statement.bufferRowCount || 0; this.dmlRowCounts = []; this.batchErrors = false; this.outVariables = []; this.inFetch = false; this.parseOnly = false; this.resultSetsToSetup = []; this.deferredErr = null; } /** * processMessage() - Process the data type message */ processMessage(buf, messageType) { if (messageType === constants.TNS_MSG_TYPE_DESCRIBE_INFO) { buf.skipBytesChunked(); const prevQueryVars = this.statement.queryVars; this.statement.numQueryVars = 0; this.statement.bufferRowCount = 0; this.statement.bufferRowIndex = 0; this.processDescribeInfo(buf, this.resultSet, prevQueryVars); this.outVariables = this.statement.queryVars; } else if (messageType === constants.TNS_MSG_TYPE_ROW_HEADER) { this.processRowHeader(buf); } else if (messageType === constants.TNS_MSG_TYPE_ROW_DATA) { this.processRowData(buf); } else if (messageType === constants.TNS_MSG_TYPE_IMPLICIT_RESULTSET) { this.processImplicitResultSet(buf); } else if (messageType === constants.TNS_MSG_TYPE_BIT_VECTOR) { this.processBitVector(buf); } else if (messageType === constants.TNS_MSG_TYPE_IO_VECTOR) { this.processIOVector(buf); } else if (messageType === constants.TNS_MSG_TYPE_FLUSH_OUT_BINDS) { this.flushOutBinds = true; this.endOfResponse = true; } else if (messageType === constants.TNS_MSG_TYPE_ERROR) { this.processErrorInfo(buf); } else { super.processMessage(buf, messageType); } } processErrorInfo(buf) { super.processErrorInfo(buf); if (this.errorInfo.cursorId !== 0) { this.statement.cursorId = this.errorInfo.cursorId; } if (!this.statement.isPlSql) { this.statement.rowCount = this.errorInfo.rowCount; } // we do not set the lastRowid if the rows affected is 0 if (this.errorInfo.rowCount > 0) { this.statement.lastRowid = utils.encodeRowID(this.errorInfo.rowID); } this.options.batchErrors = this.errorInfo.batchErrors; if (this.batchErrors && this.options.batchErrors === null) { this.options.batchErrors = []; } if (this.errorInfo.num === constants.TNS_ERR_NO_DATA_FOUND && this.statement.isQuery) { this.errorInfo.num = 0; this.errorOccurred = false; this.statement.moreRowsToFetch = false; } else if (this.retry) { this.retry = false; } else if (this.statement.isQuery && (this.errorInfo.num === constants.TNS_ERR_VAR_NOT_IN_SELECT_LIST || this.errorInfo.num === constants.TNS_ERR_INCONSISTENT_DATA_TYPES)) { this.retry = true; this.connection.statementCache.clearCursor(this.statement); } else if (this.errorInfo.num !== 0 && this.errorInfo.cursorId !== 0) { if (!errors.ERR_INTEGRITY_ERROR_CODES.includes(this.errorInfo.num)) { this.connection.statementCache.clearCursor(this.statement); this.statement.returnToCache = false; } } if (this.errorInfo.batchErrors) { this.errorOccurred = false; } } //--------------------------------------------------------------------------- // If we have fetched this column earlier, we set that // fetch type for the describe info variable received // assuming the returned column order is same as previous. //--------------------------------------------------------------------------- _adjustFetchType(pVar, cVar) { if ((cVar.fetchInfo.dbType._oraTypeNum === constants.TNS_DATA_TYPE_CLOB && pVar.fetchInfo.fetchType._oraTypeNum === constants.TNS_DATA_TYPE_LONG) || (cVar.fetchInfo.dbType._oraTypeNum === constants.TNS_DATA_TYPE_BLOB && pVar.fetchInfo.fetchType._oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW) || (cVar.fetchInfo.dbType._oraTypeNum === constants.TNS_DATA_TYPE_JSON && pVar.fetchInfo.fetchType._oraTypeNum === constants.TNS_DATA_TYPE_VARCHAR) || (cVar.fetchInfo.dbType._oraTypeNum === constants.TNS_DATA_TYPE_VECTOR && pVar.fetchInfo.fetchType._oraTypeNum === constants.TNS_DATA_TYPE_LONG)) { cVar.type = pVar.fetchInfo.fetchType; cVar.maxSize = pVar.maxSize; } } processDescribeInfo(buf, resultSet, prevQueryVars) { const statement = resultSet.statement; buf.skipUB4(); // max row size statement.numQueryVars = buf.readUB4(); if (statement.numQueryVars > 0) { buf.skipUB1(); } resultSet.metadata = []; const metadata = []; const queryVars = []; for (let i = 0; i < statement.numQueryVars; i++) { const variable = this.processColumnInfo(buf, i + 1); if (prevQueryVars && i < prevQueryVars.length) { this._adjustFetchType(prevQueryVars[i], variable); } queryVars.push(variable); metadata.push(variable.fetchInfo); } let numBytes = buf.readUB4(); if (numBytes > 0) { buf.skipBytesChunked(); // current date } buf.skipUB4(); // dcbflag buf.skipUB4(); // dcbmdbz buf.skipUB4(); // dcbmnpr buf.skipUB4(); // dcbmxpr numBytes = buf.readUB4(); if (numBytes > 0) { buf.skipBytesChunked(); } /* * The message state(resultSet) and statement state(queryVars) is modified * at end of the DescribeInfo function so that an OutOfPacketsError * won't cause partial information state to be stored. */ resultSet.metadata = metadata; statement.queryVars = queryVars; this.resultSetsToSetup.push(resultSet); } processColumnInfo(buf, columnNum) { const dataType = buf.readUInt8(); buf.skipUB1(); // flags const precision = buf.readInt8(); const scale = buf.readInt8(); const maxSize = buf.readUB4(); buf.skipUB4(); // max number of array elements buf.skipUB8(); // cont flags let oid; let numBytes = buf.readUB4(); // OID if (numBytes > 0) { oid = Buffer.from(buf.readBytesWithLength()); } buf.skipUB2(); // version buf.skipUB2(); // character set id const csfrm = buf.readUInt8(); // character set form let size = buf.readUB4(); if (dataType === constants.TNS_DATA_TYPE_RAW) { size = maxSize; } if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_12_2) { buf.skipUB4(); // oaccolid } const nullable = Boolean(buf.readUInt8()); buf.skipUB1(); // v7 length of name let name; numBytes = buf.readUB4(); if (numBytes > 0) { name = buf.readStr(constants.CSFRM_IMPLICIT); } let schema; numBytes = buf.readUB4(); if (numBytes > 0) { schema = buf.readStr(constants.CSFRM_IMPLICIT); } numBytes = buf.readUB4(); let typeName; if (numBytes > 0) { typeName = buf.readStr(constants.CSFRM_IMPLICIT); } buf.skipUB2(); // column position const udsFlags = buf.readUB4(); // uds flag // build metadata const fetchInfo = { name: name, dbType: types.getTypeByOraTypeNum(dataType, csfrm), nullable: nullable }; fetchInfo.isJson = Boolean(udsFlags & constants.TNS_UDS_FLAGS_IS_JSON); fetchInfo.isOson = Boolean(udsFlags & constants.TNS_UDS_FLAGS_IS_OSON); if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1) { numBytes = buf.readUB4(); if (numBytes > 0) { fetchInfo.domainSchema = buf.readStr(constants.CSFRM_IMPLICIT); } numBytes = buf.readUB4(); if (numBytes > 0) { fetchInfo.domainName = buf.readStr(constants.CSFRM_IMPLICIT); } } if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_3) { if (buf.readUB4() > 0) { fetchInfo.annotations = {}; buf.skipUB1(); const numAnnotations = buf.readUB4(); buf.skipUB1(); let key, value; for (let i = 0; i < numAnnotations; i++) { buf.skipUB4(); value = ""; key = buf.readStr(constants.CSFRM_IMPLICIT); numBytes = buf.readUB4(); if (numBytes > 0) { value = buf.readStr(constants.CSFRM_IMPLICIT); } fetchInfo.annotations[key] = value; buf.skipUB4(); // flags } buf.skipUB4(); // flags } } if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_4) { const dimensions = buf.readUB4(); const vectorFormat = buf.readUInt8(); const vectorFlags = buf.readUInt8(); if (fetchInfo.dbType === types.DB_TYPE_VECTOR) { if (!(vectorFlags & constants.VECTOR_META_FLAG_FLEXIBLE_DIM)) { fetchInfo.vectorDimensions = dimensions; } fetchInfo.isSparseVector = Boolean(vectorFlags & constants.VECTOR_META_FLAG_SPARSE); if (vectorFormat !== constants.VECTOR_FORMAT_FLEX) { fetchInfo.vectorFormat = vectorFormat; } } } switch (fetchInfo.dbType) { case types.DB_TYPE_VARCHAR: case types.DB_TYPE_NVARCHAR: case types.DB_TYPE_CHAR: case types.DB_TYPE_NCHAR: case types.DB_TYPE_RAW: fetchInfo.byteSize = size; break; case types.DB_TYPE_NUMBER: fetchInfo.precision = precision; break; case types.DB_TYPE_TIMESTAMP: case types.DB_TYPE_TIMESTAMP_TZ: case types.DB_TYPE_TIMESTAMP_LTZ: fetchInfo.precision = scale; break; case types.DB_TYPE_OBJECT: fetchInfo.dbTypeClass = this.connection._getDbObjectType(schema, typeName, undefined, oid); if (fetchInfo.dbTypeClass.partial) { this.connection._partialDbObjectTypes.push(fetchInfo.dbTypeClass); } if (fetchInfo.dbTypeClass.isXmlType) { fetchInfo.dbType = types.DB_TYPE_XMLTYPE; } break; default: break; } if (fetchInfo.dbType === types.DB_TYPE_NUMBER) { fetchInfo.scale = scale; } return { fetchInfo: fetchInfo, type: fetchInfo.dbType, maxSize: maxSize, columnNum: columnNum, values: new Array(this.options.fetchArraySize) }; } processRowHeader(buf) { buf.skipUB1(); // flags buf.skipUB2(); // num requests buf.skipUB4(); // iteration number buf.skipUB4(); // num iters buf.skipUB2(); // buffer length let numBytes = buf.readUB4(); if (numBytes > 0) { this.bitVector = Buffer.from(buf.readBytesWithLength()); } numBytes = buf.readUB4(); if (numBytes > 0) { buf.skipBytesChunked(); // rxhrid } } isDuplicateData(columnName) { if (!this.bitVector) { return false; } const byteNum = Math.floor(columnName / 8); const bitNum = columnName % 8; return (this.bitVector[byteNum] & (1 << bitNum)) === 0; } processRowData(buf) { let value; for (const [col, variable] of this.outVariables.entries()) { if (variable.isArray) { variable.numElementsInArray = buf.readUB4(); const values = new Array(variable.numElementsInArray).fill(null); for (let pos = 0; pos < variable.numElementsInArray; pos++) { value = this.processColumnData(buf, variable, pos); values[pos] = value; } variable.values[this.rowIndex] = values; } else if (this.statement.isReturning) { const numRows = buf.readUB4(); const values = Array(numRows).fill(null); for (let j = 0; j < numRows; j++) { values[j] = this.processColumnData(buf, variable, j); } variable.values[this.rowIndex] = values; } else if (this.isDuplicateData(col)) { if (this.rowIndex === 0 && variable.outConverter) { value = variable.lastRawValue; } else { value = variable.values[this.statement.lastRowIndex]; } variable.values[this.rowIndex] = value; } else { value = this.processColumnData(buf, variable, this.rowIndex); variable.values[this.rowIndex] = value; } } this.rowIndex++; if (this.inFetch) { this.statement.lastRowIndex = this.rowIndex - 1; this.statement.bufferRowCount++; this.bitVector = null; } } processIOVector(buf) { let numBytes; buf.skipUB1(); // flag const temp16 = buf.readUB2(); // num requests const temp32 = buf.readUB4(); // iter num const numBinds = temp32 * 256 + temp16; buf.skipUB4(); // num iters this time buf.skipUB2(); // uac buffer length numBytes = buf.readUB2(); // bit vector for fast fetch if (numBytes > 0) { buf.skipBytes(numBytes); } numBytes = buf.readUB2(); // rowid if (numBytes > 0) { buf.skipBytes(numBytes); } this.outVariables = []; for (let i = 0; i < numBinds; i++) { // bind directions const bindInfo = this.statement.bindInfoList[i]; bindInfo.bindDir = buf.readUInt8(); if (bindInfo.bindDir === constants.TNS_BIND_DIR_INPUT) { continue; } this.outVariables.push(bindInfo.bindVar); } } processColumnData(buf, variable) { const dbType = variable.type; const oraTypeNum = dbType._oraTypeNum; const csfrm = dbType._csfrm; const maxSize = variable.maxSize; let colValue = null; if (maxSize === 0 && oraTypeNum !== constants.TNS_DATA_TYPE_LONG && oraTypeNum !== constants.TNS_DATA_TYPE_LONG_RAW && oraTypeNum !== constants.TNS_DATA_TYPE_UROWID) { colValue = null; } else if ( oraTypeNum === constants.TNS_DATA_TYPE_VARCHAR || oraTypeNum === constants.TNS_DATA_TYPE_CHAR || oraTypeNum === constants.TNS_DATA_TYPE_LONG ) { if (csfrm === constants.CSFRM_NCHAR) { buf.caps.checkNCharsetId(); } colValue = buf.readStr(csfrm); } else if (oraTypeNum === constants.TNS_DATA_TYPE_RAW || oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW) { colValue = buf.readBytesWithLength(); if (colValue !== null) { colValue = Buffer.from(colValue); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_NUMBER) { colValue = buf.readOracleNumber(); if (!this.inFetch && colValue !== null) colValue = parseFloat(colValue); } else if ( oraTypeNum === constants.TNS_DATA_TYPE_DATE || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_TZ ) { const useLocalTime = (oraTypeNum === constants.TNS_DATA_TYPE_DATE || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP); colValue = buf.readOracleDate(useLocalTime); } else if (oraTypeNum === constants.TNS_DATA_TYPE_ROWID) { if (!this.inFetch) { colValue = buf.readStr(constants.CSFRM_IMPLICIT); } else { const numBytes = buf.readUInt8(); if (isNullLength(numBytes)) { colValue = null; } else { const rowid = buf.readRowID(); colValue = utils.encodeRowID(rowid); } } } else if (oraTypeNum === constants.TNS_DATA_TYPE_UROWID) { if (!this.inFetch) { colValue = buf.readStr(constants.CSFRM_IMPLICIT); } else { colValue = buf.readURowID(); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_DOUBLE) { colValue = buf.readBinaryDouble(); } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_FLOAT) { colValue = buf.readBinaryFloat(); } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_INTEGER) { colValue = buf.readOracleNumber(); if (colValue !== null) colValue = parseFloat(colValue); } else if (oraTypeNum === constants.TNS_DATA_TYPE_CURSOR) { const numBytes = buf.readUInt8(); if (isNullLength(numBytes)) { colValue = null; } else { colValue = this.createCursorFromDescribe(buf); colValue.statement.cursorId = buf.readUB2(); // If the cursor ID is 0 for the returned ref cursor then // it is an invalid cursor if (colValue.statement.cursorId === 0 && variable.dir !== constants.BIND_IN) { if (this.options.nullifyInvalidCursor) { colValue = null; } else { errors.throwErr(errors.ERR_INVALID_REF_CURSOR); } } } } else if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { colValue = buf.readBool(); } else if (oraTypeNum === constants.TNS_DATA_TYPE_INTERVAL_YM) { colValue = buf.readOracleIntervalYM(); } else if (oraTypeNum === constants.TNS_DATA_TYPE_INTERVAL_DS) { colValue = buf.readOracleIntervalDS(); } else if ( oraTypeNum === constants.TNS_DATA_TYPE_CLOB || oraTypeNum === constants.TNS_DATA_TYPE_BLOB || oraTypeNum === constants.TNS_DATA_TYPE_BFILE ) { let length = 0; let chunkSize = 0; const bvalue = buf.readUB4(); if (bvalue > 0) { // Non Null data in column colValue = new ThinLobImpl(); if (oraTypeNum !== constants.TNS_DATA_TYPE_BFILE) { length = buf.readUB8(); chunkSize = buf.readUB4(); } const locator = Buffer.from(buf.readBytesWithLength()); colValue.init(this.connection, locator, dbType, length, chunkSize); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_JSON) { colValue = buf.readOson(); } else if (oraTypeNum === constants.TNS_DATA_TYPE_VECTOR) { colValue = buf.readVector(); } else if (oraTypeNum === constants.TNS_DATA_TYPE_INT_NAMED) { const obj = buf.readDbObject(); if (obj.packedData) { const objType = (variable.fetchInfo) ? variable.fetchInfo.dbTypeClass : variable.typeClass; if (variable.type === types.DB_TYPE_XMLTYPE) { colValue = readXML(this.connection, obj.packedData); } else { colValue = new ThinDbObjectImpl(objType, obj.packedData); colValue.toid = obj.toid; colValue.oid = obj.oid; } } } else { errors.throwErr(errors.ERR_UNSUPPORTED_DATA_TYPE, dbType.num, variable.columnNum); } if (!this.inFetch) { const actualNumBytes = buf.readSB4(); if (actualNumBytes < 0 && oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { colValue = null; // For objects, maxsize validation is skipped } else if (actualNumBytes !== 0 && colValue !== null && oraTypeNum !== constants.TNS_DATA_TYPE_INT_NAMED) { this.saveDeferredErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_LONG || oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW || variable.maxSize > buf.caps.maxStringSize) { buf.skipSB4(); // null indicator buf.skipUB4(); // return code } return colValue; } processReturnParameter(buf) { let keywordNum = 0; let keyTextValue; let numParams = buf.readUB2(); // al8o4l (ignored) for (let i = 0; i < numParams; i++) { buf.skipUB4(); } let numBytes = buf.readUB2(); // al8txl (ignored) if (numBytes > 0) { buf.skipBytes(numBytes); } numParams = buf.readUB2(); // num key/value pairs for (let i = 0; i < numParams; i++) { numBytes = buf.readUB2(); // key if (numBytes > 0) { keyTextValue = buf.readStr(constants.CSFRM_IMPLICIT); } numBytes = buf.readUB2(); // value if (numBytes > 0) { buf.skipBytesChunked(); } keywordNum = buf.readUB2(); // keyword num if (keywordNum === constants.TNS_KEYWORD_NUM_CURRENT_SCHEMA) { this.connection.currentSchema = keyTextValue; } else if (keywordNum === constants.TNS_KEYWORD_NUM_EDITION) { this.connection._edition = keyTextValue; } } numBytes = buf.readUB2(); // registration if (numBytes > 0) { buf.skip(numBytes); } if (this.arrayDmlRowCounts) { const numRows = buf.readUB4(); const rowCounts = this.options.dmlRowCounts = []; for (let i = 0; i < numRows; i++) { const rowCount = buf.readUB8(); rowCounts.push(rowCount); } } } async postProcess() { if (this.deferredErr) { throw this.deferredErr; } if (this.outVariables) { for (const variable of this.outVariables) { if (variable.isArray) { if (variable.outConverter) { for (let pos = 0; pos < variable.numElementsInArray; pos++) { variable.values[0][pos] = await variable.outConverter(variable.values[0][pos]); } } } else { if (variable.outConverter) { variable.values[0] = await variable.outConverter(variable.values[0]); } } } } await this.connection._populatePartialDbObjectTypes(); for (const resultSet of this.resultSetsToSetup) { resultSet._setup(this.options, resultSet.metadata); // LOBs always require define and they change the type that is actually // returned by the server for (const variable of resultSet.statement.queryVars) { if (variable.type === types.DB_TYPE_CLOB || variable.type === types.DB_TYPE_NCLOB || variable.type === types.DB_TYPE_BLOB || variable.type === types.DB_TYPE_JSON || variable.type === types.DB_TYPE_VECTOR) { if (variable.type !== variable.fetchInfo.fetchType) { variable.type = variable.fetchInfo.fetchType; variable.maxSize = constants.TNS_MAX_LONG_LENGTH; } if (!resultSet.statement.noPrefetch) { resultSet.statement.requiresDefine = true; resultSet.statement.noPrefetch = true; } } } } } preProcess() { if (this.statement.isReturning && !this.parseOnly) { this.outVariables = []; for (const bindInfo of this.statement.bindInfoList) { if (bindInfo.isReturnBind) { this.outVariables.push(bindInfo.bindVar); } } } if (this.statement.isQuery) { this.inFetch = true; if (this.statement.queryVars) { this.outVariables = []; for (let i = 0; i < this.statement.queryVars.length; i++) { this.outVariables.push(this.statement.queryVars[i]); } } } } processBitVector(buf) { this.numColumnsSent = buf.readUB2(); let numBytes = Math.floor(this.statement.numQueryVars / 8); if (this.statement.numQueryVars % 8 > 0) { numBytes += 1; } this.bitVector = Buffer.from(buf.readBytes(numBytes)); } processBindParams(buf, params) { const bindVars = []; const nonReturningParams = []; for (const bindInfo of params) { if (!bindInfo.isReturnBind) { nonReturningParams.push(bindInfo); } bindVars.push(bindInfo.bindVar); } this.writeColumnMetadata(buf, bindVars); return nonReturningParams; } writeColumnMetadata(buf, bindVars) { for (const variable of bindVars) { let oraTypeNum = variable.type._oraTypeNum; let maxSize = variable.maxSize || variable.type._bufferSizeFactor; let lobPrefetchLength = 0; // NCHAR, NVARCHAR reports ORA-01460: unimplemented or unreasonable // conversion requested if maxSize is not multiplied by the // bufferSizeFactor if (variable.type._csfrm === constants.CSFRM_NCHAR) { maxSize = Math.min(maxSize * variable.type._bufferSizeFactor, constants.TNS_MAX_LONG_LENGTH); } if ([constants.TNS_DATA_TYPE_ROWID, constants.TNS_DATA_TYPE_UROWID].includes(oraTypeNum)) { oraTypeNum = constants.TNS_DATA_TYPE_VARCHAR; maxSize = constants.TNS_MAX_UROWID_LENGTH; } let flag = constants.TNS_BIND_USE_INDICATORS; if (variable.isArray) { flag |= constants.TNS_BIND_ARRAY; } let contFlag = 0; if (variable.type === types.DB_TYPE_BLOB || variable.type === types.DB_TYPE_CLOB || variable.type === types.DB_TYPE_NCLOB) { contFlag = constants.TNS_LOB_PREFETCH_FLAG; } else if (variable.type === types.DB_TYPE_JSON) { contFlag = constants.TNS_LOB_PREFETCH_FLAG; maxSize = lobPrefetchLength = constants.TNS_JSON_MAX_LENGTH; } else if (variable.type === types.DB_TYPE_VECTOR) { contFlag = constants.TNS_LOB_PREFETCH_FLAG; maxSize = lobPrefetchLength = constants.TNS_VECTOR_MAX_LENGTH; } buf.writeUInt8(oraTypeNum); buf.writeUInt8(flag); // precision and scale are always written as zero as the server // expects that and complains if any other value is sent! buf.writeUInt8(0); buf.writeUInt8(0); // Write the max buffer size buf.writeUB4(maxSize); if (variable.isArray) { buf.writeUB4(variable.maxArraySize); } else { buf.writeUB4(0); // max num elements } buf.writeUB4(contFlag); if (variable.objType) { const objType = variable.objType; buf.writeUB4(objType.oid.length); buf.writeBytesWithLength(objType.oid); buf.writeUB2(objType.version); } else { buf.writeUB4(0); // OID buf.writeUB2(0); // version } if (variable.type._csfrm !== 0) { buf.writeUB2(constants.TNS_CHARSET_UTF8); } else { buf.writeUB2(0); } buf.writeUInt8(variable.type._csfrm); buf.writeUB4(lobPrefetchLength); // max chars (LOB prefetch) if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_12_2) { buf.writeUB4(0); // oaccolid } } } writeBindParamsRow(buf, params, pos) { const offset = this.offset; let foundLong = false; for (const bindInfo of params) { if (bindInfo.isReturnBind) continue; const variable = bindInfo.bindVar; if (variable.isArray) { const numElements = variable.values.length; buf.writeUB4(numElements); for (let i = 0; i < numElements; i++) { this.writeBindParamsColumn(buf, variable, variable.values[i]); } } else { if ((!this.statement.isPlSql) && variable.maxSize > buf.caps.maxStringSize) { foundLong = true; } else { this.writeBindParamsColumn(buf, variable, variable.values[pos + offset]); } } } if (foundLong) { for (const bindInfo of params) { if (bindInfo.isReturnBind) continue; const variable = bindInfo.bindVar; if (variable.maxSize > buf.caps.maxStringSize) { this.writeBindParamsColumn(buf, variable, variable.values[pos + offset]); } } } } writeBindParamsColumn(buf, variable, value) { const oraTypeNum = variable.type._oraTypeNum; let tempVal; if ((value === undefined || value === null) && oraTypeNum !== constants.TNS_DATA_TYPE_CURSOR && oraTypeNum !== constants.TNS_DATA_TYPE_JSON) { if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { buf.writeUInt8(constants.TNS_ESCAPE_CHAR); buf.writeUInt8(1); } else if (oraTypeNum === constants.TNS_DATA_TYPE_INT_NAMED) { buf.writeUB4(0); // TOID buf.writeUB4(0); // OID buf.writeUB4(0); // snapshot buf.writeUB4(0); // version buf.writeUB4(0); // packed data length buf.writeUB4(constants.TNS_OBJ_TOP_LEVEL); // flags } else { buf.writeUInt8(0); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_NUMBER || oraTypeNum === constants.TNS_DATA_TYPE_BINARY_INTEGER) { if (typeof value === 'boolean') { tempVal = (value) ? "1" : "0"; } else { tempVal = value.toString(); } buf.writeOracleNumber(tempVal); } else if (oraTypeNum === constants.TNS_DATA_TYPE_VARCHAR || oraTypeNum === constants.TNS_DATA_TYPE_CHAR || oraTypeNum === constants.TNS_DATA_TYPE_LONG || oraTypeNum === constants.TNS_DATA_TYPE_RAW || oraTypeNum === constants.TNS_DATA_TYPE_LONG_RAW) { if (variable.type._csfrm === constants.CSFRM_NCHAR) { buf.caps.checkNCharsetId(); value = Buffer.from(value, constants.TNS_ENCODING_UTF16).swap16(); } else { value = Buffer.from(value); } buf.writeBytesWithLength(value); } else if ( oraTypeNum === constants.TNS_DATA_TYPE_DATE || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_TZ || oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ ) { buf.writeOracleDate(value, variable.type); } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_DOUBLE) { buf.writeUInt8(8); buf.writeBinaryDouble(value); } else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_FLOAT) { buf.writeUInt8(4); buf.writeBinaryFloat(value); } else if (oraTypeNum === constants.TNS_DATA_TYPE_CURSOR) { let cursor = value; if (!value) { cursor = this.connection._createResultSet(); } if (cursor.statement.cursorId === 0) { buf.writeUInt8(1); buf.writeUInt8(0); } else { buf.writeUB4(1); buf.writeUB4(cursor.statement.cursorId); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_BOOLEAN) { if (value) { buf.writeUInt8(2); buf.writeUInt16BE(0x0101); } else { buf.writeUInt16BE(0x0100); } } else if (oraTypeNum === constants.TNS_DATA_TYPE_INTERVAL_YM) { buf.writeOracleIntervalYM(value); } else if (oraTypeNum === constants.TNS_DATA_TYPE_INTERVAL_DS) { buf.writeOracleIntervalDS(value); } else if ( oraTypeNum === constants.TNS_DATA_TYPE_CLOB || oraTypeNum === constants.TNS_DATA_TYPE_BLOB || oraTypeNum === constants.TNS_DATA_TYPE_BFILE ) { buf.writeUB4(value._locator.length); buf.writeBytesWithLength(value._locator); } else if ([constants.TNS_DATA_TYPE_ROWID, constants.TNS_DATA_TYPE_UROWID].includes(oraTypeNum)) { buf.writeBytesWithLength(Buffer.from(value)); } else if (oraTypeNum === constants.TNS_DATA_TYPE_JSON) { buf.writeOson(value, this.connection._osonMaxFieldNameSize); } else if (oraTypeNum === constants.TNS_DATA_TYPE_VECTOR) { buf.writeVector(value); } else if (oraTypeNum === constants.TNS_DATA_TYPE_INT_NAMED) { buf.writeDbObject(value); } else { const message = `Binding data of type ${variable.type}`; errors.throwErr(errors.ERR_NOT_IMPLEMENTED, message); } } createCursorFromDescribe(buf) { const resultSet = this.connection._createResultSet(this.options); resultSet.options.moreRowsToFetch = true; resultSet.statement.isQuery = true; resultSet.statement.requiresFullExecute = true; this.processDescribeInfo(buf, resultSet); return resultSet; } processImplicitResultSet(buf) { this.options.implicitResultSet = []; const numResults = buf.readUB4(); for (let i = 0; i < numResults; i++) { const numBytes = buf.readUInt8(); buf.skipBytes(numBytes); const childResultSet = this.createCursorFromDescribe(buf); childResultSet.statement.cursorId = buf.readUB2(); this.options.implicitResultSet.push(childResultSet); } } } const isNullLength = (len) => { return len === 0 || len === constants.TNS_NULL_LENGTH_INDICATOR; }; module.exports = MessageWithData;