848 lines
29 KiB
JavaScript
848 lines
29 KiB
JavaScript
// 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
|
|
};
|