oracledb 6.8.0 → 6.9.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 (67) hide show
  1. package/README.md +39 -3
  2. package/build/Release/oracledb-6.9.0-darwin-arm64.node +0 -0
  3. package/build/Release/oracledb-6.9.0-darwin-arm64.node-buildinfo.txt +1 -0
  4. package/build/Release/oracledb-6.9.0-darwin-x64.node +0 -0
  5. package/build/Release/oracledb-6.9.0-darwin-x64.node-buildinfo.txt +1 -0
  6. package/build/Release/oracledb-6.9.0-js-buildinfo.txt +1 -0
  7. package/build/Release/oracledb-6.9.0-linux-arm64.node +0 -0
  8. package/build/Release/oracledb-6.9.0-linux-arm64.node-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-6.9.0-linux-x64.node +0 -0
  10. package/build/Release/oracledb-6.9.0-linux-x64.node-buildinfo.txt +1 -0
  11. package/build/Release/oracledb-6.9.0-win32-x64.node +0 -0
  12. package/build/Release/oracledb-6.9.0-win32-x64.node-buildinfo.txt +1 -0
  13. package/lib/aqMessage.js +10 -1
  14. package/lib/configProviders/file.js +72 -0
  15. package/lib/connection.js +87 -2
  16. package/lib/dbObject.js +9 -0
  17. package/lib/errors.js +56 -31
  18. package/lib/impl/connection.js +9 -0
  19. package/lib/impl/pool.js +10 -1
  20. package/lib/impl/resultset.js +5 -4
  21. package/lib/lob.js +9 -1
  22. package/lib/oracledb.js +129 -16
  23. package/lib/pool.js +12 -1
  24. package/lib/poolStatistics.js +3 -1
  25. package/lib/thin/connection.js +117 -10
  26. package/lib/thin/dbObject.js +8 -6
  27. package/lib/thin/lob.js +2 -3
  28. package/lib/thin/pool.js +105 -28
  29. package/lib/thin/protocol/capabilities.js +7 -3
  30. package/lib/thin/protocol/constants.js +29 -1
  31. package/lib/thin/protocol/messages/auth.js +13 -0
  32. package/lib/thin/protocol/messages/base.js +98 -9
  33. package/lib/thin/protocol/messages/dataType.js +2 -1
  34. package/lib/thin/protocol/messages/execute.js +46 -4
  35. package/lib/thin/protocol/messages/lobOp.js +5 -22
  36. package/lib/thin/protocol/messages/logOff.js +0 -1
  37. package/lib/thin/protocol/messages/sessionRelease.js +0 -1
  38. package/lib/thin/protocol/messages/transactionSwitch.js +12 -4
  39. package/lib/thin/protocol/protocol.js +2 -1
  40. package/lib/thin/sqlnet/connStrategy.js +2 -1
  41. package/lib/thin/sqlnet/navNodes.js +1 -1
  42. package/lib/thin/sqlnet/networkSession.js +9 -3
  43. package/lib/thin/sqlnet/packet.js +2 -1
  44. package/lib/thin/sqlnet/sessionAtts.js +2 -3
  45. package/lib/types.js +0 -5
  46. package/lib/util.js +76 -2
  47. package/lib/version.js +1 -1
  48. package/package/install.js +3 -3
  49. package/package/prunebinaries.js +6 -12
  50. package/package.json +2 -2
  51. package/{lib/configProviders/azure.js → plugins/configProviders/azure/index.js} +70 -63
  52. package/plugins/configProviders/azurevault/index.js +84 -0
  53. package/{lib → plugins}/configProviders/base.js +13 -3
  54. package/{lib/configProviders/ociobject.js → plugins/configProviders/ociobject/index.js} +43 -78
  55. package/plugins/configProviders/ocivault/index.js +100 -0
  56. package/plugins/token/extensionOci/index.js +71 -35
  57. package/build/Release/oracledb-6.8.0-darwin-arm64.node +0 -0
  58. package/build/Release/oracledb-6.8.0-darwin-arm64.node-buildinfo.txt +0 -1
  59. package/build/Release/oracledb-6.8.0-darwin-x64.node +0 -0
  60. package/build/Release/oracledb-6.8.0-darwin-x64.node-buildinfo.txt +0 -1
  61. package/build/Release/oracledb-6.8.0-js-buildinfo.txt +0 -1
  62. package/build/Release/oracledb-6.8.0-linux-arm64.node +0 -0
  63. package/build/Release/oracledb-6.8.0-linux-arm64.node-buildinfo.txt +0 -1
  64. package/build/Release/oracledb-6.8.0-linux-x64.node +0 -0
  65. package/build/Release/oracledb-6.8.0-linux-x64.node-buildinfo.txt +0 -1
  66. package/build/Release/oracledb-6.8.0-win32-x64.node +0 -0
  67. package/build/Release/oracledb-6.8.0-win32-x64.node-buildinfo.txt +0 -1
package/lib/thin/pool.js CHANGED
@@ -35,7 +35,6 @@ const util = require('../util.js');
35
35
  const thinUtil = require('./util.js');
36
36
  const {getConnectionInfo} = require('./sqlnet/networkSession.js');
37
37
  const crypto = require('crypto');
38
- const EventEmitter = require('events');
39
38
  const Timers = require('timers');
40
39
 
41
40
  class ThinPoolImpl extends PoolImpl {
@@ -57,6 +56,7 @@ class ThinPoolImpl extends PoolImpl {
57
56
  this._poolTimeout = params.poolTimeout;
58
57
  this._poolPingInterval = params.poolPingInterval;
59
58
  this._poolPingTimeout = params.poolPingTimeout;
59
+ this._maxLifetimeSession = params.maxLifetimeSession || 0;
60
60
  this._stmtCacheSize = params.stmtCacheSize;
61
61
 
62
62
  // The user Config filterd from common layer is cached except
@@ -77,6 +77,9 @@ class ThinPoolImpl extends PoolImpl {
77
77
  this._schedulerJob = null;
78
78
  this._poolCloseWaiter = null;
79
79
  this._pendingRequests = [];
80
+ this._connsToDrop = [];
81
+ this._bgCleaner = null;
82
+ this._poolCloseCleaner = null;
80
83
 
81
84
  // password obfuscation
82
85
  if (this._password !== undefined) {
@@ -117,11 +120,6 @@ class ThinPoolImpl extends PoolImpl {
117
120
  this._accessTokenConfig = params.accessTokenConfig;
118
121
  this._isDRCPEnabled = false;
119
122
  this._implicitPool = null;
120
- this.eventEmitter = new EventEmitter();
121
- // listener to remove dead or idle connections
122
- this.eventEmitter.on('_removePoolConnection', async (connImpl) => {
123
- await this._destroy(connImpl);
124
- });
125
123
  }
126
124
 
127
125
  //---------------------------------------------------------------------------
@@ -142,6 +140,9 @@ class ThinPoolImpl extends PoolImpl {
142
140
  // create a background task. It will create minimum connections in the pool
143
141
  // and expand the pool as required.
144
142
  this.bgThreadFunc();
143
+ // create a background task to remove connections that are to in
144
+ // _connsToDrop, reason for dropping may be poolTimeout or poolMax decrease
145
+ this._bgConnCleaner();
145
146
  }
146
147
 
147
148
  //---------------------------------------------------------------------------
@@ -254,9 +255,13 @@ class ThinPoolImpl extends PoolImpl {
254
255
  // destroy connection when pool close operation is called
255
256
  //---------------------------------------------------------------------------
256
257
  async _destroy(connection) {
257
- if (connection.nscon.ntAdapter.connected) {
258
- connection._dropSess = true;
259
- await connection.close();
258
+ try {
259
+ if (connection.nscon.ntAdapter.connected) {
260
+ connection._dropSess = true;
261
+ await connection.close();
262
+ }
263
+ } catch (err) {
264
+ return;
260
265
  }
261
266
  }
262
267
 
@@ -290,7 +295,12 @@ class ThinPoolImpl extends PoolImpl {
290
295
  await this._destroy(conn);
291
296
  }
292
297
 
293
- this.eventEmitter.removeAllListeners();
298
+ // Let the bgCleaner destroy all connsToDrop and stop
299
+ await new Promise((resolve) => {
300
+ this._poolCloseCleaner = resolve;
301
+ if (this._bgCleaner)
302
+ this._bgCleaner();
303
+ });
294
304
  }
295
305
 
296
306
  //---------------------------------------------------------------------------
@@ -356,6 +366,13 @@ class ThinPoolImpl extends PoolImpl {
356
366
  return this._poolTimeout;
357
367
  }
358
368
 
369
+ //---------------------------------------------------------------------------
370
+ // returns the pool max lifetime for a session (seconds)
371
+ //---------------------------------------------------------------------------
372
+ getMaxLifetimeSession() {
373
+ return this._maxLifetimeSession;
374
+ }
375
+
359
376
  //---------------------------------------------------------------------------
360
377
  // returns whether the SODA metadata cache is enabled or not
361
378
  //---------------------------------------------------------------------------
@@ -399,10 +416,13 @@ class ThinPoolImpl extends PoolImpl {
399
416
  break;
400
417
  }
401
418
 
402
- this.eventEmitter.emit('_removePoolConnection', conn);
419
+ this._connsToDrop.push(conn);
403
420
  this._freeConnectionList.shift();
404
421
  }
405
422
 
423
+ // call cleaner for destroying connections added to _connsToDrop
424
+ if (this._bgCleaner)
425
+ this._bgCleaner();
406
426
  this._schedulerJob = null;
407
427
  this._setScheduler();
408
428
  }
@@ -438,15 +458,6 @@ class ThinPoolImpl extends PoolImpl {
438
458
 
439
459
  // continue until a close request is received
440
460
  while (!this._poolCloseWaiter) {
441
- // eliminate connections that exceed the poolMax (this should only happen
442
- // after a reconfiguration has taken place where the maximum number of
443
- // connections is reduced)
444
- let numToDestroy = this._poolMax - this.getConnectionsOpen();
445
- while (numToDestroy > 0 && this._freeConnectionList.length > 0) {
446
- const connToBeRemoved = this._freeConnectionList.shift();
447
- this.eventEmitter.emit('_removePoolConnection', connToBeRemoved);
448
- numToDestroy--;
449
- }
450
461
 
451
462
  const numToCreate = this._getNumConnsToCreate();
452
463
  // connection creation is going on serially and not concurrently
@@ -459,6 +470,7 @@ class ThinPoolImpl extends PoolImpl {
459
470
  await conn.connect(config);
460
471
  conn._newSession = true;
461
472
  conn._dropSess = false;
473
+ conn._creationTime = Date.now();
462
474
  conn._lastTimeUsed = Date.now();
463
475
  this._freeConnectionList.push(conn);
464
476
  } catch (err) {
@@ -491,7 +503,8 @@ class ThinPoolImpl extends PoolImpl {
491
503
  }
492
504
 
493
505
  // if no pending requests, wait for pending requests to appear!
494
- if (this._pendingRequests.length == 0 || this._bgErr) {
506
+ if ((this._pendingRequests.length == 0 || this._bgErr) &&
507
+ this.getConnectionsOpen() >= this._poolMin) {
495
508
  await new Promise((resolve) => {
496
509
  this.bgWaiter = resolve;
497
510
  });
@@ -503,6 +516,27 @@ class ThinPoolImpl extends PoolImpl {
503
516
  this._poolCloseWaiter();
504
517
  }
505
518
 
519
+ //---------------------------------------------------------------------------
520
+ // _bgConnCleaner()
521
+ // Method which stays in the stack and performs cleanup whenever a connection
522
+ // gets added for cleanup to _connsToDrop
523
+ // acquire a connection from connection pool
524
+ //---------------------------------------------------------------------------
525
+ async _bgConnCleaner() {
526
+ // run until a pool.close() is issued
527
+ while (!this._poolCloseCleaner) {
528
+
529
+ // wait for _bgCleaner to be called for cleanup
530
+ if (this._connsToDrop.length == 0)
531
+ await new Promise(resolve => this._bgCleaner = resolve);
532
+
533
+ // destroy connections in _connsToDrop
534
+ while (this._connsToDrop.length)
535
+ await this._destroy(this._connsToDrop.pop());
536
+ }
537
+ this._poolCloseCleaner();
538
+ }
539
+
506
540
  //---------------------------------------------------------------------------
507
541
  // acquire()
508
542
  //
@@ -514,9 +548,10 @@ class ThinPoolImpl extends PoolImpl {
514
548
  while (this._freeConnectionList.length > 0) {
515
549
  const conn = this._freeConnectionList.pop();
516
550
 
517
- // if connection is unhealthy, drop it from the pool
518
- if (!conn.isHealthy()) {
519
- this.eventEmitter.emit('_removePoolConnection', conn);
551
+ // drop from the pool if the connection is unhealthy, or if the
552
+ // connection's lifetime exceeds maxLifetimeSession
553
+ if (!this._shouldRetainInPool(conn)) {
554
+ this._invokeBgCleaner(conn);
520
555
  continue;
521
556
  }
522
557
 
@@ -595,11 +630,11 @@ class ThinPoolImpl extends PoolImpl {
595
630
  if (conn.nscon.connected) {
596
631
  conn._lastTimeUsed = Date.now();
597
632
  conn._newSession = false;
598
- if ((this._freeConnectionList.length + this._usedConnectionList.size)
599
- < this._poolMax) {
633
+ if ((this.getConnectionsOpen() < this._poolMax) &&
634
+ this._shouldRetainInPool(conn)) {
600
635
  this._freeConnectionList.push(conn);
601
636
  } else {
602
- this.eventEmitter.emit('_removePoolConnection', conn);
637
+ this._invokeBgCleaner(conn);
603
638
  }
604
639
  }
605
640
 
@@ -652,9 +687,51 @@ class ThinPoolImpl extends PoolImpl {
652
687
  this._poolMin = params.poolMin;
653
688
  }
654
689
 
655
- if (this.bgWaiter) {
690
+ if (params.maxLifetimeSession !== undefined) {
691
+ this._maxLifetimeSession = params.maxLifetimeSession;
692
+ }
693
+
694
+ let numToDestroy = this.getConnectionsOpen() - this._poolMax;
695
+ while (this._freeConnectionList.length && numToDestroy > 0) {
696
+ const conn = this._freeConnectionList.pop();
697
+ this._connsToDrop.push(conn);
698
+ numToDestroy--;
699
+ }
700
+
701
+ // call cleanup for destroying connections in _connsToDrop
702
+ if (this._bgCleaner)
703
+ this._bgCleaner();
704
+ }
705
+
706
+ //---------------------------------------------------------------------------
707
+ // _invokeBgCleaner()
708
+ //
709
+ // drop conn and invoke the bgCleaner while also ensuring min connections
710
+ //---------------------------------------------------------------------------
711
+ _invokeBgCleaner(conn) {
712
+ this._connsToDrop.push(conn);
713
+ if (this._bgCleaner)
714
+ this._bgCleaner();
715
+
716
+ // ensure min connections are maintained in pool
717
+ if ((this.getConnectionsOpen() < this._poolMin) && this.bgWaiter)
656
718
  this.bgWaiter();
719
+ }
720
+
721
+ //---------------------------------------------------------------------------
722
+ // _shouldRetainInPool()
723
+ //
724
+ // returns true if the connection should be retained in pool
725
+ //---------------------------------------------------------------------------
726
+ _shouldRetainInPool(conn) {
727
+ if (!conn.isHealthy())
728
+ return false;
729
+ // check if maxLifetimeSession of conn has expired
730
+ if (this._maxLifetimeSession !== 0) {
731
+ if ((Date.now() - conn._creationTime) > (this._maxLifetimeSession * 1000))
732
+ return false;
657
733
  }
734
+ return true;
658
735
  }
659
736
  }
660
737
 
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2022, 2024, Oracle and/or its affiliates.
1
+ // Copyright (c) 2022, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -104,11 +104,14 @@ class Capabilities {
104
104
  constants.TNS_CCAP_LOB2_QUASI | constants.TNS_CCAP_LOB2_2GB_PREFETCH;
105
105
  this.compileCaps[constants.TNS_CCAP_TTC3] =
106
106
  constants.TNS_CCAP_IMPLICIT_RESULTS | constants.TNS_CCAP_BIG_CHUNK_CLR |
107
- constants.TNS_CCAP_KEEP_OUT_ORDER;
107
+ constants.TNS_CCAP_KEEP_OUT_ORDER | constants.TNS_CCAP_LTXID;
108
+ this.compileCaps[constants.TNS_CCAP_OCI3] = constants.TNS_CCAP_OCI3_OCSSYNC;
108
109
  this.compileCaps[constants.TNS_CCAP_TTC2] = constants.TNS_CCAP_ZLNP;
109
110
  this.compileCaps[constants.TNS_CCAP_OCI2] = constants.TNS_CCAP_DRCP;
110
111
  this.compileCaps[constants.TNS_CCAP_CLIENT_FN] =
111
112
  constants.TNS_CCAP_CLIENT_FN_MAX;
113
+ this.compileCaps[constants.TNS_CCAP_SESS_SIGNATURE_VERSION] =
114
+ constants.TNS_CCAP_FIELD_VERSION_12_2;
112
115
  this.compileCaps[constants.TNS_CCAP_TTC4] =
113
116
  constants.TNS_CCAP_INBAND_NOTIFICATION;
114
117
  if (nscon.endOfRequestSupport) {
@@ -117,7 +120,8 @@ class Capabilities {
117
120
  this.compileCaps[constants.TNS_CCAP_CTB_FEATURE_BACKPORT] =
118
121
  constants.TNS_CCAP_CTB_IMPLICIT_POOL;
119
122
  this.compileCaps[constants.TNS_CCAP_TTC5] =
120
- constants.TNS_CCAP_VECTOR_SUPPORT;
123
+ constants.TNS_CCAP_VECTOR_SUPPORT |
124
+ constants.TNS_CCAP_TTC5_SESSIONLESS_TXNS;
121
125
  this.compileCaps[constants.TNS_CCAP_VECTOR_FEATURES] =
122
126
  constants.TNS_CCAP_VECTOR_FEATURE_BINARY | constants.TNS_CCAP_VECTOR_FEATURE_SPARSE;
123
127
  }
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2022, 2024, Oracle and/or its affiliates.
1
+ // Copyright (c) 2022, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -405,6 +405,7 @@ module.exports = {
405
405
  TNS_DATA_TYPE_UD21: 639,
406
406
  TNS_DATA_TYPE_TNP: 640,
407
407
  TNS_DATA_TYPE_OAC: 646,
408
+ TNS_DATA_TYPE_SESSSIGN: 647,
408
409
  TNS_DATA_TYPE_OER: 652,
409
410
  TNS_DATA_TYPE_UDS: 663,
410
411
 
@@ -439,6 +440,7 @@ module.exports = {
439
440
  // parameter keyword numbers,
440
441
  TNS_KEYWORD_NUM_CURRENT_SCHEMA: 168,
441
442
  TNS_KEYWORD_NUM_EDITION: 172,
443
+ TNS_KEYWORD_NUM_TRANSACTION_ID: 201,
442
444
 
443
445
  // bind flags
444
446
  TNS_BIND_USE_INDICATORS: 0x0001,
@@ -476,6 +478,7 @@ module.exports = {
476
478
  TNS_SERVER_PIGGYBACK_LTXID: 7,
477
479
  TNS_SERVER_PIGGYBACK_AC_REPLAY_CONTEXT: 8,
478
480
  TNS_SERVER_PIGGYBACK_EXT_SYNC: 9,
481
+ TNS_SERVER_PIGGYBACK_SESS_SIGNATURE: 10,
479
482
 
480
483
  // session return constants
481
484
  TNS_SESSGET_SESSION_CHANGED: 4,
@@ -582,7 +585,9 @@ module.exports = {
582
585
  TNS_CCAP_UB2_DTY: 27,
583
586
  TNS_CCAP_OCI2: 31,
584
587
  TNS_CCAP_CLIENT_FN: 34,
588
+ TNS_CCAP_OCI3: 35,
585
589
  TNS_CCAP_TTC3: 37,
590
+ TNS_CCAP_SESS_SIGNATURE_VERSION: 39,
586
591
  TNS_CCAP_TTC4: 40,
587
592
  TNS_CCAP_LOB2: 42,
588
593
  TNS_CCAP_TTC5: 44,
@@ -625,6 +630,7 @@ module.exports = {
625
630
  TNS_CCAP_RPC_VERSION_MAX: 7,
626
631
  TNS_CCAP_RPC_SIG_VALUE: 3,
627
632
  TNS_CCAP_DBF_VERSION_MAX: 1,
633
+ TNS_CCAP_LTXID: 0x08,
628
634
  TNS_CCAP_IMPLICIT_RESULTS: 0x10,
629
635
  TNS_CCAP_BIG_CHUNK_CLR: 0x20,
630
636
  TNS_CCAP_KEEP_OUT_ORDER: 0x80,
@@ -645,6 +651,8 @@ module.exports = {
645
651
  TNS_CCAP_VECTOR_SUPPORT: 0x08,
646
652
  TNS_CCAP_VECTOR_FEATURE_BINARY: 0x01,
647
653
  TNS_CCAP_VECTOR_FEATURE_SPARSE: 0x02,
654
+ TNS_CCAP_TTC5_SESSIONLESS_TXNS: 0x20,
655
+ TNS_CCAP_OCI3_OCSSYNC: 0x20,
648
656
 
649
657
  // runtime capability indices
650
658
  TNS_RCAP_COMPAT: 0,
@@ -674,6 +682,7 @@ module.exports = {
674
682
  // transaction switching op codes
675
683
  TNS_TPC_TXN_START: 0x01,
676
684
  TNS_TPC_TXN_DETACH: 0x02,
685
+ TNS_TPC_TXN_POST_DETACH: 0x04,
677
686
 
678
687
  // transaction change state op codes
679
688
  TNS_TPC_TXN_COMMIT: 0x01,
@@ -689,6 +698,25 @@ module.exports = {
689
698
  TNS_TPC_TXN_STATE_READ_ONLY: 4,
690
699
  TNS_TPC_TXN_STATE_FORGOTTEN: 5,
691
700
 
701
+ // sessionless transaction flag
702
+ TNS_TPC_TRANS_SESSIONLESS: 0x00000010,
703
+
704
+ // sessionless transaction format
705
+ TNS_TPC_TRANS_SESSIONLESS_FORMAT: 0x4e5c3e,
706
+
707
+ // sessionless sync version
708
+ TNS_TPC_TRANS_TRANSACTION_ID_SYNC_VERSION_1: 0x01,
709
+
710
+ // sessionless server states
711
+ TNS_TPC_TRANS_TRANSACTION_ID_SYNC_SET: 0x40,
712
+ TNS_TPC_TRANS_TRANSACTION_ID_SYNC_UNSET: 0x80,
713
+
714
+ // sessionless state reason
715
+ TNS_TPC_TRANS_TRANSACTION_ID_SYNC_SERVER: 0x01,
716
+ TNS_TPC_TRANS_TRANSACTION_ID_SYNC_CLIENT: 0x02,
717
+ TNS_TPC_TRANS_TRANSACTION_ID_SYNC_TXEND_XA: 0x03,
718
+
719
+
692
720
  // other constants
693
721
  TNS_ESCAPE_CHAR: 253,
694
722
  TNS_LONG_LENGTH_INDICATOR: dataHandlerConstants.TNS_LONG_LENGTH_INDICATOR,
@@ -114,6 +114,7 @@ class AuthMessage extends Message {
114
114
  } else if (process.env.ORA_EDITION) {
115
115
  this.edition = process.env.ORA_EDITION;
116
116
  }
117
+ this.appContext = config.appContext;
117
118
  this.setAuthMode(config);
118
119
  }
119
120
 
@@ -247,6 +248,9 @@ class AuthMessage extends Message {
247
248
  if (this.edition) {
248
249
  numPairs += 1;
249
250
  }
251
+ if (this.appContext) {
252
+ numPairs += this.appContext.length * 3;
253
+ }
250
254
  if (this.schemaUser.length !== 0) {
251
255
  numPairs += 1;
252
256
  }
@@ -319,6 +323,15 @@ class AuthMessage extends Message {
319
323
  if (this.edition) {
320
324
  buf.writeKeyValue("AUTH_ORA_EDITION", this.edition);
321
325
  }
326
+ if (this.appContext) {
327
+ // NOTE: these keys require a trailing null character as the server
328
+ // expects it!
329
+ for (const entry of this.appContext) {
330
+ buf.writeKeyValue("AUTH_APPCTX_NSPACE\0", entry[0]);
331
+ buf.writeKeyValue("AUTH_APPCTX_ATTR\0", entry[1]);
332
+ buf.writeKeyValue("AUTH_APPCTX_VALUE\0", entry[2]);
333
+ }
334
+ }
322
335
 
323
336
  }
324
337
  }
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2022, 2024, Oracle and/or its affiliates.
1
+ // Copyright (c) 2022, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -30,6 +30,34 @@ const { Buffer } = require('buffer');
30
30
  const constants = require("../constants.js");
31
31
  const errors = require("../../../errors.js");
32
32
 
33
+ // List of recoverable errors
34
+ const RECOVERABLE_ERRORS = new Set([
35
+ 28, // session has been terminated
36
+ 31, // session marked for kill
37
+ 376, // file %s cannot be read at this time
38
+ 603, // ORACLE server session terminated
39
+ 1012, // not logged on
40
+ 1033, // ORACLE initialization or shutdown in progress
41
+ 1034, // the Oracle instance is not available for use
42
+ 1089, // immediate shutdown or close in progress
43
+ 1090, // shutdown in progress
44
+ 1092, // ORACLE instance terminated
45
+ 1115, // IO error reading block from file %s (block # %s)
46
+ 2396, // exceeded maximum idle time
47
+ 3113, // end-of-file on communication channel
48
+ 3114, // not connected to ORACLE
49
+ 3135, // connection lost contact
50
+ 12153, // TNS:not connected
51
+ 12514, // Service %s is not registered with the listener
52
+ 12537, // TNS:connection closed
53
+ 12547, // TNS:lost contact
54
+ 12570, // TNS:packet reader failure
55
+ 12571, // TNS:packet writer failure
56
+ 12583, // TNS:no reader
57
+ 12757, // instance does not currently know of requested service
58
+ 16456 // missing or invalid value
59
+ ]);
60
+
33
61
  /**
34
62
  *
35
63
  * Base class for all the RPC messages to support encode/decode functions
@@ -52,6 +80,7 @@ class Message {
52
80
  async postProcess() { }
53
81
 
54
82
  writeFunctionHeader(buf) {
83
+ this.writePiggybacks(buf);
55
84
  buf.writeUInt8(this.messageType);
56
85
  buf.writeUInt8(this.functionCode);
57
86
  buf.writeSeqNum();
@@ -160,6 +189,8 @@ class Message {
160
189
  */
161
190
  this.errorInfo.message = this.errorInfo.message.trim();
162
191
  }
192
+ this.errorInfo.isRecoverable = RECOVERABLE_ERRORS.has(this.errorInfo.num);
193
+
163
194
  this.endOfResponse = !this.connection.nscon.endOfRequestSupport;
164
195
  }
165
196
 
@@ -220,7 +251,7 @@ class Message {
220
251
  if (opcode === constants.TNS_SERVER_PIGGYBACK_LTXID) {
221
252
  const num_bytes = buf.readUB4();
222
253
  if (num_bytes > 0) {
223
- buf.skipBytesChunked();
254
+ this.connection._ltxid = Buffer.from(buf.readBytesWithLength());
224
255
  }
225
256
  } else if ((opcode === constants.TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION)
226
257
  || (opcode === constants.TNS_SERVER_PIGGYBACK_TRACE_EVENT)) {
@@ -235,15 +266,22 @@ class Message {
235
266
  const num_elements = buf.readUB4();
236
267
  buf.skipBytes(1); // skip length
237
268
  for (let i = 0; i < num_elements; i++) {
238
- let temp16 = buf.readUB2();
239
- if (temp16 > 0) { // skip key
240
- buf.skipBytesChunked();
269
+ let numBytes = buf.readUB2();
270
+ let keyTextValue, value;
271
+ if (numBytes > 0) { // read key
272
+ keyTextValue = buf.readStr(constants.CSFRM_IMPLICIT);
241
273
  }
242
- temp16 = buf.readUB2();
243
- if (temp16 > 0) { // skip value
244
- buf.skipBytesChunked();
274
+ numBytes = buf.readUB2();
275
+ if (numBytes > 0) // read value
276
+ value = buf.readBytesWithLength();
277
+ const keywordNum = buf.readUB2(); // read keyword number
278
+ if (keywordNum == constants.TNS_KEYWORD_NUM_TRANSACTION_ID) {
279
+ this._updateSessionlessTxnState(value);
280
+ } else if (keywordNum === constants.TNS_KEYWORD_NUM_CURRENT_SCHEMA) {
281
+ this.connection.currentSchema = keyTextValue;
282
+ } else if (keywordNum === constants.TNS_KEYWORD_NUM_EDITION) {
283
+ this.connection._edition = keyTextValue;
245
284
  }
246
- buf.skipUB2(); // skip flags
247
285
  }
248
286
  buf.skipUB4(); // skip overall flags
249
287
  } else if (opcode === constants.TNS_SERVER_PIGGYBACK_EXT_SYNC) {
@@ -286,12 +324,22 @@ class Message {
286
324
  this.connection._drcpEstablishSession = false;
287
325
  buf.skipUB4(); // session id
288
326
  buf.skipUB2(); // serial number
327
+ } else if (opcode === constants.TNS_SERVER_PIGGYBACK_SESS_SIGNATURE) {
328
+ buf.skipUB2(); // number of dtys
329
+ buf.skipUB1(); // length of dty
330
+ buf.skipUB8(); // signature flags
331
+ buf.skipUB8(); // client signature
332
+ buf.skipUB8(); // server signature
289
333
  } else {
290
334
  errors.throwErr(errors.ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, opcode);
291
335
  }
292
336
  }
293
337
 
294
338
  writePiggybacks(buf) {
339
+ // Don't write piggybacks if not authenticated yet
340
+ if (this.connection._protocol.connInProgress)
341
+ return;
342
+
295
343
  if (this.connection._currentSchemaModified) {
296
344
  this._writeCurrentSchemaPiggyback(buf);
297
345
  }
@@ -313,6 +361,11 @@ class Message {
313
361
  if (this.connection._tempLobsTotalSize > 0) {
314
362
  this.writeCloseTempLobsPiggyback(buf);
315
363
  }
364
+ if (this.connection._sessionlessData?.pending) {
365
+ this.connection._sessionlessData.pending = false;
366
+ this.connection._sessionlessData.piggyback.encode(buf);
367
+ this.connection._sessionlessData.piggyback = null;
368
+ }
316
369
  }
317
370
 
318
371
  writePiggybackHeader(buf, functionCode) {
@@ -474,6 +527,42 @@ class Message {
474
527
  this.deferredErr = errors.getErr(...arguments);
475
528
  }
476
529
  }
530
+
531
+ _updateSessionlessTxnState(buf) {
532
+ const length = buf.length;
533
+ const sessionlessState = buf.readUInt8(length - 2);
534
+ const syncVersion = buf.readUInt8(length - 1);
535
+ let startedOnServer;
536
+
537
+ if (syncVersion == constants.TNS_TPC_TRANS_TRANSACTION_ID_SYNC_VERSION_1) {
538
+ // transactionId got unset for this session (txn ended or suspended)
539
+ if (sessionlessState &
540
+ constants.TNS_TPC_TRANS_TRANSACTION_ID_SYNC_UNSET) {
541
+ this.connection._sessionlessData = null;
542
+ this.connection._protocol.txnInProgress = false;
543
+ // transactionId got set for this session
544
+ } else if (sessionlessState &
545
+ constants.TNS_TPC_TRANS_TRANSACTION_ID_SYNC_SET) {
546
+
547
+ // transaction initialized by PL/SQL procedure
548
+ if (sessionlessState &
549
+ constants.TNS_TPC_TRANS_TRANSACTION_ID_SYNC_SERVER)
550
+ startedOnServer = true;
551
+ // transaction initialized by calling client API(RPC call)
552
+ else if (sessionlessState &
553
+ constants.TNS_TPC_TRANS_TRANSACTION_ID_SYNC_CLIENT)
554
+ startedOnServer = false;
555
+
556
+ this.connection._sessionlessData = {
557
+ startedOnServer: startedOnServer,
558
+ };
559
+ this.connection._protocol.txnInProgress = true;
560
+ }
561
+ } else
562
+ errors.throwErr(errors.ERR_INTERNAL, 'Unexpected TransactionId sync ' +
563
+ 'version in session sync piggyback.');
564
+ }
565
+
477
566
  }
478
567
 
479
568
  module.exports = Message;
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2022, 2024, Oracle and/or its affiliates.
1
+ // Copyright (c) 2022, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -394,6 +394,7 @@ const dataTypes = [
394
394
  [constants.TNS_DATA_TYPE_TNP, constants.TNS_DATA_TYPE_TNP, constants.TNS_TYPE_REP_UNIVERSAL],
395
395
  [constants.TNS_DATA_TYPE_OER, constants.TNS_DATA_TYPE_OER, constants.TNS_TYPE_REP_UNIVERSAL],
396
396
  [constants.TNS_DATA_TYPE_OAC, constants.TNS_DATA_TYPE_OAC, constants.TNS_TYPE_REP_UNIVERSAL],
397
+ [constants.TNS_DATA_TYPE_SESSSIGN, constants.TNS_DATA_TYPE_SESSSIGN, constants.TNS_TYPE_REP_UNIVERSAL],
397
398
  [constants.TNS_DATA_TYPE_VECTOR, constants.TNS_DATA_TYPE_VECTOR, constants.TNS_TYPE_REP_UNIVERSAL]
398
399
  ];
399
400
 
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
1
+ // Copyright (c) 2022, 2025, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -29,6 +29,7 @@
29
29
  const MessageWithData = require("./withData.js");
30
30
  const constants = require("../constants.js");
31
31
  const errors = require("../../../errors.js");
32
+ const TransactionSwitchMessage = require("./transactionSwitch.js");
32
33
 
33
34
  /**
34
35
  *
@@ -110,12 +111,18 @@ class ExecuteMessage extends MessageWithData {
110
111
  options |= constants.TNS_EXEC_OPTION_BATCH_ERRORS;
111
112
  }
112
113
  if (this.arrayDmlRowCounts) {
113
- dmlOptions = constants.TNS_EXEC_OPTION_DML_ROWCOUNTS;
114
+ dmlOptions |= constants.TNS_EXEC_OPTION_DML_ROWCOUNTS;
114
115
  }
115
116
  if (this.options.autoCommit) {
116
117
  options |= constants.TNS_EXEC_OPTION_COMMIT;
117
118
  }
118
- this.writePiggybacks(buf);
119
+
120
+ // In case SuspendOnSuccess is set for execute and a sessionless transaction
121
+ // is active we add a piggyback for post-call suspend
122
+ if (this.options.suspendOnSuccess) {
123
+ this._handleSuspendSessionless();
124
+ }
125
+
119
126
  this.writeFunctionHeader(buf);
120
127
  buf.writeUB4(options); // execute options
121
128
  buf.writeUB4(stmt.cursorId); // cursor id
@@ -258,7 +265,13 @@ class ExecuteMessage extends MessageWithData {
258
265
  }
259
266
  numIters = this.numExecs;
260
267
  }
261
- this.writePiggybacks(buf);
268
+
269
+ // In case SuspendOnSuccess is set for execute and a sessionless transaction
270
+ // is active we add a piggyback for post-call suspend
271
+ if (this.options.suspendOnSuccess) {
272
+ this._handleSuspendSessionless();
273
+ }
274
+
262
275
  this.writeFunctionHeader(buf);
263
276
  buf.writeUB4(stmt.cursorId);
264
277
  buf.writeUB4(numIters);
@@ -267,6 +280,35 @@ class ExecuteMessage extends MessageWithData {
267
280
  return params;
268
281
  }
269
282
 
283
+ //-------------------------------------------------------------------------
284
+ // _handleSuspendSessionless()
285
+ //
286
+ // Suspend the active sessionless transaction after DML execution if
287
+ // suspendOnSuccess option was given to execute()
288
+ //-------------------------------------------------------------------------
289
+ _handleSuspendSessionless() {
290
+ const sessionlessData = this.connection._sessionlessData;
291
+ // If sessionless transaction was started by server-call we cannot suspend
292
+ // using client API(RPC) calls
293
+ this.connection._validateSessionlessState();
294
+
295
+ if (!sessionlessData)
296
+ errors.throwErr(errors.ERR_SESSIONLESS_INACTIVE);
297
+ // If a piggybacks already exists it is most likely a start/resume for a
298
+ // sessionless transaction, add a post-call detach opcode to it
299
+ if (sessionlessData.piggyback) {
300
+ sessionlessData.piggyback.operation |=
301
+ constants.TNS_TPC_TXN_POST_DETACH;
302
+ } else {
303
+ const message = new TransactionSwitchMessage(this.connection);
304
+ message.operation = constants.TNS_TPC_TXN_POST_DETACH;
305
+ message.flags = constants.TNS_TPC_TRANS_SESSIONLESS;
306
+ message.messageType = constants.TNS_MSG_TYPE_PIGGYBACK;
307
+ sessionlessData.piggyback = message;
308
+ sessionlessData.pending = true;
309
+ }
310
+ }
311
+
270
312
  //-------------------------------------------------------------------------
271
313
  // encode()
272
314
  //