UNPKG

oracledb

Version:

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

480 lines (439 loc) 18 kB
// Copyright (c) 2022, 2024, 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 constants = require("../constants.js"); const errors = require("../../../errors.js"); /** * * Base class for all the RPC messages to support encode/decode functions */ class Message { constructor(connection) { this.errorInfo = {}; this.connection = connection; this.messageType = constants.TNS_MSG_TYPE_FUNCTION; this.functionCode = 0; this.callStatus = 0; this.flushOutBinds = false; this.endOfResponse = false; this.endToEndSeqNum = 0; this.errorOccurred = false; this.warning = undefined; } preProcess() { } async postProcess() { } writeFunctionHeader(buf) { buf.writeUInt8(this.messageType); buf.writeUInt8(this.functionCode); buf.writeSeqNum(); if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_1) { buf.writeUB8(0); // token number } } processErrorInfo(buf) { this.callStatus = buf.readUB4(); // end of call status buf.skipUB2(); // end to end seq number buf.skipUB4(); // current row number buf.skipUB2(); // error number buf.skipUB2(); // array elem error buf.skipUB2(); // array elem error this.errorInfo.cursorId = buf.readUB2(); // cursor id const errorPos = buf.readSB2(); // error position buf.skipUB1(); // sql type (19c and earlier) buf.skipUB1(); // fatal ? buf.skipUB1(); // flags buf.skipUB1(); // user cursor options buf.skipUB1(); // UPI parameter const warnFlag = buf.readUInt8(); // warning flag if (warnFlag & constants.TNS_WARN_COMPILATION_CREATE) { this.warning = errors.getErr(errors.WRN_COMPILATION_CREATE); } this.errorInfo.rowID = buf.readRowID(); // rowid buf.skipUB4(); // OS error buf.skipUB1(); // statement error buf.skipUB1(); // call number buf.skipUB2(); // padding buf.skipUB4(); // success iters const numBytes = buf.readUB4(); // oerrdd (logical rowid) if (numBytes > 0) { buf.skipBytesChunked(); } // batch error codes const numErrors = buf.readUB2(); // batch error codes array if (numErrors > 0) { this.errorInfo.batchErrors = []; const firstByte = buf.readUInt8(); for (let i = 0; i < numErrors; i++) { if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { buf.skipUB4(); // chunk length ignored } const errorCode = buf.readUB2(); this.errorInfo.batchErrors.push(new Error(errorCode)); } if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { buf.skipBytes(1); // ignore end marker } } // batch error offset const numOffsets = buf.readUB4(); // batch error row offset array if (numOffsets > 0) { if (numOffsets > 65535) { errors.throwErr(errors.ERR_TOO_MANY_BATCH_ERRORS); } const firstByte = buf.readUInt8(); let offset; for (let i = 0; i < numOffsets; i++) { if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { buf.skipUB4(); // chunk length ignored } offset = buf.readUB4(); if (i < numErrors) { this.errorInfo.batchErrors[i].offset = offset; } } if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) { buf.skipBytes(1); // ignore end marker } } // batch error messages const errMsgArr = buf.readUB2(); // batch error messages array if (errMsgArr > 0) { buf.skipBytes(1); // ignore packed size for (let i = 0; i < errMsgArr; i++) { buf.skipUB2(); // skip chunk length this.errorInfo.batchErrors[i].message = buf.readStr(constants.CSFRM_IMPLICIT); buf.skipBytes(2); // ignore end marker } } this.errorInfo.num = buf.readUB4(); // error number (extended) this.errorInfo.rowCount = buf.readUB8(); // row number (extended) // fields added in Oracle Database 20c if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_20_1) { buf.skipUB4(); // sql type buf.skipUB4(); // server checksum } // error message if (this.errorInfo.num !== 0) { this.errorOccurred = true; if (errorPos >= 0) { this.errorInfo.pos = errorPos; } this.errorInfo.message = buf.readStr(constants.CSFRM_IMPLICIT); /* * Remove ending newline from ORA error message */ this.errorInfo.message = this.errorInfo.message.trim(); } this.endOfResponse = !this.connection.nscon.endOfRequestSupport; } processReturnParameter() { } processWarningInfo(buf) { const errNum = buf.readUB2(); // warning number const numBytes = buf.readUB2(); // length of warning message buf.skipUB2(); // flags if (errNum != 0 && numBytes > 0) { // get message string and remove the ending newline. const message = buf.readStr(constants.CSFRM_IMPLICIT).trim(); this.warning = new Error(message); this.warning.errorNum = errNum; } } decode(buf) { this.process(buf); } process(buf) { this.endOfResponse = false; this.flushOutBinds = false; do { this.savePoint(buf); const messageType = buf.readUInt8(); this.processMessage(buf, messageType); } while (!this.endOfResponse); } savePoint(buf) { buf.savePoint(); } processMessage(buf, messageType) { if (messageType === constants.TNS_MSG_TYPE_ERROR) { this.processErrorInfo(buf); } else if (messageType === constants.TNS_MSG_TYPE_WARNING) { this.processWarningInfo(buf); } else if (messageType === constants.TNS_MSG_TYPE_STATUS) { this.callStatus = buf.readUB4(); this.endToEndSeqNum = buf.readUB2(); this.endOfResponse = !this.connection.nscon.endOfRequestSupport; } else if (messageType === constants.TNS_MSG_TYPE_PARAMETER) { this.processReturnParameter(buf); } else if (messageType === constants.TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK) { this.processServerSidePiggyBack(buf); } else if (messageType === constants.TNS_MSG_TYPE_END_OF_REQUEST) { this.endOfResponse = true; } else { errors.throwErr(errors.ERR_UNEXPECTED_MESSAGE_TYPE, messageType, buf.pos, buf.packetNum); } } processServerSidePiggyBack(buf) { const opcode = buf.readUInt8(); if (opcode === constants.TNS_SERVER_PIGGYBACK_LTXID) { const num_bytes = buf.readUB4(); if (num_bytes > 0) { buf.skipBytesChunked(); } } else if ((opcode === constants.TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION) || (opcode === constants.TNS_SERVER_PIGGYBACK_TRACE_EVENT)) { // pass } else if (opcode === constants.TNS_SERVER_PIGGYBACK_OS_PID_MTS) { const numDtys = buf.readUB2(); buf.skipUB1(); buf.skipBytes(numDtys); } else if (opcode === constants.TNS_SERVER_PIGGYBACK_SYNC) { buf.skipUB2(); // skip number of DTYs buf.skipUB1(); // skip length of DTYs const num_elements = buf.readUB4(); buf.skipBytes(1); // skip length for (let i = 0; i < num_elements; i++) { let temp16 = buf.readUB2(); if (temp16 > 0) { // skip key buf.skipBytesChunked(); } temp16 = buf.readUB2(); if (temp16 > 0) { // skip value buf.skipBytesChunked(); } buf.skipUB2(); // skip flags } buf.skipUB4(); // skip overall flags } else if (opcode === constants.TNS_SERVER_PIGGYBACK_EXT_SYNC) { buf.skipUB2(); buf.skipUB1(); } else if (opcode === constants.TNS_SERVER_PIGGYBACK_AC_REPLAY_CONTEXT) { buf.skipUB2(); // skip number of DTYs buf.skipUB1(); // skip length of DTYs buf.skipUB4(); // skip flags buf.skipUB4(); // skip error code buf.skipUB1(); // skip queue const num_bytes = buf.readUB4(); // skip replay context if (num_bytes > 0) { buf.skipBytesChunked(); } } else if (opcode === constants.TNS_SERVER_PIGGYBACK_SESS_RET) { buf.skipUB2(); buf.skipUB1(); const num_elements = buf.readUB2(); if (num_elements > 0) { buf.skipUB1(); for (let i = 0; i < num_elements; ++i) { let temp16 = buf.readUB2(); if (temp16 > 0) { // skip key buf.skipBytesChunked(); } temp16 = buf.readUB2(); if (temp16 > 0) { // skip value buf.skipBytesChunked(); } buf.skipUB2(); // skip flags } } const flags = buf.readUB4(); // session flags if (flags & constants.TNS_SESSGET_SESSION_CHANGED) { if (this.connection._drcpEstablishSession) { this.connection.statementCache.clearCursors(); } } this.connection._drcpEstablishSession = false; buf.skipUB4(); // session id buf.skipUB2(); // serial number } else { errors.throwErr(errors.ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, opcode); } } writePiggybacks(buf) { if (this.connection._currentSchemaModified) { this._writeCurrentSchemaPiggyback(buf); } if (this.connection.statementCache._cursorsToClose.size > 0 && !this.connection._drcpEstablishSession) { // skip closing cursors when '_drcpEstablishSession = true' Since we don't know whether the same session // is in use. We can not send the information across until after the session information has been returned // (on the first round trip). this.writeCloseCursorsPiggyBack(buf); } if ( this.connection._actionModified || this.connection._clientIdentifierModified || this.connection._dbopModified || this.connection._clientInfoModified || this.connection._moduleModified ) { this._writeEndToEndPiggybacks(buf); } if (this.connection._tempLobsTotalSize > 0) { this.writeCloseTempLobsPiggyback(buf); } } writePiggybackHeader(buf, functionCode) { buf.writeUInt8(constants.TNS_MSG_TYPE_PIGGYBACK); buf.writeUInt8(functionCode); buf.writeSeqNum(); if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_1) { buf.writeUB8(0); // token number } } writeCloseCursorsPiggyBack(buf) { this.writePiggybackHeader(buf, constants.TNS_FUNC_CLOSE_CURSORS); buf.writeUInt8(1); this.connection.statementCache.writeCursorsToClose(buf); } writeCloseTempLobsPiggyback(buf) { const lobsToClose = this.connection._tempLobsToClose; const opCode = constants.TNS_LOB_OP_FREE_TEMP | constants.TNS_LOB_OP_ARRAY; this.writePiggybackHeader(buf, constants.TNS_FUNC_LOB_OP); buf.writeUInt8(1); // pointer buf.writeUB4(this.connection._tempLobsTotalSize); buf.writeUInt8(0); // dest LOB locator buf.writeUB4(0); buf.writeUB4(0); // source LOB locator buf.writeUB4(0); buf.writeUInt8(0); // source LOB offset buf.writeUInt8(0); // dest LOB offset buf.writeUInt8(0); // charset buf.writeUB4(opCode); buf.writeUInt8(0); // scn buf.writeUB4(0); // LOB scn buf.writeUB8(0); // LOB scnl buf.writeUB8(0); buf.writeUInt8(0); // array LOB fields buf.writeUInt8(0); buf.writeUB4(0); buf.writeUInt8(0); buf.writeUB4(0); buf.writeUInt8(0); buf.writeUB4(0); for (const val of lobsToClose) { buf.writeBytes(val); } // Reset Values this.connection._tempLobsToClose = []; this.connection._tempLobsTotalSize = 0; } _writeCurrentSchemaPiggyback(buf) { this.writePiggybackHeader(buf, constants.TNS_FUNC_SET_SCHEMA); buf.writeUInt8(1); const bytes = Buffer.byteLength(this.connection.currentSchema); buf.writeUB4(bytes); buf.writeBytesWithLength(Buffer.from(this.connection.currentSchema)); } _writeEndToEndPiggybacks(buf) { let flags = 0; // determine which flags to send if (this.connection._actionModified) { flags |= constants.TNS_END_TO_END_ACTION; } if (this.connection._clientIdentifierModified) { flags |= constants.TNS_END_TO_END_CLIENT_IDENTIFIER; } if (this.connection._clientInfoModified) { flags |= constants.TNS_END_TO_END_CLIENT_INFO; } if (this.connection._moduleModified) { flags |= constants.TNS_END_TO_END_MODULE; } if (this.connection._dbOpModified) { flags |= constants.TNS_END_TO_END_DBOP; } // write initial packet data this.writePiggybackHeader(buf, constants.TNS_FUNC_SET_END_TO_END_ATTR); buf.writeUInt8(0); // pointer (cidnam) buf.writeUInt8(0); // pointer (cidser) buf.writeUB4(flags); const clientIdentifierBytes = this.writeEndEndTraceValue(buf, this.connection._clientIdentifier, this.connection._clientIdentifierModified); const moduleBytes = this.writeEndEndTraceValue(buf, this.connection._module, this.connection._moduleModified); const actionBytes = this.writeEndEndTraceValue(buf, this.connection._action, this.connection._actionModified); // write unsupported bits buf.writeUInt8(0); // pointer (cideci) buf.writeUB4(0); // length (cideci) buf.writeUInt8(0); // cidcct buf.writeUB4(0); // cidecs const clientInfoBytes = this.writeEndEndTraceValue(buf, this.connection._clientInfo, this.connection._clientInfoModified); // write unsupported bits buf.writeUInt8(0); // pointer (cideci) buf.writeUB4(0); // length (cideci) buf.writeUInt8(0); // cidcct buf.writeUB4(0); // cidecs const dbOpBytes = this.writeEndEndTraceValue(buf, this.connection._dbOp, this.connection._dbOpModified); // write strings if (this.connection._clientIdentifierModified && this.connection._clientIdentifier) { buf.writeBytesWithLength(clientIdentifierBytes); } if (this.connection._moduleModified && this.connection._module) { buf.writeBytesWithLength(moduleBytes); } if (this.connection._actionModified && this.connection._action) { buf.writeBytesWithLength(actionBytes); } if (this.connection._clientInfoModified && this.connection._clientInfo) { buf.writeBytesWithLength(clientInfoBytes); } if (this.connection._dbOpModified && this.connection._dbOp) { buf.writeBytesWithLength(dbOpBytes); } // reset flags and values this.connection._actionModified = false; this.connection._action = ""; this.connection._clientIdentifierModified = false; this.connection._clientIdentifier = ""; this.connection._clientInfoModified = false; this.connection._clientInfo = ""; this.connection._dbOpModified = false; this.connection._dbOp = ""; this.connection._moduleModified = false; this.connection._module = ""; } writeEndEndTraceValue(buf, value, modified) { // write client identifier header info let writtenBytes; if (modified) { buf.writeUInt8(1); // pointer (client identifier) if (value) { writtenBytes = Buffer.from(value); buf.writeUB4(writtenBytes.length); } else { buf.writeUB4(0); } } else { buf.writeUInt8(0); // pointer (client identifier) buf.writeUB4(0); // length of client identifier } return writtenBytes; } // Called when an error is encountered during decode of RPC saveDeferredErr() { if (!this.deferredErr) { this.deferredErr = errors.getErr(...arguments); } } } module.exports = Message;