oracle-nosqldb
Version:
Node.js driver for Oracle NoSQL Database
267 lines (238 loc) • 6.74 kB
JavaScript
/*-
* Copyright (c) 2018, 2024 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
'use strict';
const assert = require('assert');
const PackedInteger = require('./packed_integer');
const ResizableBuffer = require('./buffer');
const NoSQLProtocolError = require('../error').NoSQLProtocolError;
const stringToUTCDate = require('../utils').stringToUTCDate;
class DataReader {
constructor(buf) {
this._buf = (buf instanceof ResizableBuffer) ? buf :
new ResizableBuffer(buf);
this._off = 0;
}
_readByte() {
return this._buf.readInt8(this._off++);
}
_handleEOF(e, ...args) {
if (e.name == 'RangeError') {
let msg = 'End of stream reached';
if (args.length > 0) {
msg += ` while reading ${args[0]}`;
if (args.length > 1) {
msg += ` of length ${args[1]}`;
}
}
throw new NoSQLProtocolError(msg);
} else {
throw e;
}
}
_readBuffer(len) {
try {
const ret = this._buf.readBuffer(this._off, len);
this._off += len;
return ret;
} catch(e) {
this._handleEOF(e, 'binary', len);
}
}
get buffer() {
return this._buf;
}
get offset() {
return this._off;
}
set offset(val) {
assert(val >= 0);
if (val > this._buf.length) {
throw new NoSQLProtocolError(`End of stream reached: offset \
${val} is past length ${this._buf.length}`);
}
this._off = val;
}
/**
* Reads a packed integer from the buffer and returns it.
*
* @return the integer that was read
* @throws NoSQLError if the input format is invalid or end of input is
* reached
*/
readInt() {
try {
//We pass ResizableBuffer instead of Buffer so that EOF checking
//is performed when reading bytes.
let { value, off } = PackedInteger.readSortedInt(
this._buf, this._off);
this._off = off;
return value;
} catch(e) {
this._handleEOF(e, 'packed int');
}
}
/**
* Reads a packed long from the buffer and returns it.
*
* @param asBigInt Whether to read the value as bigint
* @return the long that was read
* @throws NoSQLError if the input format is invalid or end of input is
* reached
*/
readLong(asBigInt = false) {
try {
let { value, off } = PackedInteger.readSortedLong(
this._buf, this._off, asBigInt);
this._off = off;
return value;
} catch(e) {
this._handleEOF(e, 'packed long');
}
}
/**
* Reads a packed long from the buffer as bigint and returns it.
*
* @return bigint value of the long that was read
* @throws NoSQLError if the input format is invalid or end of input is
* reached
*/
readLongAsBigInt() {
return this._readLong(true);
}
/**
* Reads a string written by {@link #writeString}, using standard UTF-8
*
* @return a string or null
* @throws NoSQLError if the input format is invalid or end of input is
* reached
*/
readString() {
const len = this.readInt();
if (len < -1) {
throw new NoSQLProtocolError(`Invalid string length: ${len}`);
}
if (len == -1) {
return null;
}
if (len == 0) {
return '';
}
try {
const nextOff = this._off + len;
const ret = this._buf.slice(this._off, nextOff).toString('utf8');
this._off = nextOff;
return ret;
} catch(e) {
this._handleEOF(e, 'string', len);
}
}
readArray(readItem) {
const len = this.readInt();
if (len < -1) {
throw new NoSQLProtocolError(`Invalid array length: ${len}`);
}
if (len == -1) {
return null;
}
const a = new Array(len);
for(let i = 0; i < len; i++) {
a[i] = readItem();
}
return a;
}
readStringArray() {
return this.readArray(this.readString.bind(this));
}
/**
* Reads a possibly null binary as a {@link #readPackedInt
* sequence length} followed by the array contents.
*
* @return array the array or null
* @throws NoSQLError if the input format is invalid or end of input is
* reached
*/
readBinary() {
const len = this.readInt();
if (len < -1) {
throw new NoSQLProtocolError(`Invalid binary length: ${len}`);
}
if (len == -1) {
return null;
}
if (len == 0) {
return Buffer.allocUnsafe(0);
}
return this._readBuffer(len);
}
//Equivalent to readByteArrayWithInt() in BinaryProtocol.java
readBinary2() {
const len = this.readInt32BE();
if (len <= 0) {
return Buffer.allocUnsafe(0);
}
return this._readBuffer(len);
}
readIntArray() {
return this.readArray(this.readInt.bind(this));
}
readByte() {
try {
return this._readByte();
} catch(e) {
this._handleEOF(e, 'byte');
}
}
readBoolean() {
try {
return Boolean(this._readByte());
} catch(e) {
this._handleEOF(e, 'boolean');
}
}
readDouble() {
try {
const ret = this._buf.readDoubleBE(this._off);
this._off += 8;
return ret;
} catch(e) {
this._handleEOF(e, 'double');
}
}
readDate() {
const s = this.readString();
if (s === null) {
return null;
}
return stringToUTCDate(s);
}
readInt16BE() {
try {
const ret = this._buf.readInt16BE(this._off);
this._off += 2;
return ret;
} catch(e) {
this._handleEOF(e, 'short');
}
}
readInt32BE() {
try {
const ret = this._buf.readInt32BE(this._off);
this._off += 4;
return ret;
} catch(e) {
this._handleEOF(e, 'integer');
}
}
reset() {
this._off = 0;
return this;
}
toString(encoding = 'utf8') {
return this._buf.toString(encoding);
}
}
module.exports = DataReader;