From 61d42a84cc3253532a1c9582d9c060b3dbdf5a6b Mon Sep 17 00:00:00 2001 From: Sharad Chandran R Date: Thu, 5 Oct 2023 13:50:11 +0530 Subject: [PATCH] Internal code changes, example and test case additions --- doc/src/release_notes.rst | 2 + examples/dbconfig.js | 21 +- lib/connection.js | 2 +- lib/thin/protocol/buffer.js | 127 +++++------ lib/thin/protocol/oson.js | 23 +- lib/thin/protocol/packet.js | 11 +- lib/thin/protocol/utils.js | 2 +- lib/thin/sqlnet/packet.js | 2 +- test/list.txt | 49 +++- test/opts/.mocharc.yaml | 1 + test/sodaOpLock.js | 441 ++++++++++++++++++++++++++++++++++-- 11 files changed, 563 insertions(+), 118 deletions(-) diff --git a/doc/src/release_notes.rst b/doc/src/release_notes.rst index 2773ae577..ec944dc06 100644 --- a/doc/src/release_notes.rst +++ b/doc/src/release_notes.rst @@ -19,6 +19,8 @@ Common Changes Thin Mode Changes ++++++++++++++++++ +#) Internal performance optimizations for network buffer and packet handling + #) Ensure that the database port is passed as a number to the network connection. See `Issue #1600 `__ and `PR #1601 `__ diff --git a/examples/dbconfig.js b/examples/dbconfig.js index 82ce97d35..833846799 100644 --- a/examples/dbconfig.js +++ b/examples/dbconfig.js @@ -30,7 +30,7 @@ * to the database. Production applications should consider using * External Authentication to avoid hard coded credentials. * - * To create a database user see + * To create a database user, see * https://blogs.oracle.com/sql/post/how-to-create-users-grant-them-privileges-and-remove-them-in-oracle-database * * Applications can set the connectString value to an Easy Connect @@ -46,8 +46,8 @@ * Commonly just the host_name and service_name are needed * e.g. "localhost/orclpdb1" or "example.com/XEPDB1" * - * The Easy Connect syntax was enhanced in Oracle Database 19c to - * allow more options, refer to the documentation: + * The Easy Connect syntax supports lots of options. To know more, please + * refer to the latest Oracle documentation on Easy Connect syntax: * https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE * * If using a tnsnames.ora file, the file can be in a default @@ -66,17 +66,10 @@ * creating a pool should not be set when externalAuth is true. * * TROUBLESHOOTING - * Errors like: - * ORA-12541: TNS:no listener - * or - * ORA-12154: TNS:could not resolve the connect identifier specified - * indicate connectString is invalid. - * - * The error: - * ORA-12514: TNS:listener does not currently know of requested in connect descriptor - * indicates connectString is invalid. You are reaching a computer - * with Oracle installed but the service name isn't known. - * Use 'lsnrctl services' on the database server to find available services + * Refer to the Error Handling section in node-oracledb documentation + * to understand the different types of errors in both the Thin and Thick + * modes of node-oracledb: + * https://node-oracledb.readthedocs.io/en/latest/user_guide/exception_handling.html#errors-in-thin-and-thick-modes * *****************************************************************************/ diff --git a/lib/connection.js b/lib/connection.js index c110988e4..068656234 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -1074,7 +1074,7 @@ class Connection extends EventEmitter { // // Returns the health status of the connection. If this function returns // false, the caller should close the connection. - // --------------------------------------------------------------------------- + //--------------------------------------------------------------------------- isHealthy() { return (this._impl !== undefined && !this._closing && this._impl.isHealthy()); diff --git a/lib/thin/protocol/buffer.js b/lib/thin/protocol/buffer.js index e6c633961..561493011 100644 --- a/lib/thin/protocol/buffer.js +++ b/lib/thin/protocol/buffer.js @@ -49,7 +49,7 @@ class BaseBuffer { this.buf = Buffer.alloc(initializer); this.size = 0; this.maxSize = initializer; - } else { + } else if (initializer) { this.buf = initializer; this.size = this.maxSize = initializer.length; } @@ -548,11 +548,12 @@ class BaseBuffer { // remain in the buffer, the buffer is grown. //--------------------------------------------------------------------------- reserveBytes(numBytes) { - if (numBytes > this.numBytesLeft()) + if (numBytes > this.numBytesLeft()) { this._grow(this.pos + numBytes); - const buf = this.buf.subarray(this.pos, this.pos + numBytes); + } + const pos = this.pos; this.pos += numBytes; - return buf; + return pos; } //--------------------------------------------------------------------------- @@ -618,20 +619,20 @@ class BaseBuffer { //--------------------------------------------------------------------------- writeBinaryDouble(n) { this.writeUInt8(8); - const buf = this.reserveBytes(8); - buf.writeDoubleBE(n); - if ((buf[0] & 0x80) === 0) { - buf[0] |= 0x80; + const 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 - buf[0] ^= 0xff; - buf[1] ^= 0xff; - buf[2] ^= 0xff; - buf[3] ^= 0xff; - buf[4] ^= 0xff; - buf[5] ^= 0xff; - buf[6] ^= 0xff; - buf[7] ^= 0xff; + 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; } } @@ -642,16 +643,16 @@ class BaseBuffer { //--------------------------------------------------------------------------- writeBinaryFloat(n) { this.writeUInt8(4); - const buf = this.reserveBytes(4); - buf.writeFloatBE(n); - if ((buf[0] & 0x80) === 0) { - buf[0] |= 0x80; + const 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 - buf[0] ^= 0xff; - buf[1] ^= 0xff; - buf[2] ^= 0xff; - buf[3] ^= 0xff; + this.buf[pos] ^= 0xff; + this.buf[pos + 1] ^= 0xff; + this.buf[pos + 2] ^= 0xff; + this.buf[pos + 3] ^= 0xff; } } @@ -745,31 +746,31 @@ class BaseBuffer { if (writeLength) { this.writeUInt8(length); } - const ptr = this.reserveBytes(length); + const pos = this.reserveBytes(length); if (type === types.DB_TYPE_DATE || type == types.DB_TYPE_TIMESTAMP) { const year = date.getFullYear(); - ptr[0] = Math.trunc(year / 100) + 100; - ptr[1] = year % 100 + 100; - ptr[2] = date.getMonth() + 1; - ptr[3] = date.getDate(); - ptr[4] = date.getHours() + 1; - ptr[5] = date.getMinutes() + 1; - ptr[6] = date.getSeconds() + 1; + 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(); - ptr[0] = Math.trunc(year / 100) + 100; - ptr[1] = year % 100 + 100; - ptr[2] = date.getUTCMonth() + 1; - ptr[3] = date.getUTCDate(); - ptr[4] = date.getUTCHours() + 1; - ptr[5] = date.getUTCMinutes() + 1; - ptr[6] = date.getUTCSeconds() + 1; + 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) { - ptr.writeInt32BE(fsec, 7); + this.buf.writeInt32BE(fsec, pos + 7); if (length > 11) { - ptr[11] = constants.TZ_HOUR_OFFSET; - ptr[12] = constants.TZ_MINUTE_OFFSET; + this.buf[pos + 11] = constants.TZ_HOUR_OFFSET; + this.buf[pos + 12] = constants.TZ_MINUTE_OFFSET; } } } @@ -843,19 +844,19 @@ class BaseBuffer { } else if (value.length === 0 && exponent === 0) { exponentOnWire = 128; } - const buf = this.reserveBytes(numPairs + 2 + appendSentinel); - buf[0] = numPairs + 1 + appendSentinel; - buf[1] = exponentOnWire; - for (let i = 0, pos = 2; i < value.length; i += 2, pos++) { + 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) { - buf[pos] = 101 - base100Digit; + this.buf[pos++] = 101 - base100Digit; } else { - buf[pos] = base100Digit + 1; + this.buf[pos++] = base100Digit + 1; } } if (appendSentinel) { - buf[buf.length - 1] = 102; + this.buf[pos] = 102; } } @@ -898,8 +899,8 @@ class BaseBuffer { // Writes a signed 32-bit integer to the buffer in big endian order. //--------------------------------------------------------------------------- writeInt32BE(n) { - const buf = this.reserveBytes(4); - buf.writeInt32BE(n); + const pos = this.reserveBytes(4); + this.buf.writeInt32BE(n, pos); } //--------------------------------------------------------------------------- @@ -971,8 +972,8 @@ class BaseBuffer { // Writes an unsigned 8-bit integer to the buffer. //--------------------------------------------------------------------------- writeUInt8(n) { - const buf = this.reserveBytes(1); - buf[0] = n; + const pos = this.reserveBytes(1); + this.buf[pos] = n; } //--------------------------------------------------------------------------- @@ -981,8 +982,8 @@ class BaseBuffer { // Writes an unsigned 16-bit integer to the buffer in big endian order. //--------------------------------------------------------------------------- writeUInt16BE(n) { - const buf = this.reserveBytes(2); - buf.writeUInt16BE(n); + const pos = this.reserveBytes(2); + this.buf.writeUInt16BE(n, pos); } //--------------------------------------------------------------------------- @@ -991,8 +992,8 @@ class BaseBuffer { // Writes an unsigned 32-bit integer to the buffer in big endian order. //--------------------------------------------------------------------------- writeUInt32BE(n) { - const buf = this.reserveBytes(4); - buf.writeUInt32BE(n); + const pos = this.reserveBytes(4); + this.buf.writeUInt32BE(n, pos); } //--------------------------------------------------------------------------- @@ -1003,9 +1004,9 @@ class BaseBuffer { // higher order bits are simply written as 0. //--------------------------------------------------------------------------- writeUInt64BE(n) { - const buf = this.reserveBytes(8); - buf.writeUInt32BE(0); - buf.writeUInt32BE(n, 4); + const pos = this.reserveBytes(8); + this.buf.writeUInt32BE(0, pos); + this.buf.writeUInt32BE(n, pos + 4); } //--------------------------------------------------------------------------- @@ -1014,8 +1015,8 @@ class BaseBuffer { // Writes an unsigned 16-bit integer to the buffer in little endian order. //--------------------------------------------------------------------------- writeUInt16LE(n) { - const buf = this.reserveBytes(2); - buf.writeUInt16LE(n); + const pos = this.reserveBytes(2); + this.buf.writeUInt16LE(n, pos); } } @@ -1048,7 +1049,7 @@ class GrowableBuffer extends BaseBuffer { if (remainder > 0) { numBytes += (constants.BUFFER_CHUNK_SIZE - remainder); } - const buf = Buffer.alloc(numBytes); + const buf = Buffer.allocUnsafe(numBytes); this.buf.copy(buf); this.buf = buf; this.maxSize = this.size = numBytes; diff --git a/lib/thin/protocol/oson.js b/lib/thin/protocol/oson.js index 269a8833b..21eedb789 100644 --- a/lib/thin/protocol/oson.js +++ b/lib/thin/protocol/oson.js @@ -416,10 +416,11 @@ class OsonTreeSegment extends GrowableBuffer { //--------------------------------------------------------------------------- _encodeArray(value, fnamesSeg) { this._encodeContainer(constants.TNS_JSON_TYPE_ARRAY, value.length); - let offsetsBufPos = 0; - const offsetsBuf = this.reserveBytes(value.length * 4); + const len = value.length * 4; + const pos = this.reserveBytes(len); + let offsetsBufPos = pos; for (const element of value) { - offsetsBuf.writeUInt32BE(this.pos, offsetsBufPos); + this.buf.writeUInt32BE(this.pos, offsetsBufPos); offsetsBufPos += 4; this.encodeNode(element, fnamesSeg); } @@ -456,19 +457,21 @@ class OsonTreeSegment extends GrowableBuffer { _encodeObject(value, fnamesSeg) { const numChildren = value.values.length; this._encodeContainer(constants.TNS_JSON_TYPE_OBJECT, numChildren); - let fieldIdOffset = 0; - let valueOffset = numChildren * fnamesSeg.fieldIdSize; - const buf = this.reserveBytes(numChildren * (fnamesSeg.fieldIdSize + 4)); + const len = numChildren * (fnamesSeg.fieldIdSize + 4); + const pos = this.reserveBytes(len); + let fieldIdOffset = pos; + let valueOffset = pos + (numChildren * fnamesSeg.fieldIdSize); + for (let i = 0; i < value.fields.length; i++) { const fieldName = fnamesSeg.fieldNamesMap.get(value.fields[i]); if (fnamesSeg.fieldIdSize == 1) { - buf[fieldIdOffset] = fieldName.fieldId; + this.buf[fieldIdOffset] = fieldName.fieldId; } else if (fnamesSeg.fieldIdSize == 2) { - buf.writeUInt16BE(fieldName.fieldId, fieldIdOffset); + this.buf.writeUInt16BE(fieldName.fieldId, fieldIdOffset); } else { - buf.writeUInt32BE(fieldName.fieldId, fieldIdOffset); + this.buf.writeUInt32BE(fieldName.fieldId, fieldIdOffset); } - buf.writeUInt32BE(this.pos, valueOffset); + this.buf.writeUInt32BE(this.pos, valueOffset); fieldIdOffset += fnamesSeg.fieldIdSize; valueOffset += 4; this.encodeNode(value.values[i], fnamesSeg); diff --git a/lib/thin/protocol/packet.js b/lib/thin/protocol/packet.js index 735bc22cc..eaae739e5 100644 --- a/lib/thin/protocol/packet.js +++ b/lib/thin/protocol/packet.js @@ -35,7 +35,6 @@ const errors = require("../../errors.js"); const TNS_BASE64_ALPHABET_ARRAY = Buffer.from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 'utf8'); - /** * Class used for byte chunks used in the ChunkedBytesBuffer. */ @@ -52,7 +51,7 @@ class BytesChunk { if (remainder > 0) { this.allocLen += (constants.CHUNKED_BYTES_CHUNK_SIZE - remainder); } - this.buf = Buffer.alloc(this.allocLen); + this.buf = Buffer.allocUnsafe(this.allocLen); this.actualLen = 0; } @@ -133,6 +132,7 @@ class ChunkedBytesBuffer { * * @class ReadPacket */ + class ReadPacket extends BaseBuffer { /** @@ -140,8 +140,9 @@ class ReadPacket extends BaseBuffer { * @param {Object} adapter used for sending/receiving data * @param {Object} capabilities */ + constructor(nsi, caps) { - super(nsi.sAtts.sdu); + super(); this.nsi = nsi; this.caps = caps; this.chunkedBytesBuf = new ChunkedBytesBuffer(); @@ -228,7 +229,7 @@ class ReadPacket extends BaseBuffer { if (inChunkedRead) { buf = this.chunkedBytesBuf.getBuf(numBytes); } else { - buf = Buffer.alloc(numBytes); + buf = Buffer.allocUnsafe(numBytes); } // copy the bytes to the buffer from the remainder of this packet @@ -341,7 +342,7 @@ class ReadPacket extends BaseBuffer { outputLen += 3; } - const outputValue = Buffer.alloc(outputLen); + const outputValue = Buffer.allocUnsafe(outputLen); inputLen -= 1; outputValue[0] = 42; outputOffset += 1; diff --git a/lib/thin/protocol/utils.js b/lib/thin/protocol/utils.js index c70baf26a..211d4c61c 100644 --- a/lib/thin/protocol/utils.js +++ b/lib/thin/protocol/utils.js @@ -43,7 +43,7 @@ function _convertBase64(result, value, size, offset) { function encodeRowID(rowID) { let offset = 0; if (rowID.rba !== 0 || rowID.partitionID !== 0 || rowID.blockNum !== 0 || rowID.slotNum != 0) { - const result = Buffer.alloc(constants.TNS_MAX_ROWID_LENGTH); + const result = Buffer.allocUnsafe(constants.TNS_MAX_ROWID_LENGTH); offset = _convertBase64(result, rowID.rba, 6, offset); offset = _convertBase64(result, rowID.partitionID, 3, offset); offset = _convertBase64(result, rowID.blockNum, 6, offset); diff --git a/lib/thin/sqlnet/packet.js b/lib/thin/sqlnet/packet.js index 73ac20f94..24fb3c6a1 100644 --- a/lib/thin/sqlnet/packet.js +++ b/lib/thin/sqlnet/packet.js @@ -299,7 +299,7 @@ function RedirectPacket(packet) { */ function MarkerPacket(isLargeSDU) { this.len = constants.NSPMKDAT + 1; /* Packet length */ - this.buf = Buffer.alloc(constants.NSPMKDAT + 1); /* Packet Buffer */ + this.buf = Buffer.allocUnsafe(constants.NSPMKDAT + 1); /* Packet Buffer */ if (isLargeSDU) { this.buf.writeUInt32BE(this.len, constants.NSPHDLEN); diff --git a/test/list.txt b/test/list.txt index d1a0ec05b..bc6c3a04d 100755 --- a/test/list.txt +++ b/test/list.txt @@ -5510,8 +5510,22 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true 286.5 listIndexes after 2-createIndex 2-drop index 286.6 listIndexes after 3-createIndex -287. sodaOperationsLock.js - 287.1 lock on sodaOperations +287. sodaOpLock.js + 287.1 lock on document with multiple connections + 287.2 lock on fetching a single document + 287.3 lock on fetching multiple documents + 287.4 lock on insertOneAndGet() + 287.5 lock on filter() + 287.6 lock on key() + 287.7 lock on hint() + 287.8 lock on count() + 287.9 lock on keys().count() + 287.10 lock on getCursor() + 287.11 lock on skip().getCursor() + 287.12 lock on getDocuments() + 287.13 lock on getOne() + 287.14 lock on remove() + 287.15 lock on limit() 288. embedded_oid.js 288.1 Test embedded oid normal operations @@ -5544,3 +5558,34 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true 289.8 binds between comments 289.9 non-ascii character in the bind name 289.10 apostrophe in single line comment + +290. dbObject20.js + 290.1 db Object tests with NCHAR datatype + 290.1.1 insert an object with numeric/string values in NCHAR datatype + 290.1.2 insert an object with null string values + 290.1.3 insert an object with undefined string values + 290.1.4 insert an empty object - no attributes + 290.1.5 insert data via binding by object + 290.1.6 insert multiple rows using executeMany() with inferred data type + 290.1.7 insert multiple rows using executeMany() with explicit data type + 290.1.8 call procedure with 2 OUT binds of DbObject + 290.2 db Object tests with NVARCHAR2 datatype + 290.2.1 insert an object with numeric/string values in NVARCHAR2 datatype + 290.2.2 insert an object with null string values + 290.2.3 insert an object with undefined string values + 290.2.4 insert an empty object - no attributes + 290.2.5 insert data via binding by object + 290.2.6 insert multiple rows using executeMany() with inferred data type + 290.2.7 insert multiple rows using executeMany() with explicit data type + 290.2.8 call procedure with 2 OUT binds of DbObject + 290.3 db Object tests with RAW datatype + 290.3.1 insert an object with numeric/string values in RAW datatype + 290.3.2 insert an object with null string values + 290.3.3 insert an object with undefined string values + 290.3.4 insert an empty object - no attributes + 290.3.5 insert data via binding by object + 290.3.6 insert multiple rows using executeMany() with inferred data type + 290.3.7 insert multiple rows using executeMany() with explicit data type + 290.3.8 call procedure with 2 OUT binds of DbObject + 290.3.9 insert an object with buffer value with size 10 + 290.3.10 insert an object with buffer value with size 100 diff --git a/test/opts/.mocharc.yaml b/test/opts/.mocharc.yaml index 20f54de8b..e11d8502d 100644 --- a/test/opts/.mocharc.yaml +++ b/test/opts/.mocharc.yaml @@ -272,3 +272,4 @@ spec: - test/sodaOpLock.js - test/embedded_oid.js - test/sqlParser.js + - test/dbObject20.js diff --git a/test/sodaOpLock.js b/test/sodaOpLock.js index 15ddd23ea..754858cd1 100644 --- a/test/sodaOpLock.js +++ b/test/sodaOpLock.js @@ -23,25 +23,24 @@ * limitations under the License. * * NAME - * 287. sodaOperationsLock.js + * 287. sodaOpLock.js * * DESCRIPTION - * Test the fetch array size for fetching documents from a collection. + * Test the non-terminal lock() method on soda-Operation. * *****************************************************************************/ 'use strict'; +const assert = require('assert'); const oracledb = require('oracledb'); const dbConfig = require('./dbconfig.js'); const sodaUtil = require('./sodaUtil.js'); const testsUtil = require('./testsUtil.js'); +const t_contents = sodaUtil.t_contents; -describe('287. sodaOperationsLock.js', () => { - let conn, soda, coll; - - const collectionName = 'nodb_collection_286_1'; - +describe('287. sodaOpLock.js', () => { + let conn, isRunnable; before(async function() { const isSodaRunnable = await testsUtil.isSodaRunnable(); const clientVersion = testsUtil.getClientVersion(); @@ -52,29 +51,429 @@ describe('287. sodaOperationsLock.js', () => { } else { isClientOK = true; } - const isRunnable = isClientOK && isSodaRunnable; + isRunnable = isClientOK && isSodaRunnable; if (!isRunnable) { this.skip(); } - await sodaUtil.cleanup(); conn = await oracledb.getConnection(dbConfig); - soda = conn.getSodaDatabase(); - coll = await soda.createCollection(collectionName); - await conn.commit(); - }); // before + // Auto commit should not be on for testing lock() + oracledb.autoCommit = false; + }); // before - after(async () => { - if (conn) { - ////const result = await coll.drop(); - ////assert.strictEqual(result.dropped, true); - await conn.close(); + after(async function() { + if (!isRunnable) { + return; } + await conn.close(); }); - it('287.1 lock on sodaOperations', () => { - coll.find().lock(); - }); + it('287.1 lock on document with multiple connections', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_1"); + + const myKeys = []; + for (let i = 0; i < t_contents.length; i++) { + const content = t_contents[i]; + const doc = await collection.insertOneAndGet(content); + myKeys[i] = doc.key; + } + + await conn.commit(); + + // lock and Fetch back + const outDocuments = await collection.find().lock().getDocuments(); + const contents = []; + for (let i = 0; i < outDocuments.length; i++) { + contents[i] = outDocuments[i].getContent(); + testsUtil.removeID(contents[i]); + testsUtil.removeID(t_contents); + testsUtil.assertOneOf(t_contents, contents[i]); + } + + // create another connection + const conn1 = await oracledb.getConnection(dbConfig); + const soda1 = await conn1.getSodaDatabase(); + const collection1 = await soda1.createCollection("soda_lock_test_287_1"); + + // Replace operation should not succeed until a commit is performed. + const inContent1 = { id: 2000, name: "Paul", office: "Singapore" }; + + async function delayedCommit() { + await new Promise((resolve) => setTimeout(resolve, 250)); + await conn.commit(); + } + const operations = [ + collection1.find().key(myKeys[1]).replaceOne(inContent1), + delayedCommit() + ]; + + const [replaceResult] = await Promise.all(operations); + + assert.strictEqual(replaceResult.replaced, true); + + let res = await collection1.find().key(myKeys[1]).replaceOne(inContent1); + assert.strictEqual(res.replaced, true); + + await conn1.close(); + + res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.1 + + it('287.2 lock on fetching a single document', async () => { + const sd = conn.getSodaDatabase(); + const collName = "soda_lock_test_287_2"; + const coll = await sd.createCollection(collName); + + // Insert a new document + const myContent = { name: "Sally", address: {city: "Melbourne"} }; + await coll.insertOne(myContent); + + // Lock all documents + const docs = await coll.find().lock().getDocuments(); + + await docs.forEach(function(element) { + const content = element.getContent(); + assert.strictEqual(content.name, myContent.name); + assert.strictEqual(content.address.city, myContent.address.city); + }); + + assert.strictEqual(docs.length, 1); + await conn.commit(); + + const res = await coll.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.2 + + it('287.3 lock on fetching multiple documents', async () => { + const sd = conn.getSodaDatabase(); + const collectionName = 'soda_lock_test_287_3'; + const coll = await sd.createCollection(collectionName); + + const myContents = [ + { name: "Sally", address: {city: "Melbourne"} }, + { name: "John", address: {city: "Bangalore"} }, + { name: "Milan", address: {city: "Shenzhen"} } + ]; + + await Promise.all( + myContents.map(function(con) { + return coll.insertOne(con); + }) + ); + + const docs = await coll.find().lock().getDocuments(); + assert.strictEqual(docs.length, 3); + + await docs.forEach(function(element) { + const content = element.getContent(); + myContents.includes(content); + }); + + await conn.commit(); + const res = await coll.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.3 + + it('287.4 lock on insertOneAndGet()', async () => { + const sd = conn.getSodaDatabase(); + const collectionName = 'soda_lock_test_287_4'; + const coll = await sd.createCollection(collectionName); + + // Insert a new document + const testContent = { + name: "Steven Gomes", + address: {city: "Los Angeles", country: "US"}, + company: "Oracle Corporation", + manager: null, + VP: "Bruce" + }; + + const myDoc = await coll.insertOneAndGet(testContent); + const myKey = myDoc.key; + assert(myKey); + assert.strictEqual(typeof (myKey), "string"); + assert.strictEqual(typeof (myDoc), "object"); + + const content1 = myDoc.getContent(); + assert.ifError(content1); + + // Fetch it back + const doc2 = await coll.find().key(myKey).lock().getOne(); + const content2 = doc2.getContent(); + assert.strictEqual(content2.name, testContent.name); + assert.strictEqual(content2.company, testContent.company); + assert.strictEqual(content2.manager, null); + + await conn.commit(); + const res = await coll.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.4 + + it('287.5 lock on filter()', async function() { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_5"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + await assert.rejects( + async () => await collection.find() + .lock().filter({ "office": {"$like": "Shenzhen"} }) + .count(), + /ORA-40821:/ //ORA-40821: LOCK attribute cannot be set for count operation + ); + }); // 287.5 + + it('287.6 lock on key()', async function() { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_6"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + // Insert another document + const content1 = { id: 1008, name: "Justin", office: "Shenzhen" }; + const doc1 = await collection.insertOneAndGet(content1); + const key1 = doc1.key; + assert.strictEqual(typeof (key1), "string"); + + // Fetch it back + const doc2 = await collection.find().lock().key(key1).getOne(); + const content2 = doc2.getContent(); + testsUtil.removeID(content1); + testsUtil.removeID(content2); + assert.deepStrictEqual(content2, content1); + + const empInShenzhen = await collection.find() + .filter({ "office": {"$like": "Shenzhen"} }) + .count(); + assert.strictEqual(empInShenzhen.count, 3); + + await conn.commit(); + + const res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.6 + + it("287.7 lock on hint()", async function() { + /* + The SODA hint is available with Oracle Client 21.3 and + in 19 from 19.11 + */ + const clientVersion = testsUtil.getClientVersion(); + if (clientVersion < 2103000000) { + if (clientVersion < 1911000000 || clientVersion >= 2000000000) { + this.skip(); + } + } + + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_7"); + + const options = {hint: "MONITOR"}; + for (let i = 0; i < t_contents.length; i++) { + const content = t_contents[i]; + await collection.insertOneAndGet(content, options); + } + + // Fetch back + await collection + .find().lock() + .hint("MONITOR"); + + await conn.commit(); + + const res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.7 + + it('287.8 lock on count()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_8"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + // Fetch back + await assert.rejects( + async () => await collection.find().lock().count(), + /ORA-40821:/ //ORA-40821: LOCK attribute cannot be set for count operation + ); + }); // 287.8 + + it('287.9 lock on keys().count()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_9"); + + const myKeys = []; + for (let i = 0; i < t_contents.length; i++) { + const content = t_contents[i]; + const doc = await collection.insertOneAndGet(content); + myKeys[i] = doc.key; + } + + // Fetch back + const keysToCount = [ myKeys[2], myKeys[3] ]; + await assert.rejects( + async () => await collection.find().lock().keys(keysToCount).count(), + /ORA-40821:/ //ORA-40821: LOCK attribute cannot be set for count operation + ); + }); // 287.9 + + it('287.10 lock on getCursor()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_10"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + // Fetch back + const docCursor = await collection.find().lock().getCursor(); + + const myContents = []; + let hasNext = true; + let myDocument; + for (let i = 0; hasNext; i++) { + myDocument = await docCursor.getNext(); + if (!myDocument) { + hasNext = false; + } else { + myContents[i] = myDocument.getContent(); + (t_contents).includes(myContents[i]); + } + } + + assert.strictEqual(myContents.length, t_contents.length); + + await docCursor.close(); + + await conn.commit(); + + const res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.10 + + it('287.11 lock on skip().getCursor()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_11"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + // Fetch back + const numberToSkip = 3; + await assert.rejects( + async () => await collection.find().lock().skip(numberToSkip).getCursor(), + /ORA-40820:/ //ORA-40820: Cannot set LOCK attribute on the operation if SKIP or LIMIT attributes are set + ); + }); // 287.11 + + it('287.12 lock on getDocuments()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_12"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + // Fetch back + const documents = await collection.find().lock().getDocuments(); + + // Get contents + const myContents = []; + for (let i = 0; i < documents.length; i++) { + myContents[i] = documents[i].getContent(); + (t_contents).includes(myContents[i]); + } + + await conn.commit(); + + const res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.12 + + it('287.13 lock on getOne()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_13"); + + const myKeys = []; + for (let i = 0; i < t_contents.length; i++) { + const content = t_contents[i]; + const doc = await collection.insertOneAndGet(content); + myKeys[i] = doc.key; + } + + // Fetch back + const document = await collection.find().lock().key(myKeys[1]).getOne(); + const content = document.getContent(); + (t_contents).includes(content); + + await conn.commit(); + const res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.13 + + it('287.14 lock on remove()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_14"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + + // Fetch back + const result = await collection + .find() + .filter({ "office": {"$like": "Shenzhen"} }) + .remove(); + + assert.strictEqual(result.count, 2); + + const remainingLength = await collection.find().count(); + assert.strictEqual(remainingLength.count, (t_contents.length - result.count)); + + await conn.commit(); + const res = await collection.drop(); + assert.strictEqual(res.dropped, true); + }); // 287.14 + + it('287.15 lock on limit()', async () => { + const soda = conn.getSodaDatabase(); + const collection = await soda.createCollection("soda_lock_test_287_15"); + + await Promise.all( + t_contents.map(function(content) { + return collection.insertOne(content); + }) + ); + // Fetch back + const limitNumbers = 3; + await assert.rejects( + async () => await collection.find().lock().limit(limitNumbers).getCursor(), + /ORA-40820:/ //ORA-40820: Cannot set LOCK attribute on the operation if SKIP or LIMIT attributes are set + ); + }); // 287.15 });