oracledb 6.9.0 → 6.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +2 -2
  2. package/build/Release/oracledb-6.10.0-darwin-arm64.node +0 -0
  3. package/build/Release/oracledb-6.10.0-darwin-arm64.node-buildinfo.txt +1 -0
  4. package/build/Release/oracledb-6.10.0-darwin-x64.node +0 -0
  5. package/build/Release/oracledb-6.10.0-darwin-x64.node-buildinfo.txt +1 -0
  6. package/build/Release/oracledb-6.10.0-js-buildinfo.txt +1 -0
  7. package/build/Release/oracledb-6.10.0-linux-arm64.node +0 -0
  8. package/build/Release/oracledb-6.10.0-linux-arm64.node-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-6.10.0-linux-x64.node +0 -0
  10. package/build/Release/oracledb-6.10.0-linux-x64.node-buildinfo.txt +1 -0
  11. package/build/Release/oracledb-6.10.0-win32-x64.node +0 -0
  12. package/build/Release/oracledb-6.10.0-win32-x64.node-buildinfo.txt +1 -0
  13. package/lib/aqDeqOptions.js +18 -1
  14. package/lib/aqQueue.js +10 -6
  15. package/lib/connection.js +6 -2
  16. package/lib/dbObject.js +25 -4
  17. package/lib/errors.js +14 -4
  18. package/lib/impl/aqDeqOptions.js +10 -1
  19. package/lib/impl/datahandlers/buffer.js +49 -0
  20. package/lib/impl/datahandlers/oson.js +7 -2
  21. package/lib/impl/sodaDocument.js +5 -14
  22. package/lib/oracledb.js +23 -14
  23. package/lib/pool.js +187 -146
  24. package/lib/poolStatistics.js +1 -1
  25. package/lib/sodaCollection.js +12 -8
  26. package/lib/sodaDatabase.js +35 -8
  27. package/lib/sodaDocument.js +24 -6
  28. package/lib/sodaOperation.js +7 -6
  29. package/lib/thin/aq.js +475 -0
  30. package/lib/thin/connection.js +54 -4
  31. package/lib/thin/protocol/capabilities.js +3 -1
  32. package/lib/thin/protocol/constants.js +61 -0
  33. package/lib/thin/protocol/encryptDecrypt.js +2 -2
  34. package/lib/thin/protocol/messages/aqArray.js +238 -0
  35. package/lib/thin/protocol/messages/aqBase.js +305 -0
  36. package/lib/thin/protocol/messages/aqDeq.js +164 -0
  37. package/lib/thin/protocol/messages/aqEnq.js +135 -0
  38. package/lib/thin/protocol/messages/auth.js +12 -27
  39. package/lib/thin/protocol/messages/withData.js +3 -3
  40. package/lib/thin/protocol/packet.js +6 -5
  41. package/lib/thin/sqlnet/networkSession.js +1 -1
  42. package/lib/types.js +6 -6
  43. package/lib/util.js +0 -13
  44. package/lib/version.js +1 -1
  45. package/package.json +1 -1
  46. package/plugins/configProviders/azure/index.js +3 -0
  47. package/build/Release/oracledb-6.9.0-darwin-arm64.node +0 -0
  48. package/build/Release/oracledb-6.9.0-darwin-arm64.node-buildinfo.txt +0 -1
  49. package/build/Release/oracledb-6.9.0-darwin-x64.node +0 -0
  50. package/build/Release/oracledb-6.9.0-darwin-x64.node-buildinfo.txt +0 -1
  51. package/build/Release/oracledb-6.9.0-js-buildinfo.txt +0 -1
  52. package/build/Release/oracledb-6.9.0-linux-arm64.node +0 -0
  53. package/build/Release/oracledb-6.9.0-linux-arm64.node-buildinfo.txt +0 -1
  54. package/build/Release/oracledb-6.9.0-linux-x64.node +0 -0
  55. package/build/Release/oracledb-6.9.0-linux-x64.node-buildinfo.txt +0 -1
  56. package/build/Release/oracledb-6.9.0-win32-x64.node +0 -0
  57. package/build/Release/oracledb-6.9.0-win32-x64.node-buildinfo.txt +0 -1
package/lib/pool.js CHANGED
@@ -35,7 +35,6 @@ const nodbUtil = require('./util.js');
35
35
  const impl = require('./impl');
36
36
  const PoolStatistics = require('./poolStatistics.js');
37
37
 
38
-
39
38
  class Pool extends EventEmitter {
40
39
 
41
40
  constructor() {
@@ -46,8 +45,12 @@ class Pool extends EventEmitter {
46
45
  this._enableStatistics = false;
47
46
  this._timeOfReset = this._createdDate = Date.now();
48
47
  this._sessionCallback = undefined;
49
- this._connRequestQueue = [];
48
+ this._pendingRequestQueue = new Set();
50
49
  this._connectionClass = settings.connectionClass;
50
+ this._bgRequestWaiter = null;
51
+ this._bgRequestId = 0;
52
+ this._processing = 0;
53
+ this._ensureNoPendingRequests = null;
51
54
  }
52
55
 
53
56
  //---------------------------------------------------------------------------
@@ -68,36 +71,6 @@ class Pool extends EventEmitter {
68
71
  }
69
72
  }
70
73
 
71
- //---------------------------------------------------------------------------
72
- // _checkRequestQueue()
73
- //
74
- // When a connection is returned to the pool, this method is called (via an
75
- // event handler) to determine when requests for connections should be
76
- // resumed and cancels any timeout that may have been associated with the
77
- // request. This method is also called from reconfigure() so that waiting
78
- // connection requests can be processed. Note the use of a local variable for
79
- // the number of connections out. This is because the connection requests will
80
- // not resume until after the loop is finished, and therefore the number of
81
- // connections the pool thinks is out will not be incremented.
82
- //---------------------------------------------------------------------------
83
- _checkRequestQueue() {
84
- let connectionsOut = this._connectionsOut;
85
- while (this._connRequestQueue.length > 0 && connectionsOut < this.poolMax) {
86
- connectionsOut += 1;
87
- const payload = this._connRequestQueue.shift();
88
- if (this._enableStatistics) {
89
- this._totalRequestsDequeued += 1;
90
- this._updateWaitStatistics(payload);
91
- }
92
- if (payload.timeoutHandle) {
93
- clearTimeout(payload.timeoutHandle);
94
- }
95
- // inform the waiter that processing can continue
96
- payload.resolve();
97
- }
98
- }
99
-
100
- //---------------------------------------------------------------------------
101
74
  // _enableStats (DEPRECATED)
102
75
  //
103
76
  // Property for whether statistics are enabled on the pool.
@@ -118,7 +91,7 @@ class Pool extends EventEmitter {
118
91
  this._totalFailedRequests = 0;
119
92
  this._totalRequestsRejected = 0;
120
93
  this._totalRequestTimeouts = 0;
121
- this._maximumQueueLength = this._connRequestQueue.length;
94
+ this._maximumQueueLength = this._pendingRequestQueue.size;
122
95
  this._totalTimeInQueue = 0;
123
96
  this._minTimeInQueue = 0;
124
97
  this._maxTimeInQueue = 0;
@@ -143,9 +116,7 @@ class Pool extends EventEmitter {
143
116
  this._status = constants.POOL_STATUS_OPEN;
144
117
  this._connectionsOut = 0;
145
118
  this._poolAlias = poolAlias;
146
-
147
- // register event handler for when request queue should be checked
148
- this.on('_checkRequestQueue', this._checkRequestQueue);
119
+ this._bgRequestHandler();
149
120
 
150
121
  this._resetStatistics();
151
122
 
@@ -157,8 +128,8 @@ class Pool extends EventEmitter {
157
128
  // Update pool wait statistics after a connect request has spent some time in
158
129
  // the queue.
159
130
  //---------------------------------------------------------------------------
160
- _updateWaitStatistics(payload) {
161
- const waitTime = Date.now() - payload.enqueuedTime;
131
+ _updateWaitStatistics(request) {
132
+ const waitTime = Date.now() - request.enqueuedTime;
162
133
  this._totalTimeInQueue += waitTime;
163
134
  if (this._minTimeInQueue === 0) {
164
135
  this._minTimeInQueue = waitTime;
@@ -280,10 +251,12 @@ class Pool extends EventEmitter {
280
251
  // if the pool is draining/reconfiguring/closed, throw an appropriate error
281
252
  this._checkPoolOpen(false);
282
253
 
254
+ if (forceClose)
255
+ this._status = constants.POOL_STATUS_DRAINING;
256
+
283
257
  // wait for the pool to become empty or for the drain timeout to expire
284
258
  // (whichever comes first)
285
259
  if (drainTime > 0) {
286
- this._status = constants.POOL_STATUS_DRAINING;
287
260
  await new Promise(resolve => {
288
261
  const timeout = setTimeout(() => {
289
262
  this.removeAllListeners('_allCheckedIn');
@@ -302,9 +275,19 @@ class Pool extends EventEmitter {
302
275
  errors.throwErr(errors.ERR_POOL_HAS_BUSY_CONNECTIONS);
303
276
  }
304
277
 
278
+ // ensure no connection requests are being processed before proceeding to
279
+ // close the pool (required for thick mode)
280
+ if (this._processing != 0)
281
+ await new Promise(resolve => this._ensureNoPendingRequests = resolve);
282
+
305
283
  // close the pool
306
284
  await this._impl.close();
307
285
  this._status = constants.POOL_STATUS_CLOSED;
286
+
287
+ // stop the _bgRequestWaiter waiting in background
288
+ if (this._bgRequestWaiter)
289
+ this._bgRequestWaiter();
290
+
308
291
  this.emit('_afterPoolClose');
309
292
 
310
293
  }
@@ -391,7 +374,6 @@ class Pool extends EventEmitter {
391
374
  // queueTimeout milliseconds.
392
375
  //---------------------------------------------------------------------------
393
376
  async getConnection(a1) {
394
- let poolMax;
395
377
  let options = {};
396
378
 
397
379
  // check arguments
@@ -412,131 +394,191 @@ class Pool extends EventEmitter {
412
394
  this._totalConnectionRequests += 1;
413
395
  }
414
396
 
415
- // getting the poolMax setting on the pool may fail if the pool is no
416
- // longer valid
417
- try {
418
- poolMax = this.poolMax;
419
- } catch (err) {
420
- if (this._enableStatistics) {
421
- this._totalFailedRequests += 1;
422
- }
423
- throw err;
424
- }
425
-
426
- while (this._connectionsOut >= poolMax ||
427
- this.status === constants.POOL_STATUS_RECONFIGURING) {
428
-
429
- // when the queue is huge, throw error early without waiting for queue
430
- // timeout
431
- if (this._connRequestQueue.length >= this._queueMax &&
432
- this._queueMax >= 0) {
433
- if (this._enableStatistics) {
434
- this._totalRequestsRejected += 1;
397
+ if (this._bgRequestId == Number.MAX_SAFE_INTEGER)
398
+ this._bgRequestId = 0;
399
+
400
+ const request = {
401
+ id: this._bgRequestId++,
402
+ options: options,
403
+ conn: null,
404
+ isWaiting: true,
405
+ error: null,
406
+ timeoutHandle: null,
407
+ enqueuedTime: Date.now(),
408
+ wasQueued: false
409
+ };
410
+
411
+ // This promise acquires a connection from pool
412
+ const promiseToAcquire = new Promise((resolve, reject) => {
413
+ request.resolve = resolve;
414
+ request.reject = reject;
415
+
416
+ // If we haven't reached poolMax we shouldn't add to queue(honor queueMax)
417
+ if (this._connectionsOut < this.poolMax) {
418
+ this._processRequest(request);
419
+ } else {
420
+ // Ensure queueMax is not exceeded
421
+ if (this._pendingRequestQueue.size == this._queueMax) {
422
+ if (this._enableStatistics)
423
+ this._totalRequestsRejected += 1;
424
+ return reject(errors.getErr(errors.ERR_QUEUE_MAX_EXCEEDED,
425
+ this._queueMax));
435
426
  }
436
- errors.throwErr(errors.ERR_QUEUE_MAX_EXCEEDED, this._queueMax);
437
- }
438
427
 
439
- // if too many connections are out, wait until room is made available or
440
- // the queue timeout expires
441
- await new Promise((resolve, reject) => {
442
-
443
- // set up a payload which will be added to the queue for processing
444
- const payload = { resolve: resolve, reject: reject };
445
-
446
- // if using a queue timeout, establish the timeout so that when it
447
- // expires the payload will be removed from the queue and an exception
448
- // thrown
449
- if (this._queueTimeout !== 0) {
450
- payload.timeoutHandle = setTimeout(() => {
451
- const ix = this._connRequestQueue.indexOf(payload);
452
- if (ix >= 0) {
453
- this._connRequestQueue.splice(ix, 1);
454
- }
455
- if (this._enableStatistics) {
456
- this._totalRequestTimeouts += 1;
457
- this._updateWaitStatistics(payload);
458
- }
459
- try {
460
- errors.throwErr(errors.ERR_CONN_REQUEST_TIMEOUT,
461
- this._queueTimeout);
462
- } catch (err) {
463
- reject(err);
464
- }
465
- }, this._queueTimeout);
466
- }
428
+ // Queueing our request
429
+ request.wasQueued = true;
430
+ this._pendingRequestQueue.add(request);
467
431
 
468
- // add payload to the queue
469
- this._connRequestQueue.push(payload);
470
432
  if (this._enableStatistics) {
471
- payload.enqueuedTime = Date.now();
472
433
  this._totalRequestsEnqueued += 1;
473
434
  this._maximumQueueLength = Math.max(this._maximumQueueLength,
474
- this._connRequestQueue.length);
435
+ this._pendingRequestQueue.size);
475
436
  }
437
+ }
438
+ });
439
+
440
+ // This promise will timeout after queueTimeout seconds
441
+ const promiseToTimeout = new Promise ((_, reject) => {
442
+ if (this._queueTimeout > 0)
443
+ request.timeoutHandle = setTimeout(() => {
444
+ request.isWaiting = false;
445
+ this._pendingRequestQueue.delete(request);
446
+ if (this.enableStatistics)
447
+ this._totalRequestTimeouts += 1;
448
+ reject(errors.getErr(errors.ERR_CONN_REQUEST_TIMEOUT,
449
+ this._queueTimeout));
450
+ }, this._queueTimeout);
451
+ });
452
+
453
+ // Earliest promise to resolve/reject gets fulfilled
454
+ try {
455
+ await Promise.race([promiseToAcquire, promiseToTimeout]);
456
+ } catch (err) {
457
+ request.error = err;
458
+ }
476
459
 
477
- });
460
+ clearTimeout(request.timeoutHandle);
461
+ // Update time waited by request
462
+ if (this._enableStatistics && request.wasQueued)
463
+ this._updateWaitStatistics(request);
464
+
465
+ // Request failed to be fulfilled (errored/timeout)
466
+ if (request.error)
467
+ throw request.error;
468
+
469
+ return request.conn;
470
+ }
478
471
 
479
- // check if pool is draining/closed after delay has
480
- // completed and throw an appropriate error
481
- this._checkPoolOpen(true);
472
+ //---------------------------------------------------------------------------
473
+ // _bgRequestHandler()
474
+ //
475
+ // This method handles the queuing of all pending requests and processes them
476
+ // depending on the open connections and if the request is still waiting.
477
+ //---------------------------------------------------------------------------
478
+ async _bgRequestHandler() {
479
+ while (this.status != constants.POOL_STATUS_DRAINING ||
480
+ this.status != constants.POOL_STATUS_CLOSED) {
482
481
 
482
+ const request = this._pendingRequestQueue.values().next().value;
483
+ if (request && (this._connectionsOut < this.poolMax)) {
484
+ this._processRequest(request);
485
+ } else {
486
+ await new Promise((resolve) => {
487
+ this._bgRequestWaiter = resolve;
488
+ });
489
+ this._bgRequestWaiter = null;
490
+ }
483
491
  }
484
492
 
485
- // room is available in the queue, so proceed to acquire a connection from
486
- // the pool; adjust the connections out immediately in order to ensure that
487
- // another attempt doesn't proceed while this one is underway
488
- this._connectionsOut += 1;
489
- try {
493
+ // reject _pendingRequests waiting to be processed
494
+ for (const request of this._pendingRequestQueue)
495
+ request.reject(errors.throwErr(errors.ERR_POOL_CLOSED));
496
+ }
490
497
 
491
- // acquire connection from the pool
492
- const conn = new Connection();
493
- conn._impl = await this._impl.getConnection(options);
498
+ //---------------------------------------------------------------------------
499
+ // _processRequest()
500
+ //
501
+ // Method called to process a connection request and return the required
502
+ // 'connection'/'error returned by DB' if the client is still waiting.
503
+ //---------------------------------------------------------------------------
504
+ async _processRequest(request) {
505
+ const conn = new Connection();
506
+ try {
507
+ this._processing += 1;
508
+ this._connectionsOut += 1;
509
+ this._pendingRequestQueue.delete(request);
510
+ conn._impl = await this._impl.getConnection(request.options);
494
511
  conn._pool = this;
512
+ await this._tagFixup(conn, request.options.tag);
513
+ if (request.isWaiting)
514
+ request.conn = conn;
515
+ else
516
+ await conn.close({});
517
+ request.resolve();
518
+ } catch (err) {
519
+ if (request.isWaiting) {
520
+ if (this._enableStatistics)
521
+ this._totalFailedRequests += 1;
522
+ request.reject(err);
523
+ }
524
+ // Request errored, so we have some space in pool
525
+ this._release(conn._impl, request.options);
526
+ } finally {
527
+ this._processing -= 1;
528
+ if (this.status == constants.POOL_STATUS_DRAINING &&
529
+ this._processing == 0 && this._ensureNoPendingRequests)
530
+ this._ensureNoPendingRequests();
531
+ }
532
+ }
495
533
 
496
- // invoke tag fixup callback method if one has been specified and the
497
- // actual tag on the connection doesn't match the one requested, or the
498
- // connection is freshly created; if the callback fails, close the
499
- // connection and remove it from the pool
500
- const requestedTag = options.tag || "";
501
- if (typeof this.sessionCallback === 'function' &&
502
- (conn._impl._newSession || conn.tag != requestedTag)) {
503
- try {
504
- await new Promise((resolve, reject) => {
505
- this.sessionCallback(conn, requestedTag, function(err) {
506
- if (err) {
507
- reject(err);
508
- } else {
509
- resolve();
510
- }
511
- });
534
+ //---------------------------------------------------------------------------
535
+ // _tagFixup()
536
+ //
537
+ // Method to check if requested tag matches the connection's tag.
538
+ // If it does not match the callback function is invoked.
539
+ //---------------------------------------------------------------------------
540
+ async _tagFixup(conn, tag) {
541
+ const requestedTag = tag || "";
542
+ if (typeof this.sessionCallback === 'function' &&
543
+ (conn._impl._newSession ||
544
+ conn.tag != requestedTag)) {
545
+ try {
546
+ await new Promise((resolve, reject) => {
547
+ this.sessionCallback(conn, requestedTag, function(err) {
548
+ if (err) {
549
+ reject(err);
550
+ } else {
551
+ resolve();
552
+ }
512
553
  });
513
- } catch (err) {
514
- await conn.close({ drop: true });
515
- throw err;
516
- }
554
+ });
555
+ } catch (err) {
556
+ await conn.close({ drop: true });
557
+ throw err;
517
558
  }
559
+ }
560
+ }
518
561
 
519
- // when connection is closed, check to see if another request should be
520
- // processed and update any stats, as needed
521
- conn.on('_afterConnClose', () => {
522
- this._connectionsOut -= 1;
523
- this.emit('_checkRequestQueue');
524
- if (this._connectionsOut == 0) {
525
- this.emit('_allCheckedIn');
526
- }
562
+ //---------------------------------------------------------------------------
563
+ // _release()
564
+ //
565
+ // Method called by 'close() in connection.js after a release'/'error in
566
+ // _processRequest' to decrease connectionsOut & wake up _bgRequestWaiter
567
+ //---------------------------------------------------------------------------
568
+ async _release(impl, options) {
569
+ this._connectionsOut -= 1;
570
+ try {
571
+ // Directly close the connection using the underlying connection impl
572
+ if (impl)
573
+ await impl.close(options);
574
+ } finally {
575
+ if (this._connectionsOut == 0)
576
+ this.emit('_allCheckedIn');
577
+ setImmediate(() => {
578
+ if (this._pendingRequestQueue.size > 0 && this._bgRequestWaiter)
579
+ this._bgRequestWaiter();
527
580
  });
528
-
529
- return (conn);
530
-
531
- } catch (err) {
532
- this._connectionsOut -= 1;
533
- if (this._enableStatistics) {
534
- this._totalFailedRequests += 1;
535
- }
536
- this.emit('_checkRequestQueue');
537
- throw err;
538
581
  }
539
-
540
582
  }
541
583
 
542
584
  //---------------------------------------------------------------------------
@@ -747,7 +789,6 @@ class Pool extends EventEmitter {
747
789
  } finally {
748
790
  this._status = constants.POOL_STATUS_OPEN;
749
791
  }
750
- this.emit('_checkRequestQueue');
751
792
  }
752
793
 
753
794
  //---------------------------------------------------------------------------
@@ -54,7 +54,7 @@ class PoolStatistics {
54
54
  this.rejectedRequests = pool._totalRequestsRejected;
55
55
  this.requestTimeouts = pool._totalRequestTimeouts;
56
56
  this.maximumQueueLength = pool._maximumQueueLength;
57
- this.currentQueueLength = pool._connRequestQueue.length;
57
+ this.currentQueueLength = pool._pendingRequestQueue.size;
58
58
  this.timeInQueue = pool._totalTimeInQueue;
59
59
  this.minimumTimeInQueue = pool._minTimeInQueue;
60
60
  this.maximumTimeInQueue = pool._maxTimeInQueue;
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2018, 2023, Oracle and/or its affiliates.
1
+ // Copyright (c) 2018, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -34,6 +34,10 @@ const settings = require('./settings.js');
34
34
 
35
35
  class SodaCollection {
36
36
 
37
+ constructor(sodaDb) {
38
+ this._sodaDb = sodaDb;
39
+ }
40
+
37
41
  //---------------------------------------------------------------------------
38
42
  // createIndex()
39
43
  //
@@ -84,7 +88,7 @@ class SodaCollection {
84
88
  //---------------------------------------------------------------------------
85
89
  find() {
86
90
  errors.assertArgCount(arguments, 0, 0);
87
- const op = new SodaOperation();
91
+ const op = new SodaOperation(this._sodaDb);
88
92
  op._impl = this._impl.find();
89
93
  return op;
90
94
  }
@@ -111,7 +115,7 @@ class SodaCollection {
111
115
 
112
116
  const actualDocs = Array(docs.length);
113
117
  for (let i = 0; i < docs.length; i++) {
114
- actualDocs[i] = nodbUtil.verifySodaDoc(docs[i]);
118
+ actualDocs[i] = this._sodaDb._verifySodaDoc(docs[i]);
115
119
  }
116
120
 
117
121
  const options = {autoCommit: settings.autoCommit};
@@ -140,7 +144,7 @@ class SodaCollection {
140
144
 
141
145
  const actualDocs = Array(docs.length);
142
146
  for (let i = 0; i < docs.length; i++) {
143
- actualDocs[i] = nodbUtil.verifySodaDoc(docs[i]);
147
+ actualDocs[i] = this._sodaDb._verifySodaDoc(docs[i]);
144
148
  }
145
149
 
146
150
  const docImpls = await this._impl.insertManyAndGet(actualDocs, options);
@@ -159,7 +163,7 @@ class SodaCollection {
159
163
  //---------------------------------------------------------------------------
160
164
  async insertOne(content) {
161
165
  errors.assertArgCount(arguments, 1, 1);
162
- content = nodbUtil.verifySodaDoc(content);
166
+ content = this._sodaDb._verifySodaDoc(content);
163
167
  const options = {autoCommit: settings.autoCommit};
164
168
  await this._impl.insertOne(content, options);
165
169
  }
@@ -174,7 +178,7 @@ class SodaCollection {
174
178
  const options = {autoCommit: settings.autoCommit};
175
179
 
176
180
  errors.assertArgCount(arguments, 1, 2);
177
- content = nodbUtil.verifySodaDoc(content);
181
+ content = this._sodaDb._verifySodaDoc(content);
178
182
  if (arguments.length == 2) {
179
183
  errors.assertParamValue(nodbUtil.isObject(a2), 2);
180
184
  if (a2.hint !== undefined) {
@@ -223,7 +227,7 @@ class SodaCollection {
223
227
  //---------------------------------------------------------------------------
224
228
  async save(content) {
225
229
  errors.assertArgCount(arguments, 1, 1);
226
- content = nodbUtil.verifySodaDoc(content);
230
+ content = this._sodaDb._verifySodaDoc(content);
227
231
  const options = {autoCommit: settings.autoCommit};
228
232
  await this._impl.save(content, options);
229
233
  }
@@ -236,7 +240,7 @@ class SodaCollection {
236
240
  //---------------------------------------------------------------------------
237
241
  async saveAndGet(content, a2) {
238
242
  errors.assertArgCount(arguments, 1, 2);
239
- content = nodbUtil.verifySodaDoc(content);
243
+ content = this._sodaDb._verifySodaDoc(content);
240
244
  const options = {autoCommit: settings.autoCommit};
241
245
  if (arguments.length == 2) {
242
246
  errors.assertParamValue(nodbUtil.isObject(a2), 2);
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2018, 2023, Oracle and/or its affiliates.
1
+ // Copyright (c) 2018, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -32,6 +32,7 @@ const SodaDocument = require('./sodaDocument.js');
32
32
  const errors = require('./errors.js');
33
33
  const nodbUtil = require('./util.js');
34
34
  const settings = require('./settings.js');
35
+ const transformer = require('./transformer.js');
35
36
 
36
37
  class SodaDatabase {
37
38
 
@@ -64,7 +65,7 @@ class SodaDatabase {
64
65
  }
65
66
  }
66
67
 
67
- const coll = new SodaCollection();
68
+ const coll = new SodaCollection(this);
68
69
  coll._impl = await this._impl.createCollection(name, options);
69
70
  return coll;
70
71
  }
@@ -76,6 +77,7 @@ class SodaDatabase {
76
77
  //---------------------------------------------------------------------------
77
78
  createDocument(content, a2) {
78
79
  let options = {};
80
+ let inContent;
79
81
 
80
82
  errors.assertArgCount(arguments, 1, 2);
81
83
  errors.assertParamValue(Buffer.isBuffer(content) ||
@@ -87,14 +89,20 @@ class SodaDatabase {
87
89
  errors.assertParamPropString(options, 2, "mediaType");
88
90
  }
89
91
 
90
- if (typeof content === 'string') {
91
- content = Buffer.from(content);
92
- } else if (nodbUtil.isObject(content)) {
93
- content = Buffer.from(JSON.stringify(content));
92
+ // native JSON support for SODA is available from Oracle Client 23.4
93
+ if (this._impl.supportsJson &&
94
+ (!options.mediaType || options.mediaType === 'application/json')) {
95
+ inContent = transformer.transformJsonValue(content);
96
+ } else if (Buffer.isBuffer(content)) {
97
+ inContent = content;
98
+ } else if (typeof content === 'string') {
99
+ inContent = Buffer.from(content);
100
+ } else {
101
+ inContent = Buffer.from(JSON.stringify(content));
94
102
  }
95
103
 
96
104
  const doc = new SodaDocument();
97
- doc._impl = this._impl.createDocument(content, options);
105
+ doc._impl = this._impl.createDocument(inContent, options);
98
106
  return doc;
99
107
  }
100
108
 
@@ -133,12 +141,31 @@ class SodaDatabase {
133
141
  const options = {autoCommit: settings.autoCommit};
134
142
  const collImpl = await this._impl.openCollection(name, options);
135
143
  if (collImpl) {
136
- const coll = new SodaCollection();
144
+ const coll = new SodaCollection(this);
137
145
  coll._impl = collImpl;
138
146
  return coll;
139
147
  }
140
148
  }
141
149
 
150
+ //---------------------------------------------------------------------------
151
+ // _verifySodaDoc()
152
+ //
153
+ // Verifies the content passed is valid for a SodaDocument object.
154
+ //---------------------------------------------------------------------------
155
+ _verifySodaDoc(content) {
156
+ // check if the content is a sodaDocument object
157
+ if (content != null && content._sodaDocumentMarker)
158
+ return content._impl;
159
+ errors.assertParamValue(nodbUtil.isObject(content), 1);
160
+
161
+ // JSON is assumed, if you pass in the content
162
+ // starting from Oracle Client 23ai onwards, JSON in SODA is supported
163
+ if (this._impl._supportsJson)
164
+ return transformer.transformJsonValue(content);
165
+
166
+ return Buffer.from(JSON.stringify(content));
167
+ }
168
+
142
169
  }
143
170
 
144
171
  nodbUtil.wrapFns(SodaDatabase.prototype,
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2018, 2022, Oracle and/or its affiliates.
1
+ // Copyright (c) 2018, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -40,10 +40,14 @@ class SodaDocument {
40
40
  //---------------------------------------------------------------------------
41
41
  // getContent()
42
42
  //
43
- // Returns the document content as a Javascript object.
43
+ // Returns the document content in native JavaScript format.
44
+ // Non-JSON content is returned as a JavaScript Buffer.
44
45
  //---------------------------------------------------------------------------
45
46
  getContent() {
46
- return JSON.parse(this._impl.getContentAsString());
47
+ const content = this._impl.getContent();
48
+ if (typeof content === 'string' && this.mediaType === 'application/json')
49
+ return JSON.parse(content);
50
+ return content;
47
51
  }
48
52
 
49
53
  //---------------------------------------------------------------------------
@@ -52,7 +56,14 @@ class SodaDocument {
52
56
  // Returns the document content as a buffer.
53
57
  //---------------------------------------------------------------------------
54
58
  getContentAsBuffer() {
55
- return this._impl.getContentAsBuffer();
59
+ const content = this._impl.getContent();
60
+ if (Buffer.isBuffer(content))
61
+ return content;
62
+ else if (typeof content == 'string')
63
+ return Buffer.from(content);
64
+
65
+ // assumed to be a JSON Object (available from Oracle Client 23ai)
66
+ return Buffer.from(JSON.stringify(content));
56
67
  }
57
68
 
58
69
  //---------------------------------------------------------------------------
@@ -61,7 +72,14 @@ class SodaDocument {
61
72
  // Returns the document content as a string.
62
73
  //---------------------------------------------------------------------------
63
74
  getContentAsString() {
64
- return this._impl.getContentAsString();
75
+ const content = this._impl.getContent();
76
+ if (typeof content === 'string')
77
+ return content;
78
+ else if (Buffer.isBuffer(content))
79
+ return content.toString();
80
+
81
+ // assumed to be a JSON Object (available from Oracle Client 23ai)
82
+ return JSON.stringify(content);
65
83
  }
66
84
 
67
85
  //---------------------------------------------------------------------------
@@ -92,7 +110,7 @@ class SodaDocument {
92
110
  }
93
111
 
94
112
  //---------------------------------------------------------------------------
95
- // verison
113
+ // version
96
114
  //
97
115
  // Property for the version of the document.
98
116
  //---------------------------------------------------------------------------