oracledb
Version:
A Node.js module for Oracle Database access from JavaScript and TypeScript
240 lines (223 loc) • 7.77 kB
JavaScript
// 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 constants = require("./constants.js");
const Capabilities = require("./capabilities.js");
const {WritePacket, ReadPacket} = require("./packet.js");
const errors = require("../../errors");
const utils = require("./utils.js");
const traceHandler = require('../../traceHandler.js');
/**
* Handles protocol details.
*
* @class Protocol
*/
class Protocol {
constructor(conn) {
this._breakInProgress = false;
this.txnInProgress = false;
this.connInProgress = true;
this.nsi = conn.nscon;
this.sequenceId = 1;
/**
* Compile and Runtime capabilities negotiated with Server
* @type {object}
*/
this.caps = new Capabilities(conn.nscon);
this.writeBuf = new WritePacket(conn.nscon, this.caps, this);
this.readBuf = new ReadPacket(conn.nscon, this.caps);
this.callTimeout = 0;
}
/**
* Decodes the message returned by the database. A message may consist of
* multiple packets. Not all packets may be available so if insufficient
* packets are available, the message decode function is expected to return
* the value true if more data is expected to follow.
*
* If that occurs, waiting occurs until more packets arrive.
*
* @param {object} message: the RPC dynamic structure specific to the RPC
*/
async _decodeMessage(message) {
message.preProcess();
await this.readBuf.waitForPackets(true);
while (true) { // eslint-disable-line
if (this.nsi.isBreak) {
await this.resetMessage();
delete this.readBuf.savedPackets;
await this.readBuf.waitForPackets();
}
try {
message.decode(this.readBuf);
break;
} catch (err) {
if (err instanceof utils.OutOfPacketsError) {
if (!this.nsi.isBreak) {
await this.readBuf.waitForPackets();
this.readBuf.restorePoint();
}
continue;
}
throw (err);
}
}
await message.postProcess();
}
/**
* Encodes the message to be sent to the database. A message may be encoded
* in multiple packets. In order to facilitate encoding of very large
* messages consisting of a large number of packets, the message encode
* function is expected to return the value true if more data is to follow.
*
* If that occurs, waiting occurs until the stream has drained and is ready
* to accept more data.
*
* @param {object} message: the RPC dynamic structure specific to the RPC
*/
async _encodeMessage(message) {
const adapter = this.nsi.ntAdapter;
this.writeBuf.startRequest(constants.TNS_PACKET_TYPE_DATA);
while (message.encode(this.writeBuf)) {
await adapter.pauseWrite();
}
this.writeBuf.endRequest();
}
async _recoverFromError(caughtErr, message) {
/*
* We have NJS error(protocol related) detected during packet write/read
* operation. Issue a break and reset to clear channel . We receive the
* response as ORA-1013 from the server.
*/
try {
this.breakMessage();
this._breakInProgress = false;
await this.resetMessage();
await this.readBuf.waitForPackets();
message.decode(this.readBuf);
} catch (err) { // Recovery failed
this.nsi.disconnect();
const newErr = errors.getErr(errors.ERR_CONNECTION_CLOSED);
caughtErr.message = newErr.message +
"\nError recovery failed: " + err.message +
"\nOriginal error: " + caughtErr.message;
throw caughtErr;
}
}
/**
*
* @param {object} message The RPC dynamic structure specific to the RPC
* @return {Promise}
*/
async _processMessage(message) {
let callTimer;
let callTimeoutExpired = false;
let traceInstance, traceContext;
const traceEnabled = traceHandler.isEnabled();
if (traceEnabled) {
traceContext = { additionalConfig: {} };
traceInstance = traceHandler.getTraceInstance();
}
try {
if (this.callTimeout > 0) {
callTimer = setTimeout(() => {
callTimeoutExpired = true;
this.breakMessage();
}, this.callTimeout);
}
if (traceEnabled) {
traceContext.operation = `oracledb.${message.constructor.name}`;
traceInstance.onBeginRoundTrip(traceContext);
}
await this._encodeMessage(message);
if (message.messageType !== constants.TNS_MSG_TYPE_ONEWAY_FN) {
await this._decodeMessage(message);
}
} catch (err) {
if (!this.connInProgress &&
err.code !== errors.ERR_CONNECTION_CLOSED_CODE) {
await this._recoverFromError(err, message);
}
if (traceEnabled) {
traceContext.error = err;
}
throw err;
} finally {
clearTimeout(callTimer);
if (traceEnabled) {
traceContext.callLevelConfig = message.connection._callLevelTraceData;
traceContext.connectLevelConfig = message.connection._getConnectTraceConfig();
if (message.callStatus & constants.TNS_EOCS_FLAGS_SESS_RELEASE) {
traceContext.additionalConfig.implicitRelease = true;
}
traceInstance.onEndRoundTrip(traceContext);
}
}
if (message.flushOutBinds) {
await this.flushOutBindMessage(message);
}
this.txnInProgress = Boolean(message.callStatus & constants.TNS_EOCS_FLAGS_TXN_IN_PROGRESS);
// processes the call status flags returned by the server.
if (message.callStatus & constants.TNS_EOCS_FLAGS_SESS_RELEASE) {
message.connection.statementCache.clearCursors();
}
if (message.errorOccurred) {
if (callTimeoutExpired) {
errors.throwErr(errors.ERR_CALL_TIMEOUT_EXCEEDED, this.callTimeout);
}
if (message.retry) {
message.errorOccurred = false;
return await this._processMessage(message);
}
let err = new Error(message.errorInfo.message);
err.offset = message.errorInfo.pos;
err.errorNum = message.errorInfo.num;
err = errors.transformErr(err);
if (err.code === errors.ERR_CONNECTION_CLOSED_CODE) {
this.nsi.disconnect();
}
throw err;
}
}
async flushOutBindMessage(message) {
this.writeBuf.startRequest(constants.TNS_PACKET_TYPE_DATA);
this.writeBuf.writeUInt8(constants.TNS_MSG_TYPE_FLUSH_OUT_BINDS);
this.writeBuf.endRequest();
await this._decodeMessage(message);
}
/**
* Send break packet
*/
breakMessage() {
this._breakInProgress = true;
this.nsi.sendBreak();
}
/**
* Reset the connection
*/
async resetMessage() {
await this.nsi.reset();
}
}
module.exports = Protocol;