736 lines
25 KiB
JavaScript
736 lines
25 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 { Buffer } = require('buffer');
|
|
const constants = require('./protocol/constants.js');
|
|
const errors = require('../errors.js');
|
|
const types = require('../types.js');
|
|
const DbObjectImpl = require('../impl/dbObject.js');
|
|
const { GrowableBuffer } = require('../impl/datahandlers/buffer.js');
|
|
const ThinLobImpl = require('./lob.js');
|
|
|
|
class DbObjectPickleBuffer extends GrowableBuffer {
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _readBytesWithLength()
|
|
//
|
|
// Helper function that processes the number of bytes (if needed) and then
|
|
// acquires the specified number of bytes from the buffer.
|
|
//---------------------------------------------------------------------------
|
|
_readBytesWithLength(numBytes) {
|
|
if (numBytes === constants.TNS_LONG_LENGTH_INDICATOR) {
|
|
numBytes = this.readUInt32BE();
|
|
}
|
|
return this.readBytes(numBytes);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getIsAtomicNull()
|
|
//
|
|
// Reads the next byte and checks to see if the value is atomically null. If
|
|
// not, the byte is returned to the buffer for further processing.
|
|
//---------------------------------------------------------------------------
|
|
getIsAtomicNull() {
|
|
const value = this.readUInt8();
|
|
if (value === constants.TNS_OBJ_ATOMIC_NULL ||
|
|
value === constants.TNS_NULL_LENGTH_INDICATOR) {
|
|
return true;
|
|
}
|
|
this.pos -= 1;
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// readHeader()
|
|
//
|
|
// Reads the header of the pickled data.
|
|
//---------------------------------------------------------------------------
|
|
readHeader(obj) {
|
|
obj.imageFlags = this.readUInt8();
|
|
obj.imageVersion = this.readUInt8();
|
|
this.readLength();
|
|
if ((obj.imageFlags & constants.TNS_OBJ_NO_PREFIX_SEG) === 0) {
|
|
const prefixSegLength = this.readLength();
|
|
this.skipBytes(prefixSegLength);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// readLength()
|
|
//
|
|
// Read the length from the buffer. This will be a single byte, unless the
|
|
// value meets or exeeds TNS_LONG_LENGTH_INDICATOR. In that case, the value
|
|
// is stored as a 4-byte integer.
|
|
//---------------------------------------------------------------------------
|
|
readLength() {
|
|
const shortLength = this.readUInt8();
|
|
if (shortLength !== constants.TNS_LONG_LENGTH_INDICATOR) {
|
|
return shortLength;
|
|
}
|
|
return this.readUInt32BE();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// writeHeader()
|
|
//
|
|
// Writes the header of the pickled data. Since the size is unknown at this
|
|
// point, zero is written initially and the actual size is written later.
|
|
//---------------------------------------------------------------------------
|
|
writeHeader(obj) {
|
|
this.writeUInt8(obj.imageFlags);
|
|
this.writeUInt8(obj.imageVersion);
|
|
this.writeUInt8(constants.TNS_LONG_LENGTH_INDICATOR);
|
|
this.writeUInt32BE(0);
|
|
if (obj._objType.isCollection) {
|
|
this.writeUInt8(1); // length of prefix segment
|
|
this.writeUInt8(1); // prefix segment contents
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// writeLength()
|
|
//
|
|
// Writes the length to the buffer.
|
|
//---------------------------------------------------------------------------
|
|
writeLength(length) {
|
|
if (length <= constants.TNS_OBJ_MAX_SHORT_LENGTH) {
|
|
this.writeUInt8(length);
|
|
} else {
|
|
this.writeUInt8(constants.TNS_LONG_LENGTH_INDICATOR);
|
|
this.writeUInt32BE(length);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _writeRawBytesAndLength()
|
|
//
|
|
// Writes the length in the format required before
|
|
// writing the bytes.
|
|
//---------------------------------------------------------------------------
|
|
_writeRawBytesAndLength(value, numBytes) {
|
|
this.writeLength(numBytes);
|
|
this.writeBytes(value);
|
|
}
|
|
}
|
|
|
|
class ThinDbObjectImpl extends DbObjectImpl {
|
|
|
|
constructor(objType, packedData) {
|
|
if (typeof objType === 'function') {
|
|
objType = objType.prototype._objType;
|
|
}
|
|
super(objType);
|
|
this.packedData = packedData;
|
|
this.unpackedAttrs = new Map();
|
|
if (packedData) {
|
|
this.unpackedAssocArray = new Map();
|
|
this.unpackedAssocKeys = undefined;
|
|
} else if (objType) {
|
|
const prefix = Buffer.from([0, 0x22, constants.TNS_OBJ_NON_NULL_OID,
|
|
constants.TNS_OBJ_HAS_EXTENT_OID]);
|
|
this.toid = Buffer.concat([prefix, objType.oid, constants.TNS_EXTENT_OID]);
|
|
this.flags = constants.TNS_OBJ_TOP_LEVEL;
|
|
this.imageFlags = constants.TNS_OBJ_IS_VERSION_81;
|
|
this.imageVersion = constants.TNS_OBJ_IMAGE_VERSION;
|
|
if (objType.isCollection) {
|
|
this.imageFlags |= constants.TNS_OBJ_IS_COLLECTION;
|
|
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
|
|
this.unpackedAssocArray = new Map();
|
|
} else {
|
|
this.unpackedArray = [];
|
|
}
|
|
} else {
|
|
this.imageFlags |= constants.TNS_OBJ_NO_PREFIX_SEG;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _ensureAssocKeys()
|
|
//
|
|
// Ensure that the keys for the associative array have been calculated.
|
|
// PL/SQL associative arrays keep their keys in sorted order so this must be
|
|
// calculated when indices are required.
|
|
//---------------------------------------------------------------------------
|
|
_ensureAssocKeys() {
|
|
if (!this.unpackedAssocKeys) {
|
|
// Associative arrays indexed by integer are only supported now
|
|
this.unpackedAssocKeys = [...this.unpackedAssocArray.keys()].sort((x, y) => x - y);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _ensureUnpacked()
|
|
//
|
|
// Ensure that the data has been unpacked.
|
|
//---------------------------------------------------------------------------
|
|
_ensureUnpacked() {
|
|
if (this.packedData) {
|
|
this._unpackData();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _getPackedData()
|
|
//
|
|
// Returns the packed data for the object. This will either be the value
|
|
// retrieved from the database or generated packed data (for new objects and
|
|
// those that have had their data unpacked already).
|
|
//---------------------------------------------------------------------------
|
|
_getPackedData() {
|
|
if (this.packedData)
|
|
return this.packedData;
|
|
const buf = new DbObjectPickleBuffer();
|
|
buf.writeHeader(this);
|
|
this._packData(buf);
|
|
const size = buf.pos;
|
|
buf.pos = 3;
|
|
buf.writeUInt32BE(size);
|
|
return buf.buf.subarray(0, size);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _packData()
|
|
//
|
|
// Packs the data from the object into the buffer.
|
|
//---------------------------------------------------------------------------
|
|
_packData(buf) {
|
|
const objType = this._objType;
|
|
if (objType.isCollection) {
|
|
buf.writeUInt8(objType.collectionFlags);
|
|
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
|
|
this._ensureAssocKeys();
|
|
buf.writeLength(this.unpackedAssocKeys.length);
|
|
for (const index of this.unpackedAssocKeys) {
|
|
buf.writeInt32BE(index);
|
|
this._packValue(buf, objType.elementType, objType.elementTypeClass,
|
|
this.unpackedAssocArray.get(index));
|
|
}
|
|
} else {
|
|
buf.writeLength(this.unpackedArray.length);
|
|
for (const value of this.unpackedArray) {
|
|
this._packValue(buf, objType.elementType, objType.elementTypeClass,
|
|
value);
|
|
}
|
|
}
|
|
} else {
|
|
for (const attr of objType.attributes) {
|
|
this._packValue(buf, attr.type, attr.typeClass,
|
|
this.unpackedAttrs.get(attr.name));
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _packValue()
|
|
//
|
|
// Packs a value into the buffer. At this point it is assumed that the value
|
|
// matches the correct type.
|
|
//---------------------------------------------------------------------------
|
|
_packValue(buf, type, typeClass, value) {
|
|
if (value === null || value === undefined) {
|
|
if (typeClass && !typeClass.prototype.isCollection) {
|
|
buf.writeUInt8(constants.TNS_OBJ_ATOMIC_NULL);
|
|
} else {
|
|
buf.writeUInt8(constants.TNS_NULL_LENGTH_INDICATOR);
|
|
}
|
|
} else {
|
|
switch (type) {
|
|
case types.DB_TYPE_CHAR:
|
|
case types.DB_TYPE_VARCHAR:
|
|
buf.writeBytesWithLength(Buffer.from(value));
|
|
break;
|
|
case types.DB_TYPE_NCHAR:
|
|
case types.DB_TYPE_NVARCHAR:
|
|
buf.writeBytesWithLength(Buffer.from(value, 'utf16le').swap16());
|
|
break;
|
|
case types.DB_TYPE_NUMBER:
|
|
buf.writeOracleNumber(value.toString());
|
|
break;
|
|
case types.DB_TYPE_BINARY_INTEGER:
|
|
case types.DB_TYPE_BOOLEAN:
|
|
buf.writeUInt8(4);
|
|
buf.writeUInt32BE(value);
|
|
break;
|
|
case types.DB_TYPE_RAW:
|
|
buf.writeBytesWithLength(value);
|
|
break;
|
|
case types.DB_TYPE_BINARY_DOUBLE:
|
|
buf.writeUInt8(8);
|
|
buf.writeBinaryDouble(value);
|
|
break;
|
|
case types.DB_TYPE_BINARY_FLOAT:
|
|
buf.writeUInt8(4);
|
|
buf.writeBinaryFloat(value);
|
|
break;
|
|
case types.DB_TYPE_DATE:
|
|
case types.DB_TYPE_TIMESTAMP:
|
|
case types.DB_TYPE_TIMESTAMP_LTZ:
|
|
case types.DB_TYPE_TIMESTAMP_TZ:
|
|
buf.writeOracleDate(value, type);
|
|
break;
|
|
case types.DB_TYPE_OBJECT:
|
|
if (this._objType.isCollection || value._objType.isCollection) {
|
|
buf.writeBytesWithLength(value._getPackedData());
|
|
} else {
|
|
value._packData(buf);
|
|
}
|
|
break;
|
|
default:
|
|
errors.throwErr(errors.ERR_NOT_IMPLEMENTED, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _unpackData()
|
|
//
|
|
// Unpacks the packed data into a map of JavaScript values.
|
|
//---------------------------------------------------------------------------
|
|
_unpackData() {
|
|
const buf = new DbObjectPickleBuffer(this.packedData);
|
|
buf.readHeader(this);
|
|
this._unpackDataFromBuf(buf);
|
|
this.packedData = undefined;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _unpackDataFromBuf()
|
|
//
|
|
// Unpacks the data in the buffer into a map of JavaScript values.
|
|
//---------------------------------------------------------------------------
|
|
_unpackDataFromBuf(buf) {
|
|
let unpackedArray, unpackedAssocArray, assocIndex, unpackedAttrs;
|
|
const objType = this._objType;
|
|
if (objType.isCollection) {
|
|
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
|
|
unpackedAssocArray = new Map();
|
|
} else {
|
|
unpackedArray = [];
|
|
}
|
|
this.collectionFlags = buf.readUInt8();
|
|
const numElements = buf.readLength();
|
|
for (let i = 0; i < numElements; i++) {
|
|
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
|
|
assocIndex = buf.readUInt32BE();
|
|
}
|
|
const value = this._unpackValue(buf, objType.elementType,
|
|
objType.elementTypeClass);
|
|
if (objType.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
|
|
unpackedAssocArray.set(assocIndex, value);
|
|
} else {
|
|
unpackedArray.push(value);
|
|
}
|
|
}
|
|
} else {
|
|
unpackedAttrs = new Map();
|
|
for (const attr of objType.attributes) {
|
|
const value = this._unpackValue(buf, attr.type, attr.typeClass);
|
|
unpackedAttrs.set(attr.name, value);
|
|
}
|
|
}
|
|
this.unpackedAttrs = unpackedAttrs;
|
|
this.unpackedArray = unpackedArray;
|
|
this.unpackedAssocArray = unpackedAssocArray;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// _unpackValue()
|
|
//
|
|
// Unpacks a single value and returns it.
|
|
//---------------------------------------------------------------------------
|
|
_unpackValue(buf, type, typeClass) {
|
|
let isNull, obj, value;
|
|
switch (type) {
|
|
case types.DB_TYPE_NUMBER:
|
|
return buf.readOracleNumber();
|
|
case types.DB_TYPE_BINARY_INTEGER:
|
|
return buf.readBinaryInteger();
|
|
case types.DB_TYPE_VARCHAR:
|
|
case types.DB_TYPE_CHAR:
|
|
return buf.readStr(constants.CSFRM_IMPLICIT);
|
|
case types.DB_TYPE_NVARCHAR:
|
|
case types.DB_TYPE_NCHAR:
|
|
return buf.readStr(constants.CSFRM_NCHAR);
|
|
case types.DB_TYPE_RAW:
|
|
value = buf.readBytesWithLength();
|
|
if (value !== null)
|
|
value = Buffer.from(value);
|
|
return value;
|
|
case types.DB_TYPE_BINARY_DOUBLE:
|
|
return buf.readBinaryDouble();
|
|
case types.DB_TYPE_BINARY_FLOAT:
|
|
return buf.readBinaryFloat();
|
|
case types.DB_TYPE_DATE:
|
|
case types.DB_TYPE_TIMESTAMP:
|
|
return buf.readOracleDate(true);
|
|
case types.DB_TYPE_TIMESTAMP_LTZ:
|
|
case types.DB_TYPE_TIMESTAMP_TZ:
|
|
return buf.readOracleDate(false);
|
|
case types.DB_TYPE_BOOLEAN:
|
|
return buf.readBool();
|
|
case types.DB_TYPE_OBJECT:
|
|
case types.DB_TYPE_XMLTYPE:
|
|
isNull = buf.getIsAtomicNull();
|
|
if (isNull)
|
|
return null;
|
|
obj = new ThinDbObjectImpl(typeClass);
|
|
if (obj._objType.isXmlType) {
|
|
return readXML(obj._objType._connection, buf.readBytesWithLength());
|
|
}
|
|
if (obj._objType.isCollection || this._objType.isCollection) {
|
|
obj.packedData = Buffer.from(buf.readBytesWithLength());
|
|
} else {
|
|
obj._unpackDataFromBuf(buf);
|
|
}
|
|
return obj;
|
|
default:
|
|
errors.throwErr(errors.ERR_NOT_IMPLEMENTED, type);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// append()
|
|
//
|
|
// Appends an element to the collection.
|
|
//---------------------------------------------------------------------------
|
|
append(value) {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
const objType = this._objType;
|
|
if (objType.maxNumElements > 0 &&
|
|
this.unpackedArray.length >= objType.maxNumElements) {
|
|
errors.throwErr(errors.ERR_INVALID_COLL_INDEX_SET,
|
|
this.unpackedArray.length, 0, objType.maxNumElements - 1);
|
|
}
|
|
this.unpackedArray.push(value);
|
|
} else {
|
|
this._ensureAssocKeys();
|
|
let newIndex;
|
|
if (this.unpackedAssocKeys.length === 0) {
|
|
newIndex = 0;
|
|
} else {
|
|
const keyIndex = this.unpackedAssocKeys.length - 1;
|
|
newIndex = this.unpackedAssocKeys[keyIndex] + 1;
|
|
}
|
|
this.unpackedAssocArray.set(newIndex, value);
|
|
this.unpackedAssocKeys.push(newIndex);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// copy
|
|
//
|
|
// Creates and returns a copy of the ThinDBObjectImpl object. The copy is
|
|
// independent of the original object that was copied.
|
|
//---------------------------------------------------------------------------
|
|
copy() {
|
|
// We send in marshalled data of the original object to the constructor
|
|
// when we create the object copy
|
|
const newObjImpl = new ThinDbObjectImpl(this._objType, this._getPackedData());
|
|
|
|
// Set other properties
|
|
newObjImpl.toid = this.toid;
|
|
newObjImpl.flags = this.flags;
|
|
newObjImpl.imageFlags = this.imageFlags;
|
|
newObjImpl.imageVersion = this.imageVersion;
|
|
|
|
return newObjImpl;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// deleteElement()
|
|
//
|
|
// Deletes an element from a collection.
|
|
//---------------------------------------------------------------------------
|
|
deleteElement(index) {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
if (this._objType.collectionType == constants.TNS_OBJ_VARRAY) {
|
|
errors.throwErr(errors.ERR_DELETE_ELEMENTS_OF_VARRAY);
|
|
}
|
|
this.unpackedArray.splice(index, 1);
|
|
} else {
|
|
this.unpackedAssocKeys = undefined;
|
|
this.unpackedAssocArray.delete(index);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getAttrValue()
|
|
//
|
|
// Returns the value of the given attribute on the object.
|
|
//---------------------------------------------------------------------------
|
|
getAttrValue(attr) {
|
|
this._ensureUnpacked();
|
|
const value = this.unpackedAttrs.get(attr.name);
|
|
if (value === undefined)
|
|
return null;
|
|
return value;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getElement()
|
|
//
|
|
// Returns an element from the collection.
|
|
//---------------------------------------------------------------------------
|
|
getElement(index) {
|
|
let value;
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
value = this.unpackedArray[index];
|
|
} else {
|
|
value = this.unpackedAssocArray.get(index);
|
|
}
|
|
if (value === undefined) {
|
|
errors.throwErr(errors.ERR_INVALID_COLL_INDEX_GET, index);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getFirstIndex()
|
|
//
|
|
// Returns the first index in a collection.
|
|
//---------------------------------------------------------------------------
|
|
getFirstIndex() {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
return 0;
|
|
} else if (this.unpackedAssocArray) {
|
|
this._ensureAssocKeys();
|
|
return this.unpackedAssocKeys[0];
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getKeys()
|
|
//
|
|
// Returns the keys of the collection in a JavaScript array.
|
|
//---------------------------------------------------------------------------
|
|
getKeys() {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
return Array.from(this.unpackedArray.keys());
|
|
} else if (this.unpackedAssocArray) {
|
|
this._ensureAssocKeys();
|
|
return Array.from(this.unpackedAssocKeys);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getLastIndex()
|
|
//
|
|
// Returns the last index in a collection.
|
|
//---------------------------------------------------------------------------
|
|
getLastIndex() {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
if (this.unpackedArray.length > 0)
|
|
return this.unpackedArray.length - 1;
|
|
} else if (this.unpackedAssocArray) {
|
|
this._ensureAssocKeys();
|
|
return this.unpackedAssocKeys[this.unpackedAssocKeys.length - 1];
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getNextIndex()
|
|
//
|
|
// Returns the next index in a collection.
|
|
// For associative arrays indexed by integers, if the passed-in index
|
|
// parameter is not present, it will return the next higher index found
|
|
// in the associative array.
|
|
//---------------------------------------------------------------------------
|
|
getNextIndex(index) {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
if (index + 1 < this.unpackedArray.length) {
|
|
return index + 1;
|
|
}
|
|
} else if (this.unpackedAssocArray) {
|
|
this._ensureAssocKeys();
|
|
for (const key of this.unpackedAssocKeys) {
|
|
if (key > index)
|
|
return key;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getPrevIndex()
|
|
//
|
|
// Returns the previous index in a collection.
|
|
// For associative arrays indexed by integers, if the passed-in index
|
|
// parameter is not present, it will return the next lower index found
|
|
// in the associative array.
|
|
//---------------------------------------------------------------------------
|
|
getPrevIndex(index) {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
if (index > 0) {
|
|
return index - 1;
|
|
}
|
|
} else if (this.unpackedAssocArray) {
|
|
this._ensureAssocKeys();
|
|
let prev;
|
|
for (const key of this.unpackedAssocKeys) {
|
|
if (key >= index)
|
|
return prev;
|
|
prev = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getValues()
|
|
//
|
|
// Returns the values of the collection in a JavaScript array.
|
|
//---------------------------------------------------------------------------
|
|
getValues() {
|
|
const result = [];
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
return Array.from(this.unpackedArray);
|
|
} else if (this.unpackedAssocArray) {
|
|
this._ensureAssocKeys();
|
|
for (const key of this.unpackedAssocKeys) {
|
|
result.push(this.unpackedAssocArray.get(key));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// getLength
|
|
//
|
|
// Gets the size of the database object if it is a collection. Else returns
|
|
// undefined.
|
|
//---------------------------------------------------------------------------
|
|
getLength() {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray)
|
|
return this.unpackedArray.length;
|
|
if (this.unpackedAssocArray)
|
|
return this.unpackedAssocArray.size;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// hasElement()
|
|
//
|
|
// Returns whether an element exists at the given index.
|
|
//---------------------------------------------------------------------------
|
|
hasElement(index) {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
return (index >= 0 && index < this.unpackedArray.length);
|
|
}
|
|
return this.unpackedAssocArray.has(index);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// setAttrValue()
|
|
//
|
|
// Sets the value of the attribute on the object to the given value.
|
|
//---------------------------------------------------------------------------
|
|
setAttrValue(attr, value) {
|
|
this._ensureUnpacked();
|
|
this.unpackedAttrs.set(attr.name, value);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// setElement()
|
|
//
|
|
// Sets an entry in a collection that is indexed by integers.
|
|
//---------------------------------------------------------------------------
|
|
setElement(index, value) {
|
|
this._ensureUnpacked();
|
|
if (this.unpackedArray) {
|
|
const maxIndex = Math.max(this.unpackedArray.length - 1, 0);
|
|
if (index > maxIndex) {
|
|
errors.throwErr(errors.ERR_INVALID_COLL_INDEX_SET, index, 0, maxIndex);
|
|
}
|
|
this.unpackedArray[index] = value;
|
|
} else {
|
|
if (!this.unpackedAssocArray.has(index))
|
|
this.unpackedAssocKeys = undefined;
|
|
this.unpackedAssocArray.set(index, value);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// trim()
|
|
//
|
|
// Trim the specified number of elements from the end of the collection.
|
|
//---------------------------------------------------------------------------
|
|
trim(numToTrim) {
|
|
this._ensureUnpacked();
|
|
if (numToTrim > 0) {
|
|
this.unpackedArray = this.unpackedArray.slice(0,
|
|
this.unpackedArray.length - numToTrim);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// readXML()
|
|
//
|
|
// Decodes the raw Bytes to XML string or LOB object.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
function readXML(conn, buf) {
|
|
let colValue;
|
|
|
|
const xmlObj = new DbObjectPickleBuffer(buf);
|
|
const tempobj = {};
|
|
xmlObj.readHeader(tempobj);
|
|
xmlObj.skipBytes(1);
|
|
const xmlflag = xmlObj.readUInt32BE();
|
|
if (xmlflag & constants.TNS_XML_TYPE_FLAG_SKIP_NEXT_4) {
|
|
xmlObj.skipBytes(4);
|
|
}
|
|
const numBytesLeft = xmlObj.numBytesLeft();
|
|
const ptr = xmlObj.readBytes(numBytesLeft);
|
|
if (xmlflag & constants.TNS_XML_TYPE_STRING) {
|
|
colValue = ptr.toString();
|
|
} else if (xmlflag & constants.TNS_XML_TYPE_LOB) {
|
|
const lobImpl = new ThinLobImpl();
|
|
const locator = Buffer.from(ptr);
|
|
lobImpl.init(conn, locator, types.DB_TYPE_CLOB, 0, 0);
|
|
colValue = lobImpl;
|
|
} else {
|
|
// We only support String and Clob type.
|
|
errors.throwErr(errors.ERR_UNEXPECTED_XML_TYPE, xmlflag);
|
|
}
|
|
return colValue;
|
|
}
|
|
|
|
module.exports = { ThinDbObjectImpl, readXML };
|