UNPKG

oracledb

Version:

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

1,166 lines (1,071 loc) 40.5 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 constants = require("./constants.js"); const errors = require("../../errors.js"); const types = require("../../types.js"); const nodbUtil = require("../../util.js"); /** * Base buffer class used for managing buffered data without unnecessary * copying. */ class BaseBuffer { //--------------------------------------------------------------------------- // constructor() // // The initializer is either an integer specifying the size of the buffer, or // an existing Buffer, which is used directly. //--------------------------------------------------------------------------- constructor(initializer) { if (typeof initializer === 'number') { this.buf = Buffer.alloc(initializer); this.size = 0; this.maxSize = initializer; } else if (initializer) { this.buf = initializer; this.size = this.maxSize = initializer.length; } this.pos = 0; } //--------------------------------------------------------------------------- // _grow() // // Called when the buffer needs to grow. The base function simply raises an // error. //--------------------------------------------------------------------------- _grow(numBytes) { errors.throwErr(errors.ERR_BUFFER_LENGTH_INSUFFICIENT, this.numBytesLeft(), numBytes); } //--------------------------------------------------------------------------- // _readBytesWithLength() // // Helper function that processes the number of bytes (if needed) and then // acquires the specified number of bytes from the buffer. The base function // simply uses the length as given. //--------------------------------------------------------------------------- _readBytesWithLength(numBytes) { return this.readBytes(numBytes); } //--------------------------------------------------------------------------- // _readInteger() // // Read an integer from the buffer of the specified maximum size and returns // it. The signed flag indicates whether the value is allowed to be signed or // not and the skip flag indicates whether the data should simply be skipped. //--------------------------------------------------------------------------- _readInteger(maxSize, signed, skip) { let isNegative = false; let size = this.readUInt8(); if (size === 0) { return 0; } else if (size & 0x80) { if (!signed) { errors.throwErr(errors.ERR_UNEXPECTED_NEGATIVE_INTEGER, this.pos, this.packetNum); } isNegative = true; size = size & 0x7f; } if (size > maxSize) { errors.throwErr(errors.ERR_INTEGER_TOO_LARGE, size, maxSize, this.pos, this.packetNum); } if (skip) { this.skipBytes(size); } else { const buf = this.readBytes(size); const value = buf.readUIntBE(0, size); return (isNegative) ? -value : value; } } //--------------------------------------------------------------------------- // numBytesLeft() // // Returns the number of bytes that are remaining in the buffer. //--------------------------------------------------------------------------- numBytesLeft() { return this.size - this.pos; } //--------------------------------------------------------------------------- // parseBinaryDouble() // // Parses a binary double from the supplied buffer and returns a Number. // It is assumed at this point that the size of the buffer is 8 bytes. A copy // is made of the buffer in order to ensure that the original buffer is not // modified. If it is and data spans multiple packets, incorrect data may be // returned! //--------------------------------------------------------------------------- parseBinaryDouble(buf) { buf = Buffer.from(buf); if (buf[0] & 0x80) { buf[0] &= 0x7f; } else { // complement the bits for a negative number buf[0] ^= 0xff; buf[1] ^= 0xff; buf[2] ^= 0xff; buf[3] ^= 0xff; buf[4] ^= 0xff; buf[5] ^= 0xff; buf[6] ^= 0xff; buf[7] ^= 0xff; } return buf.readDoubleBE(); } //--------------------------------------------------------------------------- // parseBinaryFloat() // // Parses a binary float from the supplied buffer and returns a Number. It // is assumed at this point that the size of the buffer is 4 bytes. A copy is // made of the buffer in order to ensure that the original buffer is not // modified. If it is and data spans multiple packets, incorrect data may be // returned! //--------------------------------------------------------------------------- parseBinaryFloat(buf) { buf = Buffer.from(buf); if (buf[0] & 0x80) { buf[0] &= 0x7f; } else { // complement the bits for a negative number buf[0] ^= 0xff; buf[1] ^= 0xff; buf[2] ^= 0xff; buf[3] ^= 0xff; } return buf.readFloatBE(); } //--------------------------------------------------------------------------- // parseOracleDate() // // Parses an Oracle date from the supplied buffer and returns a Date. It is // assumed at this point that the size of the buffer is either 7 bytes (date // or compressed timestamp), 11 bytes (timestamp) or 13 bytes (timestamp with // time zone). Time zone information is discarded because Node.js uses UTC // timestamps and the server returns the data in that format, too. The Date // type in Node.js doesn't support time zone information. //--------------------------------------------------------------------------- parseOracleDate(buf, useLocalTime = true) { let fseconds = 0; if (buf.length >= 11) { fseconds = Math.floor(buf.readUInt32BE(7) / (1000 * 1000)); } const year = (buf[0] - 100) * 100 + buf[1] - 100; return nodbUtil.makeDate(useLocalTime, year, buf[2], buf[3], buf[4] - 1, buf[5] - 1, buf[6] - 1, fseconds, 0); } //--------------------------------------------------------------------------- // parseOracleIntervalYM() // // Parses an Oracle interval year-to-month (YM) from the supplied buffer // and returns a corresponding IntervalYM object representing that value. // This object contains attributes for years and months. //--------------------------------------------------------------------------- parseOracleIntervalYM(buf) { const years = buf.readUInt32BE() - constants.TNS_DURATION_MID; const months = buf[4] - constants.TNS_DURATION_OFFSET; return new types.IntervalYM({ years: years, months: months }); } //--------------------------------------------------------------------------- // parseOracleIntervalDS() // // Parses an Oracle interval day-to-second (DS) from the supplied buffer // and returns a corresponding IntervalDS object representing that value. // This object contains attributes for day and time units. //--------------------------------------------------------------------------- parseOracleIntervalDS(buf) { const days = buf.readUInt32BE() - constants.TNS_DURATION_MID; const fseconds = buf.readUInt32BE(7) - constants.TNS_DURATION_MID; const hours = buf[4] - constants.TNS_DURATION_OFFSET; const minutes = buf[5] - constants.TNS_DURATION_OFFSET; const seconds = buf[6] - constants.TNS_DURATION_OFFSET; return new types.IntervalDS({ days: days, hours: hours, minutes: minutes, seconds: seconds, fseconds: fseconds }); } //--------------------------------------------------------------------------- // parseOracleNumber() // // Parses an Oracle number from the supplied buffer and returns a Number. It // is assumed at this point that the buffer only contains the encoded numeric // data. //--------------------------------------------------------------------------- parseOracleNumber(buf) { // the first byte is the exponent; positive numbers have the highest // order bit set, whereas negative numbers have the highest order bit // cleared and the bits inverted let exponent = buf[0]; const isPositive = Boolean(exponent & 0x80); if (!isPositive) { exponent = (exponent ^ 0xFF); } exponent -= 193; let decimalPointIndex = exponent * 2 + 2; // a mantissa length of 0 implies a value of 0 (if positive) or a value // of -1e126 (if negative) if (buf.length === 1) { if (isPositive) { return "0"; } return "-1e126"; } // check for the trailing 102 byte for negative numbers and, if present, // reduce the number of mantissa digits let numBytes = buf.length; if (!isPositive && buf[buf.length - 1] === 102) { numBytes -= 1; } // process the mantissa bytes which are the remaining bytes; each // mantissa byte is a base-100 digit let base100Digit; const digits = []; for (let i = 1; i < numBytes; i++) { // positive numbers have 1 added to them; negative numbers are // subtracted from the value 101 if (isPositive) { base100Digit = buf[i] - 1; } else { base100Digit = 101 - buf[i]; } // process the first digit; leading zeroes are ignored let digit = Math.floor(base100Digit / 10); if (digit === 0 && i === 1) { decimalPointIndex -= 1; } else if (digit === 10) { digits.push("1"); digits.push("0"); decimalPointIndex += 1; } else if (digit !== 0 || i > 1) { digits.push(digit.toString()); } // process the second digit; trailing zeroes are ignored digit = base100Digit % 10; if (digit !== 0 || i < numBytes - 1) { digits.push(digit.toString()); } } // create string of digits for transformation to JS value const chars = []; // if negative, include the sign if (!isPositive) { chars.push("-"); } // if the decimal point index is 0 or less, add the decimal point and // any leading zeroes that are needed if (decimalPointIndex <= 0) { chars.push("."); if (decimalPointIndex < 0) chars.push("0".repeat(-decimalPointIndex)); } // add each of the digits for (let i = 0; i < digits.length; i++) { if (i > 0 && i === decimalPointIndex) { chars.push("."); } chars.push(digits[i]); } // if the decimal point index exceeds the number of digits, add any // trailing zeroes that are needed if (decimalPointIndex > digits.length) { for (let i = digits.length; i < decimalPointIndex; i++) { chars.push("0"); } } // convert result to a Number return chars.join(""); } //--------------------------------------------------------------------------- // readBinaryDouble() // // Reads a binary double value from the buffer and returns a Number or a // String, depending on the desired type. //--------------------------------------------------------------------------- readBinaryDouble() { const buf = this.readBytesWithLength(); if (!buf) { return null; } return this.parseBinaryDouble(buf); } //--------------------------------------------------------------------------- // readBinaryFloat() // // Reads a binary float value from the buffer and returns a Number or a // String, depending on the desired type. //--------------------------------------------------------------------------- readBinaryFloat() { const buf = this.readBytesWithLength(); if (!buf) { return null; } return this.parseBinaryFloat(buf); } //--------------------------------------------------------------------------- // readBinaryInteger() // // Reads a binary integer value from the buffer and returns a Number //--------------------------------------------------------------------------- readBinaryInteger() { const buf = this.readBytesWithLength(); if (!buf) { return 0; } if (buf.length > 4) { errors.throwErr(errors.ERR_INTEGER_TOO_LARGE, buf.length, 4, this.pos, this.packetNum); } return (buf.readIntBE(0, buf.length)); } //--------------------------------------------------------------------------- // readBool() // // Reads a boolean value from the buffer and returns a Boolean. //--------------------------------------------------------------------------- readBool() { const buf = this.readBytesWithLength(); if (!buf) { return null; } return (buf[buf.length - 1] === 1); } //--------------------------------------------------------------------------- // readBytes() // // Returns a Buffer containing the specified number of bytes. If an // insufficient number of bytes are available an error is thrown. //--------------------------------------------------------------------------- readBytes(numBytes) { const numBytesLeft = this.numBytesLeft(); if (numBytes > numBytesLeft) { errors.throwErr(errors.ERR_UNEXPECTED_END_OF_DATA, numBytes, numBytesLeft); } const buf = this.buf.subarray(this.pos, this.pos + numBytes); this.pos += numBytes; return buf; } //--------------------------------------------------------------------------- // readBytesWithLength() // // Reads the length from the buffer and then returns a Buffer containing the // specified number of bytes. If the length is 0 or the special null length // indicator value, null is returned instead. //--------------------------------------------------------------------------- readBytesWithLength() { const numBytes = this.readUInt8(); if (numBytes === 0 || numBytes === constants.TNS_NULL_LENGTH_INDICATOR) return null; return this._readBytesWithLength(numBytes); } //--------------------------------------------------------------------------- // readDbObject() // // Reads a database object from the buffer and returns the implementation // object (or null, if the object is atomically null). //--------------------------------------------------------------------------- readDbObject() { const obj = {}; let numBytes = this.readUB4(); if (numBytes > 0) obj.toid = Buffer.from(this.readBytesWithLength()); numBytes = this.readUB4(); if (numBytes > 0) obj.oid = Buffer.from(this.readBytesWithLength()); numBytes = this.readUB4(); if (numBytes > 0) obj.snapshot = Buffer.from(this.readBytesWithLength()); this.skipUB2(); // version numBytes = this.readUB4(); this.skipUB2(); // flags if (numBytes > 0) obj.packedData = Buffer.from(this.readBytesWithLength()); return obj; } //--------------------------------------------------------------------------- // readInt8() // // Reads a signed 8-bit integer from the buffer. //--------------------------------------------------------------------------- readInt8() { const buf = this.readBytes(1); return buf.readInt8(); } //--------------------------------------------------------------------------- // readOracleDate() // // Reads an Oracle date from the buffer and returns a Date or a String, // depending on the desired type. //--------------------------------------------------------------------------- readOracleDate(useLocalTime) { const buf = this.readBytesWithLength(); if (!buf) { return null; } return this.parseOracleDate(buf, useLocalTime); } //--------------------------------------------------------------------------- // readOracleIntervalYM() // // Reads interval year to month value from the buffer and returns a // JavaScript object representing that value. //--------------------------------------------------------------------------- readOracleIntervalYM() { const buf = this.readBytesWithLength(); if (!buf) { return null; } return this.parseOracleIntervalYM(buf); } //--------------------------------------------------------------------------- // readOracleIntervalDS() // // Reads interval day to second value from the buffer and returns a // JavaScript object representing that value. //--------------------------------------------------------------------------- readOracleIntervalDS() { const buf = this.readBytesWithLength(); if (!buf) { return null; } return this.parseOracleIntervalDS(buf); } //--------------------------------------------------------------------------- // readOracleNumber() // // Reads an Oracle number from the buffer and returns a Number or a String, // depending on the desired type. //--------------------------------------------------------------------------- readOracleNumber() { const buf = this.readBytesWithLength(); if (!buf) { return null; } return this.parseOracleNumber(buf); } //--------------------------------------------------------------------------- // readSB2() // // Reads a signed, variable length integer of up to 2 bytes in length. //--------------------------------------------------------------------------- readSB2() { return this._readInteger(2, true, false); } //--------------------------------------------------------------------------- // readSB4() // // Reads a signed, variable length integer of up to 4 bytes in length. //--------------------------------------------------------------------------- readSB4() { return this._readInteger(4, true, false); } //--------------------------------------------------------------------------- // readSB8() // // Reads a signed, variable length integer of up to 8 bytes in length. //--------------------------------------------------------------------------- readSB8() { return this._readInteger(8, true, false); } //--------------------------------------------------------------------------- // readStr() // // Reads a string from the buffer in the specified character set form. //--------------------------------------------------------------------------- readStr(csfrm) { const buf = this.readBytesWithLength(); if (!buf) { return null; } if (csfrm === constants.CSFRM_IMPLICIT) return buf.toString(); // need a copy of the buffer since swap16() changes the buffer in place and // it is possible that the buffer may need to be rescanned (for the case // where insufficient packets are available during the initial scan) return Buffer.from(buf).swap16().toString('utf16le'); } //--------------------------------------------------------------------------- // readUB2() // // Reads an unsigned, variable length integer of up to 2 bytes in length. //--------------------------------------------------------------------------- readUB2() { return this._readInteger(2, false, false); } //--------------------------------------------------------------------------- // readUB4() // // Reads an unsigned, variable length integer of up to 4 bytes in length. //--------------------------------------------------------------------------- readUB4() { return this._readInteger(4, false, false); } //--------------------------------------------------------------------------- // readUB8() // // Reads an unsigned, variable length integer of up to 8 bytes in length. //--------------------------------------------------------------------------- readUB8() { return this._readInteger(8, false, false); } //--------------------------------------------------------------------------- // readUInt8() // // Reads an unsigned 8-bit integer from the buffer. //--------------------------------------------------------------------------- readUInt8() { const buf = this.readBytes(1); return buf[0]; } //--------------------------------------------------------------------------- // readUInt16BE() // // Reads an unsigned 16-bit integer from the buffer in big endian order. //--------------------------------------------------------------------------- readUInt16BE() { const buf = this.readBytes(2); return buf.readUInt16BE(); } //--------------------------------------------------------------------------- // readUInt16LE() // // Reads an unsigned 16-bit integer from the buffer in little endian order. //--------------------------------------------------------------------------- readUInt16LE() { const buf = this.readBytes(2); return buf.readUInt16LE(); } //--------------------------------------------------------------------------- // readUInt32BE() // // Reads an unsigned 32-bit integer from the buffer in big endian order. //--------------------------------------------------------------------------- readUInt32BE() { const buf = this.readBytes(4); return buf.readUInt32BE(); } //--------------------------------------------------------------------------- // reserveBytes() // // Reserves the specified number of bytes in the buffer. If not enough bytes // remain in the buffer, the buffer is grown. //--------------------------------------------------------------------------- reserveBytes(numBytes) { if (numBytes > this.numBytesLeft()) { this._grow(this.pos + numBytes); } const pos = this.pos; this.pos += numBytes; return pos; } //--------------------------------------------------------------------------- // skipBytes() // // Skips the specified number of bytes in the buffer. //--------------------------------------------------------------------------- skipBytes(numBytes) { if (numBytes > this.numBytesLeft()) errors.throwErr(errors.ERR_UNEXPECTED_END_OF_DATA); this.pos += numBytes; } //--------------------------------------------------------------------------- // skipSB4() // // Skips a signed, variable length integer of up to 4 bytes in length. //--------------------------------------------------------------------------- skipSB4() { return this._readInteger(4, true, true); } //--------------------------------------------------------------------------- // skipUB1() // // Skips a single byte integer in the buffer. //--------------------------------------------------------------------------- skipUB1() { this.skipBytes(1); } //--------------------------------------------------------------------------- // skipUB2() // // Skips an unsigned, variable length integer of up to 2 bytes in length. //--------------------------------------------------------------------------- skipUB2() { return this._readInteger(2, false, true); } //--------------------------------------------------------------------------- // skipUB4() // // Skips an unsigned, variable length integer of up to 4 bytes in length. //--------------------------------------------------------------------------- skipUB4() { return this._readInteger(4, false, true); } //--------------------------------------------------------------------------- // skipUB8() // // Skips an unsigned, variable length integer of up to 8 bytes in length. //--------------------------------------------------------------------------- skipUB8() { return this._readInteger(8, false, true); } //--------------------------------------------------------------------------- // writeBinaryDouble() // // Writes the number in binary double format to the buffer. //--------------------------------------------------------------------------- writeBinaryDouble(n, pos) { if (!pos) { pos = this.reserveBytes(8); } this.buf.writeDoubleBE(n, pos); if ((this.buf[pos] & 0x80) === 0) { this.buf[pos] |= 0x80; } else { // We complement the bits for a negative number this.buf[pos] ^= 0xff; this.buf[pos + 1] ^= 0xff; this.buf[pos + 2] ^= 0xff; this.buf[pos + 3] ^= 0xff; this.buf[pos + 4] ^= 0xff; this.buf[pos + 5] ^= 0xff; this.buf[pos + 6] ^= 0xff; this.buf[pos + 7] ^= 0xff; } } //--------------------------------------------------------------------------- // writeBinaryFloat() // // Writes the number in binary float format to the buffer. //--------------------------------------------------------------------------- writeBinaryFloat(n, pos) { if (!pos) { pos = this.reserveBytes(4); } this.buf.writeFloatBE(n, pos); if ((this.buf[pos] & 0x80) === 0) { this.buf[pos] |= 0x80; } else { // We complement the bits for a negative number this.buf[pos] ^= 0xff; this.buf[pos + 1] ^= 0xff; this.buf[pos + 2] ^= 0xff; this.buf[pos + 3] ^= 0xff; } } //--------------------------------------------------------------------------- // writeBytes() // // Writes the bytes in the supplied buffer to the buffer. //--------------------------------------------------------------------------- writeBytes(value) { let start = 0; let valueLen = value.length; while (valueLen > 0) { const bytesLeft = this.numBytesLeft(); if (bytesLeft === 0) { this._grow(this.pos + valueLen); } const bytesToWrite = Math.min(bytesLeft, valueLen); value.copy(this.buf, this.pos, start, start + bytesToWrite); this.pos += bytesToWrite; start += bytesToWrite; valueLen -= bytesToWrite; } } // _writeRawBytesAndLength() // // Writes the length in the format required before // writing the bytes. //--------------------------------------------------------------------------- _writeRawBytesAndLength(value, numBytes) { if (numBytes <= constants.TNS_MAX_SHORT_LENGTH) { this.writeUInt8(numBytes); if (numBytes > 0) { this.writeBytes(value); } } else { let start = 0; this.writeUInt8(constants.TNS_LONG_LENGTH_INDICATOR); while (numBytes > 0) { const chunkLen = Math.min(numBytes, constants.BUFFER_CHUNK_SIZE); this.writeUB4(chunkLen); this.writeBytes(value.subarray(start, start + chunkLen)); numBytes -= chunkLen; start += chunkLen; } this.writeUB4(0); } } //--------------------------------------------------------------------------- // writeBytesWithLength() // // Writes the bytes in the supplied buffer to the buffer, but first writes // the length. If the length exceeds a fixed value, the value is written in // chunks instead. //--------------------------------------------------------------------------- writeBytesWithLength(value) { const numBytes = value.length; this._writeRawBytesAndLength(value, numBytes); } //--------------------------------------------------------------------------- // writeDbObject() // // Writes a database object to the buffer. //--------------------------------------------------------------------------- writeDbObject(obj) { this.writeUB4(obj.toid.length); this.writeBytesWithLength(obj.toid); if (obj.oid) { this.writeUB4(obj.oid.length); this.writeBytesWithLength(obj.oid); } else { this.writeUB4(0); } this.writeUB4(0); // snapshot this.writeUB4(0); // version const packedData = obj._getPackedData(); this.writeUB4(packedData.length); this.writeUB4(obj.flags); this.writeBytesWithLength(packedData); } //--------------------------------------------------------------------------- // writeOracleDate() // // Writes the date to the buffer using the given Oracle type. Note that if a // timestamp with zero milliseconds is written, the type is automatically // changed to DB_TYPE_DATE (except for DB_TYPE_TIMESTAMP_TZ which requires // the full amount to be written). //--------------------------------------------------------------------------- writeOracleDate(date, type, writeLength = true) { let fsec; let length = type._bufferSizeFactor; if (length > 7) { fsec = date.getUTCMilliseconds() * 1000 * 1000; if (fsec === 0 && length <= 11) length = 7; } if (writeLength) { this.writeUInt8(length); } const pos = this.reserveBytes(length); if (type === types.DB_TYPE_DATE || type == types.DB_TYPE_TIMESTAMP) { const year = date.getFullYear(); this.buf[pos] = Math.trunc(year / 100) + 100; this.buf[pos + 1] = year % 100 + 100; this.buf[pos + 2] = date.getMonth() + 1; this.buf[pos + 3] = date.getDate(); this.buf[pos + 4] = date.getHours() + 1; this.buf[pos + 5] = date.getMinutes() + 1; this.buf[pos + 6] = date.getSeconds() + 1; } else { const year = date.getUTCFullYear(); this.buf[pos] = Math.trunc(year / 100) + 100; this.buf[pos + 1] = year % 100 + 100; this.buf[pos + 2] = date.getUTCMonth() + 1; this.buf[pos + 3] = date.getUTCDate(); this.buf[pos + 4] = date.getUTCHours() + 1; this.buf[pos + 5] = date.getUTCMinutes() + 1; this.buf[pos + 6] = date.getUTCSeconds() + 1; } if (length > 7) { this.buf.writeInt32BE(fsec, pos + 7); if (length > 11) { this.buf[pos + 11] = constants.TZ_HOUR_OFFSET; this.buf[pos + 12] = constants.TZ_MINUTE_OFFSET; } } } //--------------------------------------------------------------------------- // writeOracleIntervalYM() // // Writes a time interval to the buffer in Oracle Interval Year To Month // format. It is assumed that the 'value' parameter is a valid IntervalYM // object at this stage. //--------------------------------------------------------------------------- writeOracleIntervalYM(value, writeLength = true) { if (writeLength) { this.writeUInt8(5); } this.writeUInt32BE(value.years + constants.TNS_DURATION_MID); this.writeUInt8(value.months + constants.TNS_DURATION_OFFSET); } //--------------------------------------------------------------------------- // writeOracleIntervalDS() // // Writes a time interval to the buffer in Oracle Interval Day To Second // format. It is assumed that the 'value' parameter is a valid IntervalDS // object at this stage. //--------------------------------------------------------------------------- writeOracleIntervalDS(value, writeLength = true) { if (writeLength) { this.writeUInt8(11); } this.writeUInt32BE(value.days + constants.TNS_DURATION_MID); this.writeUInt8(value.hours + constants.TNS_DURATION_OFFSET); this.writeUInt8(value.minutes + constants.TNS_DURATION_OFFSET); this.writeUInt8(value.seconds + constants.TNS_DURATION_OFFSET); this.writeUInt32BE(value.fseconds + constants.TNS_DURATION_MID); } //--------------------------------------------------------------------------- // writeOracleNumber() // // Writes the number (in string form) in Oracle Number format to the buffer. //--------------------------------------------------------------------------- writeOracleNumber(value) { // determine if number is negative let isNegative = false; if (value[0] === '-') { isNegative = true; value = value.substring(1); } // parse the exponent, if one is present let exponent = 0; const exponentPos = value.indexOf('e'); if (exponentPos > 0) { exponent = Number(value.substring(exponentPos + 1)); value = value.substring(0, exponentPos); } // adjust the exponent and the value if there is a decimal point const decimalPos = value.indexOf('.'); if (decimalPos > 0) { exponent -= (value.length - decimalPos - 1); value = value.substring(0, decimalPos) + value.substring(decimalPos + 1); } // strip any leading zeroes if (value[0] === '0') { value = value.replace(/^0+/, ""); } // strip any trailing zeroes if (value.length > 0 && value[value.length - 1] === '0') { const trimmedValue = value.replace(/0+$/, ""); exponent += (value.length - trimmedValue.length); value = trimmedValue; } // throw exception if number cannot be represented as an Oracle Number if (value.length > constants.NUMBER_MAX_DIGITS || exponent >= 126 || exponent <= -131) { errors.throwErr(errors.ERR_ORACLE_NUMBER_NO_REPR); } // if the exponent is odd, append a zero if ((exponent > 0 && exponent % 2 === 1) || (exponent < 0 && exponent % 2 === -1)) { exponent--; value += "0"; } // add a leading zero if the number of digits is odd if (value.length % 2 === 1) { value = "0" + value; } // write the encoded data to the wire const appendSentinel = (isNegative && value.length < constants.NUMBER_MAX_DIGITS); const numPairs = value.length / 2; let exponentOnWire = ((exponent + value.length) / 2) + 192; if (isNegative) { exponentOnWire = (exponentOnWire ^ 0xFF); } else if (value.length === 0 && exponent === 0) { exponentOnWire = 128; } let pos = this.reserveBytes(numPairs + 2 + appendSentinel); this.buf[pos++] = numPairs + 1 + appendSentinel; this.buf[pos++] = exponentOnWire; for (let i = 0; i < value.length; i += 2) { const base100Digit = Number(value.substring(i, i + 2)); if (isNegative) { this.buf[pos++] = 101 - base100Digit; } else { this.buf[pos++] = base100Digit + 1; } } if (appendSentinel) { this.buf[pos] = 102; } } //--------------------------------------------------------------------------- // writeStr() // // Writes the string to the buffer. //--------------------------------------------------------------------------- writeStr(s) { this.writeBytes(Buffer.from(s)); } //--------------------------------------------------------------------------- // writeInt32BE() // // Writes a signed 32-bit integer to the buffer in big endian order. //--------------------------------------------------------------------------- writeInt32BE(n) { const pos = this.reserveBytes(4); this.buf.writeInt32BE(n, pos); } //--------------------------------------------------------------------------- // writeUB4() // // Writes an unsigned integer (up to 4 bytes in length) in variable length // format to the buffer. //--------------------------------------------------------------------------- writeUB4(value) { if (value === 0) { this.writeUInt8(0); } else if (value <= 0xff) { this.writeUInt8(1); this.writeUInt8(value); } else if (value <= 0xffff) { this.writeUInt8(2); this.writeUInt16BE(value); } else { this.writeUInt8(4); this.writeUInt32BE(value); } } //--------------------------------------------------------------------------- // writeUB2() // // Writes an unsigned integer (up to 2 bytes in length) in variable length // format to the buffer. //--------------------------------------------------------------------------- writeUB2(value) { if (value === 0) { this.writeUInt8(0); } else if (value <= 0xff) { this.writeUInt8(1); this.writeUInt8(value); } else { this.writeUInt8(2); this.writeUInt16BE(value); } } //--------------------------------------------------------------------------- // writeUB8() // // Writes an unsigned integer (up to 8 bytes in length) in variable length // format to the buffer. //--------------------------------------------------------------------------- writeUB8(value) { if (value === 0) { this.writeUInt8(0); } else if (value <= 0xff) { this.writeUInt8(1); this.writeUInt8(value); } else if (value <= 0xffff) { this.writeUInt8(2); this.writeUInt16BE(value); } else if (value <= 0xffffffff) { this.writeUInt8(4); this.writeUInt32BE(value); } else { this.writeUInt8(8); this.writeUInt64BE(value); } } //--------------------------------------------------------------------------- // writeUInt8() // // Writes an unsigned 8-bit integer to the buffer. //--------------------------------------------------------------------------- writeUInt8(n) { const pos = this.reserveBytes(1); this.buf[pos] = n; } // writeSB1() // // Writes an signed 8-bit integer to the buffer. //--------------------------------------------------------------------------- writeSB1(n) { const pos = this.reserveBytes(1); this.buf[pos] = n; } //--------------------------------------------------------------------------- // writeUInt16BE() // // Writes an unsigned 16-bit integer to the buffer in big endian order. //--------------------------------------------------------------------------- writeUInt16BE(n) { const pos = this.reserveBytes(2); this.buf.writeUInt16BE(n, pos); } //--------------------------------------------------------------------------- // writeUInt32BE() // // Writes an unsigned 32-bit integer to the buffer in big endian order. //--------------------------------------------------------------------------- writeUInt32BE(n) { const pos = this.reserveBytes(4); this.buf.writeUInt32BE(n, pos); } //--------------------------------------------------------------------------- // writeUInt64BE() // // Writes an unsigned 64-bit integer to the buffer in big endian order. Since // Node.js doesn't support anything above 32-bits without using BigInt, the // higher order bits are simply written as 0. //--------------------------------------------------------------------------- writeUInt64BE(n) { const pos = this.reserveBytes(8); this.buf.writeUInt32BE(0, pos); this.buf.writeUInt32BE(n, pos + 4); } //--------------------------------------------------------------------------- // writeUInt16LE() // // Writes an unsigned 16-bit integer to the buffer in little endian order. //--------------------------------------------------------------------------- writeUInt16LE(n) { const pos = this.reserveBytes(2); this.buf.writeUInt16LE(n, pos); } } class GrowableBuffer extends BaseBuffer { //--------------------------------------------------------------------------- // constructor() // // Initializes the buffer with an initial fixed chunk size. //--------------------------------------------------------------------------- constructor(initializer) { if (initializer) { super(initializer); } else { super(constants.BUFFER_CHUNK_SIZE); this.size = this.maxSize; } } //--------------------------------------------------------------------------- // _grow() // // Called when the buffer needs to grow. Ensures that sufficient space is // allocated to include the requested number of bytes, rounded to the nearest // chunk size. //--------------------------------------------------------------------------- _grow(numBytes) { const remainder = numBytes % constants.BUFFER_CHUNK_SIZE; if (remainder > 0) { numBytes += (constants.BUFFER_CHUNK_SIZE - remainder); } const buf = Buffer.allocUnsafe(numBytes); this.buf.copy(buf); this.buf = buf; this.maxSize = this.size = numBytes; } } module.exports = { BaseBuffer, GrowableBuffer };