UNPKG

oracledb

Version:

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

848 lines (752 loc) 29.9 kB
// Copyright (c) 2023, 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 { BaseBuffer, GrowableBuffer } = require('./buffer.js'); const { Buffer } = require('buffer'); const constants = require("./constants.js"); const errors = require("../../errors.js"); const types = require("../../types.js"); const util = require("util"); const vector = require("./vector.js"); const nodbUtil = require("../../util.js"); /** * Class used for decoding */ class OsonDecoder extends BaseBuffer { //--------------------------------------------------------------------------- // _decodeContainerNode() // // Decodes a container node (object or array) from the tree segment and // returns the JavaScript equivalent. //--------------------------------------------------------------------------- _decodeContainerNode(nodeType) { // determine the number of children by examining the 4th and 5th most // significant bits of the node type; determine the offsets in the tree // segment to the field ids array and the value offsets array let container, offsetsPos, fieldIdsPos; const containerOffset = this.pos - this.treeSegPos - 1; let numChildren = this._getNumChildren(nodeType); const isObject = ((nodeType & 0x40) === 0); if (numChildren === undefined) { const offset = this._getOffset(nodeType); offsetsPos = this.pos; this.pos = this.treeSegPos + offset; const sharedNodeType = this.readUInt8(); numChildren = this._getNumChildren(sharedNodeType); container = (isObject) ? {} : new Array(numChildren); fieldIdsPos = this.pos; } else if (isObject) { container = {}; fieldIdsPos = this.pos; offsetsPos = this.pos + this.fieldIdLength * numChildren; } else { container = new Array(numChildren); offsetsPos = this.pos; } for (let i = 0; i < numChildren; i++) { let name; if (isObject) { let fieldId; if (this.fieldIdLength === 1) { fieldId = this.buf[fieldIdsPos]; } else if (this.fieldIdLength == 2) { fieldId = this.buf.readUInt16BE(fieldIdsPos); } else { fieldId = this.buf.readUInt32BE(fieldIdsPos); } name = this.fieldNames[fieldId - 1]; fieldIdsPos += this.fieldIdLength; } this.pos = offsetsPos; let offset = this._getOffset(nodeType); if (this.relativeOffsets) { offset += containerOffset; } offsetsPos = this.pos; this.pos = this.treeSegPos + offset; if (isObject) { container[name] = this._decodeNode(); } else { container[i] = this._decodeNode(); } } return container; } //--------------------------------------------------------------------------- // _decodeNode() // // Decodes a node from the tree segment and returns the JavaScript // equivalent. //--------------------------------------------------------------------------- _decodeNode() { // if the most significant bit is set the node refers to a container let nodeType = this.readUInt8(); if (nodeType & 0x80) { return this._decodeContainerNode(nodeType); } // handle simple scalars if (nodeType === constants.TNS_JSON_TYPE_NULL) { return null; } else if (nodeType === constants.TNS_JSON_TYPE_TRUE) { return true; } else if (nodeType === constants.TNS_JSON_TYPE_FALSE) { return false; // handle fixed length scalars } else if (nodeType === constants.TNS_JSON_TYPE_DATE || nodeType === constants.TNS_JSON_TYPE_TIMESTAMP7) { return this.parseOracleDate(this.readBytes(7)); } else if (nodeType === constants.TNS_JSON_TYPE_TIMESTAMP) { return this.parseOracleDate(this.readBytes(11)); } else if (nodeType === constants.TNS_JSON_TYPE_TIMESTAMP_TZ) { return this.parseOracleDate(this.readBytes(13)); } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_FLOAT) { return this.parseBinaryFloat(this.readBytes(4)); } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_DOUBLE) { return this.parseBinaryDouble(this.readBytes(8)); // handle interval datatypes } else if (nodeType === constants.TNS_JSON_TYPE_INTERVAL_YM) { return this.parseOracleIntervalYM(this.readBytes(5)); } else if (nodeType === constants.TNS_JSON_TYPE_INTERVAL_DS) { return this.parseOracleIntervalDS(this.readBytes(11)); // handle scalars with lengths stored outside the node itself } else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT8) { return this.readBytes(this.readUInt8()).toString(); } else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT16) { return this.readBytes(this.readUInt16BE()).toString(); } else if (nodeType === constants.TNS_JSON_TYPE_STRING_LENGTH_UINT32) { return this.readBytes(this.readUInt32BE()).toString(); } else if (nodeType === constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8) { return parseFloat(this.readOracleNumber()); } else if (nodeType === constants.TNS_JSON_TYPE_ID) { const buf = this.readBytes(this.readUInt8()); const jsonId = new types.JsonId(buf.length); buf.copy(jsonId); return jsonId; } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16) { return Buffer.from(this.readBytes(this.readUInt16BE())); } else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32) { return Buffer.from(this.readBytes(this.readUInt32BE())); } else if (nodeType === constants.TNS_JSON_TYPE_EXTENDED) { nodeType = this.readUInt8(); if (nodeType === constants.TNS_JSON_TYPE_VECTOR) { const vecImage = this.readBytes(this.readUInt32BE()); const decoder = new vector.VectorDecoder(vecImage); return decoder.decode(); } } // handle number/decimal with length stored inside the node itself const typeBits = nodeType & 0xf0; if (typeBits === 0x20 || typeBits === 0x60) { const len = nodeType & 0x0f; return parseFloat(this.parseOracleNumber(this.readBytes(len + 1))); // handle integer with length stored inside the node itself } else if (typeBits === 0x40 || typeBits === 0x50) { const len = nodeType & 0x0f; return parseFloat(this.parseOracleNumber(this.readBytes(len))); // handle string with length stored inside the node itself } else if ((nodeType & 0xe0) == 0) { if (nodeType === 0) return ''; return this.readBytes(nodeType).toString(); } errors.throwErr(errors.ERR_UNSUPPORTED_DATA_TYPE_IN_JSON, nodeType); } //--------------------------------------------------------------------------- // _getNumChildren() // // Returns the number of children a container has. This is determined by // looking at the 4th and 5th most significant bits of the node type. // // 00 - number of children is uint8_t // 01 - number of children is uint16_t // 10 - number of children is uint32_t // 11 - field ids are shared with another object whose offset follows // // In the latter case the value undefined is returned and the number of // children must be read from the shared object at the specified offset. //--------------------------------------------------------------------------- _getNumChildren(nodeType) { const childrenBits = (nodeType & 0x18); if (childrenBits === 0) { return this.readUInt8(); } else if (childrenBits === 0x08) { return this.readUInt16BE(); } else if (childrenBits === 0x10) { return this.readUInt32BE(); } } //--------------------------------------------------------------------------- // _getOffset() // // Returns an offset. The offset will be either a 16-bit or 32-bit value // depending on the value of the 3rd significant bit of the node type. //--------------------------------------------------------------------------- _getOffset(nodeType) { if (nodeType & 0x20) { return this.readUInt32BE(); } else { return this.readUInt16BE(); } } //--------------------------------------------------------------------------- // _getFieldNames // // Reads the field names from the buffer. //--------------------------------------------------------------------------- _getFieldNames(arrStartPos, numFields, offsetsSize, fieldNamesSegSize, fieldNamesSize) { // skip the hash id array (1 byte * fieldNamesSize for each field) this.skipBytes(numFields * fieldNamesSize); // skip the field name offsets array for now const offsetsPos = this.pos; this.skipBytes(numFields * offsetsSize); const ptr = this.readBytes(fieldNamesSegSize); const finalPos = this.pos; // determine the names of the fields this.pos = offsetsPos; let offset; for (let i = arrStartPos; i < arrStartPos + numFields; i++) { if (offsetsSize === 2) { offset = this.readUInt16BE(); } else { offset = this.readUInt32BE(); } // get the field name object let temp; if (fieldNamesSize === 1) { // Short Field Name temp = ptr.readUInt8(offset); } else { // Long Field Name temp = ptr.readUInt16BE(offset); } this.fieldNames[i] = ptr.subarray(offset + fieldNamesSize, offset + temp + fieldNamesSize).toString(); } this.pos = finalPos; } //--------------------------------------------------------------------------- // decode() // // Decodes the OSON and returns a JavaScript object corresponding to its // contents. //--------------------------------------------------------------------------- decode() { // parse root header const magic = this.readBytes(3); if (magic[0] !== constants.TNS_JSON_MAGIC_BYTE_1 || magic[1] !== constants.TNS_JSON_MAGIC_BYTE_2 || magic[2] !== constants.TNS_JSON_MAGIC_BYTE_3) { errors.throwErr(errors.ERR_UNEXPECTED_DATA, magic.toString('hex')); } const version = this.readUInt8(); if (version !== constants.TNS_JSON_VERSION_MAX_FNAME_255 && version !== constants.TNS_JSON_VERSION_MAX_FNAME_65535) { errors.throwErr(errors.ERR_OSON_VERSION_NOT_SUPPORTED, version); } const primaryFlags = this.readUInt16BE(); this.relativeOffsets = primaryFlags & constants.TNS_JSON_FLAG_REL_OFFSET_MODE; // scalar values are much simpler if (primaryFlags & constants.TNS_JSON_FLAG_IS_SCALAR) { if (primaryFlags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) { this.skipBytes(4); } else { this.skipBytes(2); } return this._decodeNode(); } // determine the number of short field names let numShortFieldNames; if (primaryFlags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32) { numShortFieldNames = this.readUInt32BE(); this.fieldIdLength = 4; } else if (primaryFlags & constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16) { numShortFieldNames = this.readUInt16BE(); this.fieldIdLength = 2; } else { numShortFieldNames = this.readUInt8(); this.fieldIdLength = 1; } // determine the size of the short field names segment let shortFieldNameOffsetsSize, shortFieldNamesSegSize; if (primaryFlags & constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32) { shortFieldNameOffsetsSize = 4; shortFieldNamesSegSize = this.readUInt32BE(); } else { shortFieldNameOffsetsSize = 2; shortFieldNamesSegSize = this.readUInt16BE(); } // if the version indicates that field names > 255 bytes exist, parse // the information about that segment let longFieldNameOffsetsSize, longFieldNamesSegSize; let numLongFieldNames = 0; if (version === constants.TNS_JSON_VERSION_MAX_FNAME_65535) { const secondaryFlags = this.readUInt16BE(); if (secondaryFlags & constants.TNS_JSON_FLAG_SEC_FNAMES_SEG_UINT16) { longFieldNameOffsetsSize = 2; } else { longFieldNameOffsetsSize = 4; } numLongFieldNames = this.readUInt32BE(); longFieldNamesSegSize = this.readUInt32BE(); } // skip the size of the tree segment if (primaryFlags & constants.TNS_JSON_FLAG_TREE_SEG_UINT32) { this.skipBytes(4); } else { this.skipBytes(2); } // skip the number of "tiny" nodes this.skipBytes(2); this.fieldNames = new Array(numShortFieldNames + numLongFieldNames); // if there are any short names, read them now if (numShortFieldNames > 0) { this._getFieldNames(0, numShortFieldNames, shortFieldNameOffsetsSize, shortFieldNamesSegSize, 1); } // if there are any long names, read them now if (numLongFieldNames > 0) { this._getFieldNames(numShortFieldNames, numLongFieldNames, longFieldNameOffsetsSize, longFieldNamesSegSize, 2); } // determine tree segment position in the buffer this.treeSegPos = this.pos; // decode the root node return this._decodeNode(); } } class OsonFieldName { constructor(name, maxFieldNameSize) { this.name = name; this.nameBytes = Buffer.from(name); if (this.nameBytes.length > maxFieldNameSize) { errors.throwErr(errors.ERR_OSON_FIELD_NAME_LIMITATION, maxFieldNameSize); } // BigInt constants for calculating Hash ID for the OSON Field Name const INITIAL_HASHID = 0x811C9DC5n; const HASH_MULTIPLIER = 16777619n; const HASH_MASK = 0xffffffffn; this.hashId = INITIAL_HASHID; for (let i = 0; i < this.nameBytes.length; i++) { const c = BigInt(this.nameBytes[i]); this.hashId = ((this.hashId ^ c) * HASH_MULTIPLIER) & HASH_MASK; } this.hashId = Number(this.hashId) & 0xff; } } class OsonFieldNamesSegment extends GrowableBuffer { constructor() { super(); this.fieldNames = []; } //--------------------------------------------------------------------------- // addName() // // Adds a name to the field names segment. //--------------------------------------------------------------------------- addName(fieldName) { fieldName.offset = this.pos; if (fieldName.nameBytes.length <= 255) { this.writeUInt8(fieldName.nameBytes.length); } else { this.writeUInt16BE(fieldName.nameBytes.length); } this.writeBytes(fieldName.nameBytes); this.fieldNames.push(fieldName); } //--------------------------------------------------------------------------- // _processFieldNames() // // Processes the field names in preparation for encoding within OSON. //--------------------------------------------------------------------------- _processFieldNames(fieldIdOffset) { this.fieldNames.sort((a, b) => { if (a.hashId < b.hashId) return -1; if (a.hashId > b.hashId) return 1; if (a.nameBytes.length < b.nameBytes.length) return -1; if (a.nameBytes.length > b.nameBytes.length) return 1; if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; }); for (let i = 0; i < this.fieldNames.length; i++) { this.fieldNames[i].fieldId = fieldIdOffset + i + 1; } if (this.fieldNames.length < 256) { this.fieldIdSize = 1; } else if (this.fieldNames.length < 65536) { this.fieldIdSize = 2; } else { this.fieldIdSize = 4; } } } class OsonTreeSegment extends GrowableBuffer { //--------------------------------------------------------------------------- // _encodeArray() // // Encodes an array in the OSON tree segment. //--------------------------------------------------------------------------- _encodeArray(value, encoder) { this._encodeContainer(constants.TNS_JSON_TYPE_ARRAY, value.length); const len = value.length * 4; const pos = this.reserveBytes(len); let offsetsBufPos = pos; for (const element of value) { this.buf.writeUInt32BE(this.pos, offsetsBufPos); offsetsBufPos += 4; this.encodeNode(element, encoder); } } //--------------------------------------------------------------------------- // _encodeContainer() // // Encodes the first part of a container (array or object) in the OSON tree // segment. //--------------------------------------------------------------------------- _encodeContainer(nodeType, numChildren) { nodeType |= 0x20; // use uint32_t for offsets if (numChildren > 65535) { nodeType |= 0x10; // num children is uint32_t } else if (numChildren > 255) { nodeType |= 0x08; // num children is uint16_t } this.writeUInt8(nodeType); if (numChildren < 256) { this.writeUInt8(numChildren); } else if (numChildren < 65536) { this.writeUInt16BE(numChildren); } else { this.writeUInt32BE(numChildren); } } //--------------------------------------------------------------------------- // _encodeObject() // // Encodes an object in the OSON tree segment. //--------------------------------------------------------------------------- _encodeObject(value, encoder) { const numChildren = value.values.length; this._encodeContainer(constants.TNS_JSON_TYPE_OBJECT, numChildren); let fieldIdOffset = this.pos; let valueOffset = this.pos + (numChildren * encoder.fieldIdSize); const finalOffset = valueOffset + numChildren * 4; this.reserveBytes(finalOffset - this.pos); for (let i = 0; i < value.fields.length; i++) { const fieldName = encoder.fieldNamesMap.get(value.fields[i]); if (encoder.fieldIdSize == 1) { this.buf[fieldIdOffset] = fieldName.fieldId; } else if (encoder.fieldIdSize == 2) { this.buf.writeUInt16BE(fieldName.fieldId, fieldIdOffset); } else { this.buf.writeUInt32BE(fieldName.fieldId, fieldIdOffset); } this.buf.writeUInt32BE(this.pos, valueOffset); fieldIdOffset += encoder.fieldIdSize; valueOffset += 4; this.encodeNode(value.values[i], encoder); } } //--------------------------------------------------------------------------- // encodeNode() // // Encodes a value (node) in the OSON tree segment. //--------------------------------------------------------------------------- encodeNode(value, encoder) { // handle null if (value === undefined || value === null) { this.writeUInt8(constants.TNS_JSON_TYPE_NULL); // handle booleans } else if (typeof value === 'boolean') { if (value) { this.writeUInt8(constants.TNS_JSON_TYPE_TRUE); } else { this.writeUInt8(constants.TNS_JSON_TYPE_FALSE); } // handle numbers } else if (typeof value === 'number') { this.writeUInt8(constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8); this.writeOracleNumber(value.toString()); // handle strings } else if (typeof value === 'string') { const buf = Buffer.from(value); if (buf.length < 256) { this.writeUInt8(constants.TNS_JSON_TYPE_STRING_LENGTH_UINT8); this.writeUInt8(buf.length); } else if (buf.length < 65536) { this.writeUInt8(constants.TNS_JSON_TYPE_STRING_LENGTH_UINT16); this.writeUInt16BE(buf.length); } else { this.writeUInt8(constants.TNS_JSON_TYPE_STRING_LENGTH_UINT32); this.writeUInt32BE(buf.length); } if (buf.length > 0) { this.writeBytes(buf); } // handle dates } else if (util.types.isDate(value)) { if (value.getUTCMilliseconds() === 0) { this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP7); this.writeOracleDate(value, types.DB_TYPE_DATE, false); } else { this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP); this.writeOracleDate(value, types.DB_TYPE_TIMESTAMP, false); } // handle interval data types } else if (value instanceof types.IntervalYM) { this.writeUInt8(constants.TNS_JSON_TYPE_INTERVAL_YM); this.writeOracleIntervalYM(value, false); } else if (value instanceof types.IntervalDS) { this.writeUInt8(constants.TNS_JSON_TYPE_INTERVAL_DS); this.writeOracleIntervalDS(value, false); // handle buffers } else if (Buffer.isBuffer(value)) { if (value.length < 65536) { this.writeUInt8(constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16); this.writeUInt16BE(value.length); } else { this.writeUInt8(constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32); this.writeUInt32BE(value.length); } this.writeBytes(value); // handle arrays } else if (Array.isArray(value)) { this._encodeArray(value, encoder); // handle vectors } else if (nodbUtil.isVectorValue(value)) { this.writeUInt8(constants.TNS_JSON_TYPE_EXTENDED); this.writeUInt8(constants.TNS_JSON_TYPE_VECTOR); const encoder = new vector.VectorEncoder(); const buf = encoder.encode(value); this.writeUInt32BE(buf.length); this.writeBytes(buf); } else if (value instanceof types.JsonId) { this.writeUInt8(constants.TNS_JSON_TYPE_ID); this.writeUInt8(value.length); this.writeBytes(Buffer.from(value.buffer)); // handle objects } else { this._encodeObject(value, encoder); } } } /** * Class used for encoding */ class OsonEncoder extends GrowableBuffer { //--------------------------------------------------------------------------- // _addFieldName() // // Add a field with the given name. //--------------------------------------------------------------------------- _addFieldName(name) { const fieldName = new OsonFieldName(name, this.maxFieldNameSize); this.fieldNamesMap.set(name, fieldName); if (fieldName.nameBytes.length <= 255) { this.shortFieldNamesSeg.addName(fieldName); } else { if (!this.longFieldNamesSeg) { this.longFieldNamesSeg = new OsonFieldNamesSegment(); } this.longFieldNamesSeg.addName(fieldName); } } //--------------------------------------------------------------------------- // _examineNode() // // Examines the value. If it contains fields, unique names are retained. The // values are then examined to see if they also contain fields. Arrays are // examined to determine they contain elements that contain fields. //--------------------------------------------------------------------------- _examineNode(value) { if (Array.isArray(value)) { for (const element of value) { this._examineNode(element); } } else if (value && Array.isArray(value.fields)) { for (let i = 0; i < value.fields.length; i++) { const name = value.fields[i]; const element = value.values[i]; if (!this.fieldNamesMap.has(name)) { this._addFieldName(name); } this._examineNode(element); } } } //--------------------------------------------------------------------------- // _writeExtendedHeader() // // Write the extended header containing information about the short and long // field name segments. //--------------------------------------------------------------------------- _writeExtendedHeader() { // write number of short field names if (this.fieldIdSize === 1) { this.writeUInt8(this.shortFieldNamesSeg.fieldNames.length); } else if (this.fieldIdSize === 2) { this.writeUInt16BE(this.shortFieldNamesSeg.fieldNames.length); } else { this.writeUInt32BE(this.shortFieldNamesSeg.fieldNames.length); } // write size of short field names segment if (this.shortFieldNamesSeg.pos < 65536) { this.writeUInt16BE(this.shortFieldNamesSeg.pos); } else { this.writeUInt32BE(this.shortFieldNamesSeg.pos); } // write fields for long field names segment, if applicable if (this.longFieldNamesSeg) { let secondaryFlags = 0; if (this.longFieldNamesSeg.pos < 65536) { secondaryFlags = constants.TNS_JSON_FLAG_SEC_FNAMES_SEG_UINT16; } this.writeUInt16BE(secondaryFlags); this.writeUInt32BE(this.longFieldNamesSeg.fieldNames.length); this.writeUInt32BE(this.longFieldNamesSeg.pos); } } //--------------------------------------------------------------------------- // _writeFieldNamesSeg() // // Write the contents of the field names segment to the buffer. //--------------------------------------------------------------------------- _writeFieldNamesSeg(fieldNamesSeg) { // write array of hash ids for (const fieldName of fieldNamesSeg.fieldNames) { if (fieldName.nameBytes.length <= 255) { this.writeUInt8(fieldName.hashId); } else { this.writeUInt16BE(fieldName.hashId); } } // write array of field name offsets for the short field names for (const fieldName of fieldNamesSeg.fieldNames) { if (fieldNamesSeg.pos < 65536) { this.writeUInt16BE(fieldName.offset); } else { this.writeUInt32BE(fieldName.offset); } } // write field names if (fieldNamesSeg.pos > 0) { this.writeBytes(fieldNamesSeg.buf.subarray(0, fieldNamesSeg.pos)); } } //--------------------------------------------------------------------------- // encode() // // Encodes the value as OSON and returns a buffer containing the OSON bytes. //--------------------------------------------------------------------------- encode(value, maxFieldNameSize) { this.maxFieldNameSize = maxFieldNameSize; // determine the flags to use let flags = constants.TNS_JSON_FLAG_INLINE_LEAF; if (Array.isArray(value) || (value && Array.isArray(value.fields))) { // examine all values recursively to determine the unique set of field // names and whether they need to be added to the long field names // segment (> 255 bytes) or short field names segment (<= 255 bytes) this.fieldNamesMap = new Map(); this.shortFieldNamesSeg = new OsonFieldNamesSegment(); this._examineNode(value); // perform processing of field names segments and determine the total // number of unique field names in the value let totalNumFieldNames = 0; if (this.shortFieldNamesSeg) { this.shortFieldNamesSeg._processFieldNames(0); totalNumFieldNames += this.shortFieldNamesSeg.fieldNames.length; } if (this.longFieldNamesSeg) { this.longFieldNamesSeg._processFieldNames(totalNumFieldNames); totalNumFieldNames += this.longFieldNamesSeg.fieldNames.length; } // determine remaining flags and field id size flags |= constants.TNS_JSON_FLAG_HASH_ID_UINT8 | constants.TNS_JSON_FLAG_TINY_NODES_STAT; if (totalNumFieldNames > 65535) { flags |= constants.TNS_JSON_FLAG_NUM_FNAMES_UINT32; this.fieldIdSize = 4; } else if (totalNumFieldNames > 255) { flags |= constants.TNS_JSON_FLAG_NUM_FNAMES_UINT16; this.fieldIdSize = 2; } else { this.fieldIdSize = 1; } if (this.shortFieldNamesSeg.pos > 65535) { flags |= constants.TNS_JSON_FLAG_FNAMES_SEG_UINT32; } } else { // if the value is a simple scalar flags |= constants.TNS_JSON_FLAG_IS_SCALAR; } // encode values into the OSON tree segment const treeSeg = new OsonTreeSegment(); treeSeg.encodeNode(value, this); if (treeSeg.pos > 65535) { flags |= constants.TNS_JSON_FLAG_TREE_SEG_UINT32; } // write initial header this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_1); this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_2); this.writeUInt8(constants.TNS_JSON_MAGIC_BYTE_3); if (this.longFieldNamesSeg) { this.writeUInt8(constants.TNS_JSON_VERSION_MAX_FNAME_65535); } else { this.writeUInt8(constants.TNS_JSON_VERSION_MAX_FNAME_255); } this.writeUInt16BE(flags); // write extended header (when value is not scalar) if (this.shortFieldNamesSeg) { this._writeExtendedHeader(); } // write size of tree segment if (treeSeg.pos < 65536) { this.writeUInt16BE(treeSeg.pos); } else { this.writeUInt32BE(treeSeg.pos); } // write remainder of header and any data (when value is not scalar) if (this.shortFieldNamesSeg) { // write number of "tiny" nodes (always zero) this.writeUInt16BE(0); // write the field names segments this._writeFieldNamesSeg(this.shortFieldNamesSeg); if (this.longFieldNamesSeg) { this._writeFieldNamesSeg(this.longFieldNamesSeg); } } // write tree segment data this.writeBytes(treeSeg.buf.subarray(0, treeSeg.pos)); return this.buf.subarray(0, this.pos); } } module.exports = { OsonDecoder, OsonEncoder };