oracledb 6.8.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 (85) hide show
  1. package/README.md +39 -3
  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/aqMessage.js +10 -1
  15. package/lib/aqQueue.js +10 -6
  16. package/lib/configProviders/file.js +72 -0
  17. package/lib/connection.js +93 -4
  18. package/lib/dbObject.js +34 -4
  19. package/lib/errors.js +70 -35
  20. package/lib/impl/aqDeqOptions.js +10 -1
  21. package/lib/impl/connection.js +9 -0
  22. package/lib/impl/datahandlers/buffer.js +49 -0
  23. package/lib/impl/datahandlers/oson.js +7 -2
  24. package/lib/impl/pool.js +10 -1
  25. package/lib/impl/resultset.js +5 -4
  26. package/lib/impl/sodaDocument.js +5 -14
  27. package/lib/lob.js +9 -1
  28. package/lib/oracledb.js +152 -30
  29. package/lib/pool.js +199 -147
  30. package/lib/poolStatistics.js +4 -2
  31. package/lib/sodaCollection.js +12 -8
  32. package/lib/sodaDatabase.js +35 -8
  33. package/lib/sodaDocument.js +24 -6
  34. package/lib/sodaOperation.js +7 -6
  35. package/lib/thin/aq.js +475 -0
  36. package/lib/thin/connection.js +171 -14
  37. package/lib/thin/dbObject.js +8 -6
  38. package/lib/thin/lob.js +2 -3
  39. package/lib/thin/pool.js +105 -28
  40. package/lib/thin/protocol/capabilities.js +10 -4
  41. package/lib/thin/protocol/constants.js +90 -1
  42. package/lib/thin/protocol/encryptDecrypt.js +2 -2
  43. package/lib/thin/protocol/messages/aqArray.js +238 -0
  44. package/lib/thin/protocol/messages/aqBase.js +305 -0
  45. package/lib/thin/protocol/messages/aqDeq.js +164 -0
  46. package/lib/thin/protocol/messages/aqEnq.js +135 -0
  47. package/lib/thin/protocol/messages/auth.js +25 -27
  48. package/lib/thin/protocol/messages/base.js +98 -9
  49. package/lib/thin/protocol/messages/dataType.js +2 -1
  50. package/lib/thin/protocol/messages/execute.js +46 -4
  51. package/lib/thin/protocol/messages/lobOp.js +5 -22
  52. package/lib/thin/protocol/messages/logOff.js +0 -1
  53. package/lib/thin/protocol/messages/sessionRelease.js +0 -1
  54. package/lib/thin/protocol/messages/transactionSwitch.js +12 -4
  55. package/lib/thin/protocol/messages/withData.js +3 -3
  56. package/lib/thin/protocol/packet.js +6 -5
  57. package/lib/thin/protocol/protocol.js +2 -1
  58. package/lib/thin/sqlnet/connStrategy.js +2 -1
  59. package/lib/thin/sqlnet/navNodes.js +1 -1
  60. package/lib/thin/sqlnet/networkSession.js +10 -4
  61. package/lib/thin/sqlnet/packet.js +2 -1
  62. package/lib/thin/sqlnet/sessionAtts.js +2 -3
  63. package/lib/types.js +6 -11
  64. package/lib/util.js +72 -11
  65. package/lib/version.js +1 -1
  66. package/package/install.js +3 -3
  67. package/package/prunebinaries.js +6 -12
  68. package/package.json +2 -2
  69. package/{lib/configProviders/azure.js → plugins/configProviders/azure/index.js} +73 -63
  70. package/plugins/configProviders/azurevault/index.js +84 -0
  71. package/{lib → plugins}/configProviders/base.js +13 -3
  72. package/{lib/configProviders/ociobject.js → plugins/configProviders/ociobject/index.js} +43 -78
  73. package/plugins/configProviders/ocivault/index.js +100 -0
  74. package/plugins/token/extensionOci/index.js +71 -35
  75. package/build/Release/oracledb-6.8.0-darwin-arm64.node +0 -0
  76. package/build/Release/oracledb-6.8.0-darwin-arm64.node-buildinfo.txt +0 -1
  77. package/build/Release/oracledb-6.8.0-darwin-x64.node +0 -0
  78. package/build/Release/oracledb-6.8.0-darwin-x64.node-buildinfo.txt +0 -1
  79. package/build/Release/oracledb-6.8.0-js-buildinfo.txt +0 -1
  80. package/build/Release/oracledb-6.8.0-linux-arm64.node +0 -0
  81. package/build/Release/oracledb-6.8.0-linux-arm64.node-buildinfo.txt +0 -1
  82. package/build/Release/oracledb-6.8.0-linux-x64.node +0 -0
  83. package/build/Release/oracledb-6.8.0-linux-x64.node-buildinfo.txt +0 -1
  84. package/build/Release/oracledb-6.8.0-win32-x64.node +0 -0
  85. package/build/Release/oracledb-6.8.0-win32-x64.node-buildinfo.txt +0 -1
@@ -42,6 +42,7 @@ const types = require('../types.js');
42
42
  const errors = require("../errors.js");
43
43
  const messages = require('./protocol/messages');
44
44
  const StatementCache = require('./statementCache.js');
45
+ const { ThinQueueImpl } = require('./aq.js');
45
46
 
46
47
  const finalizationRegistry = new global.FinalizationRegistry((heldValue) => {
47
48
  heldValue.disconnect();
@@ -85,6 +86,9 @@ class ThinConnectionImpl extends ConnectionImpl {
85
86
  // immediate close of open socket on failure
86
87
  // exception won't be thrown to user
87
88
  this.nscon.disconnect(sqlNetConstants.NSFIMM);
89
+ // If connection is associated with a pool, we release it
90
+ if (this._pool)
91
+ await this._pool.release(this);
88
92
  }
89
93
  }
90
94
 
@@ -167,7 +171,7 @@ class ThinConnectionImpl extends ConnectionImpl {
167
171
  //
168
172
  // Calls the RPC that executes a SQL statement and returns the results.
169
173
  //---------------------------------------------------------------------------
170
- async _execute(statement, numIters, binds, options, executeManyFlag) {
174
+ async _execute(statement, numIters, binds, options, executeManyFlag, internalTempLobs = []) {
171
175
 
172
176
  // Throw error if executeMany is not called on a DML statement or PL/SQL
173
177
  if (executeManyFlag && statement.isQuery) {
@@ -191,7 +195,7 @@ class ThinConnectionImpl extends ConnectionImpl {
191
195
  }
192
196
 
193
197
  for (let i = 0; i < binds.length; i++) {
194
- await this._bind(statement, binds[i], i + 1);
198
+ await this._bind(statement, binds[i], i + 1, internalTempLobs);
195
199
  }
196
200
  if (statement.isPlSql && (options.batchErrors || options.dmlRowCounts)) {
197
201
  errors.throwErr(errors.ERR_EXEC_MODE_ONLY_FOR_DML);
@@ -299,13 +303,20 @@ class ThinConnectionImpl extends ConnectionImpl {
299
303
  }
300
304
 
301
305
  // process the type code.
306
+ let tempPrecision, tempScale;
302
307
  switch (attrType) {
303
308
  case constants.TNS_OBJ_TDS_TYPE_NUMBER:
304
- metaData.precision = buf.readInt8();
305
- metaData.scale = buf.readInt8();
309
+ tempPrecision = buf.readInt8();
310
+ tempScale = buf.readInt8();
311
+ if (tempPrecision || tempScale) {
312
+ metaData.precision = tempPrecision;
313
+ metaData.scale = tempScale;
314
+ }
306
315
  return types.DB_TYPE_NUMBER;
307
316
  case constants.TNS_OBJ_TDS_TYPE_FLOAT:
308
- metaData.precision = buf.readInt8();
317
+ tempPrecision = buf.readInt8();
318
+ if (tempPrecision)
319
+ metaData.precision = tempPrecision;
309
320
  return types.DB_TYPE_NUMBER;
310
321
  case constants.TNS_OBJ_TDS_TYPE_VARCHAR:
311
322
  case constants.TNS_OBJ_TDS_TYPE_CHAR:
@@ -433,11 +444,24 @@ class ThinConnectionImpl extends ConnectionImpl {
433
444
  case
434
445
  when data_type in
435
446
  ('CHAR', 'NCHAR', 'VARCHAR2', 'NVARCHAR2', 'RAW')
436
- then data_length
447
+ then data_length
437
448
  else 0
438
449
  end,
439
- nvl(data_precision, 0),
440
- nvl(data_scale, 0)
450
+ case
451
+ when data_precision is null and data_scale is null
452
+ then 0
453
+ when data_precision is null
454
+ then 38
455
+ else data_precision
456
+ end,
457
+ case
458
+ when data_precision is null and data_scale is null
459
+ and data_type = 'NUMBER'
460
+ then -127
461
+ when data_scale is null
462
+ then 0
463
+ else data_scale
464
+ end
441
465
  from all_tab_cols
442
466
  where owner = :owner
443
467
  and table_name = :name
@@ -670,10 +694,36 @@ class ThinConnectionImpl extends ConnectionImpl {
670
694
  this._partialDbObjectTypes.push(attr.typeClass);
671
695
  }
672
696
  } else {
673
- attr.type = types.getTypeByColumnTypeName(attrInfo.dataType);
674
- attr.maxSize = attrInfo.maxSize;
675
- attr.precision = attrInfo.dataPrecision;
676
- attr.scale = attrInfo.dataScale;
697
+ if (attrInfo.dataType === 'INTEGER' || attrInfo.dataType === 'SMALLINT') {
698
+ attr.type = types.DB_TYPE_NUMBER;
699
+ attr.precision = 38;
700
+ attr.scale = 0;
701
+ } else if (attrInfo.dataType === 'REAL') {
702
+ attr.type = types.DB_TYPE_NUMBER;
703
+ attr.precision = 63;
704
+ attr.scale = -127;
705
+ } else if (attrInfo.dataType === 'DOUBLE PRECISION' || attrInfo.dataType === 'FLOAT') {
706
+ attr.type = types.DB_TYPE_NUMBER;
707
+ // the database sends type name "FLOAT" instead of type name "REAL"
708
+ // when looking at table metadata but not when examining database
709
+ // object attribute metadata so account for that here
710
+ if (attrInfo.dataPrecision != null || attrInfo.dataPrecision != undefined)
711
+ attr.precision = Number(attrInfo.dataPrecision);
712
+ else
713
+ attr.precision = 126;
714
+ attr.scale = -127;
715
+ } else {
716
+ attr.type = types.getTypeByColumnTypeName(attrInfo.dataType);
717
+ if (attrInfo.maxSize != null || attrInfo.maxSize != undefined) {
718
+ const tempMaxSize = Number(attrInfo.maxSize);
719
+ if (tempMaxSize)
720
+ attr.maxSize = tempMaxSize;
721
+ }
722
+ if (attr.type === types.DB_TYPE_NUMBER) {
723
+ attr.precision = Number(attrInfo.dataPrecision);
724
+ attr.scale = Number(attrInfo.dataScale);
725
+ }
726
+ }
677
727
  }
678
728
  attributes.push(attr);
679
729
  }
@@ -732,6 +782,36 @@ class ThinConnectionImpl extends ConnectionImpl {
732
782
  return (this._pool) ? true : false;
733
783
  }
734
784
 
785
+ _postConnect(authMessage) {
786
+ let releaseNum;
787
+ let updateNum;
788
+ let portReleaseNum;
789
+ let portUpdateNum;
790
+
791
+ this.dbDomain = authMessage.sessionData['AUTH_SC_DB_DOMAIN'];
792
+ this.dbName = authMessage.sessionData['AUTH_DBNAME'];
793
+ this.maxOpenCursors = Number(authMessage.sessionData['AUTH_MAX_OPEN_CURSORS'] || 0);
794
+ this.serviceName = authMessage.sessionData['AUTH_SC_SERVICE_NAME'];
795
+ this.instanceName = authMessage.sessionData['AUTH_INSTANCENAME'];
796
+ this.maxIdentifierLength = Number(authMessage.sessionData['AUTH_MAX_IDEN_LENGTH'] || 30);
797
+ const fullVersionNum = Number(authMessage.sessionData['AUTH_VERSION_NO']);
798
+ const versionNum = (fullVersionNum >> 24) & 0xFF;
799
+ this.warning = authMessage.warning;
800
+ if (this._protocol.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_18_1_EXT_1) {
801
+ releaseNum = (fullVersionNum >> 16) & 0xFF;
802
+ updateNum = (fullVersionNum >> 12) & 0x0F;
803
+ portReleaseNum = (fullVersionNum >> 4) & 0xFF;
804
+ portUpdateNum = fullVersionNum & 0x0F;
805
+ } else {
806
+ releaseNum = (fullVersionNum >> 20) & 0x0F;
807
+ updateNum = (fullVersionNum >> 12) & 0xFF;
808
+ portReleaseNum = (fullVersionNum >> 8) & 0x0F;
809
+ portUpdateNum = fullVersionNum & 0xFF;
810
+ }
811
+ this.serverVersionString = versionNum + '.' + releaseNum + '.' + updateNum + '.' + portReleaseNum + '.' + portUpdateNum;
812
+ this.serverVersion = versionNum * 100000000 + releaseNum * 1000000 + updateNum * 10000 + portReleaseNum * 100 + portUpdateNum * 1;
813
+ }
814
+
735
815
  /**
736
816
  *
737
817
  * @param {object} params Configuration of the connection
@@ -772,6 +852,7 @@ class ThinConnectionImpl extends ConnectionImpl {
772
852
  this.remoteAddress = '';
773
853
  this.comboKey = null; // used in changePassword API
774
854
  this.tpcContext = null;
855
+ this._sessionlessData = null;
775
856
 
776
857
  this.nscon = new nsi();
777
858
  finalizationRegistry.register(this, this.nscon);
@@ -845,6 +926,7 @@ class ThinConnectionImpl extends ConnectionImpl {
845
926
  if (!params.externalAuth) { // non-token Authentication
846
927
  await this._protocol._processMessage(authMessage); // OAUTH
847
928
  }
929
+ this._postConnect(authMessage);
848
930
  } catch (err) {
849
931
  this.nscon.disconnect();
850
932
  throw err;
@@ -885,7 +967,7 @@ class ThinConnectionImpl extends ConnectionImpl {
885
967
  //---------------------------------------------------------------------------
886
968
  // Binds the values by user to the statement object
887
969
  //---------------------------------------------------------------------------
888
- async _bind(stmt, variable, pos = 0) {
970
+ async _bind(stmt, variable, pos = 0, internalTempLobs = []) {
889
971
  const bindInfoDict = stmt.bindInfoDict;
890
972
  const bindInfoList = stmt.bindInfoList;
891
973
 
@@ -927,6 +1009,7 @@ class ThinConnectionImpl extends ConnectionImpl {
927
1009
  if (val && val.length > 0) {
928
1010
  const lobImpl = new ThinLobImpl();
929
1011
  await lobImpl.create(this, variable.type);
1012
+ internalTempLobs.push(lobImpl);
930
1013
  await lobImpl.write(1, val);
931
1014
  variable.values[index] = lobImpl;
932
1015
  } else {
@@ -1021,12 +1104,19 @@ class ThinConnectionImpl extends ConnectionImpl {
1021
1104
  //---------------------------------------------------------------------------
1022
1105
  async execute(sql, numIters, binds, options, executeManyFlag) {
1023
1106
  const statement = this._prepare(sql, options);
1107
+ const internalTempLobs = [];
1024
1108
  try {
1025
1109
  return await this._execute(statement, numIters, binds, options,
1026
- executeManyFlag);
1110
+ executeManyFlag, internalTempLobs);
1027
1111
  } catch (err) {
1028
1112
  this._returnStatement(statement);
1029
1113
  throw err;
1114
+ } finally {
1115
+ // Add to freelist which will be sent in piggyback fashion
1116
+ for (const lob of internalTempLobs) {
1117
+ this._tempLobsToClose.push(lob._locator);
1118
+ this._tempLobsTotalSize += lob._locator.length;
1119
+ }
1030
1120
  }
1031
1121
  }
1032
1122
 
@@ -1233,6 +1323,55 @@ class ThinConnectionImpl extends ConnectionImpl {
1233
1323
  errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, message.state);
1234
1324
  }
1235
1325
  }
1326
+ //---------------------------------------------------------------------------
1327
+ // Ensure no sessionless transaction was started through server procedure
1328
+ //---------------------------------------------------------------------------
1329
+ _validateSessionlessState() {
1330
+ if (this._sessionlessData?.startedOnServer) {
1331
+ errors.throwErr(errors.ERR_SESSIONLESS_DIFFERING_METHODS);
1332
+ }
1333
+ }
1334
+
1335
+ //---------------------------------------------------------------------------
1336
+ // Begin/Resume a sessionless transaction with provided transactionId
1337
+ //---------------------------------------------------------------------------
1338
+ async startSessionlessTransaction(transactionId, timeout, flags,
1339
+ deferRoundTrip) {
1340
+ this._validateSessionlessState();
1341
+ if (this._sessionlessData)
1342
+ errors.throwErr(errors.ERR_SESSIONLESS_ALREADY_ACTIVE);
1343
+ const message = new messages.TransactionSwitchMessage(this);
1344
+ message.xid = {
1345
+ globalTransactionId: transactionId,
1346
+ branchQualifier: "",
1347
+ formatId: constants.TNS_TPC_TRANS_SESSIONLESS_FORMAT
1348
+ };
1349
+ message.timeout = timeout;
1350
+ message.operation = constants.TNS_TPC_TXN_START;
1351
+ message.flags = constants.TNS_TPC_TRANS_SESSIONLESS | flags;
1352
+ if (deferRoundTrip) {
1353
+ message.messageType = constants.TNS_MSG_TYPE_PIGGYBACK;
1354
+ this._sessionlessData = {
1355
+ piggyback: message,
1356
+ pending: true
1357
+ };
1358
+ } else {
1359
+ await this._protocol._processMessage(message);
1360
+ }
1361
+ }
1362
+
1363
+ //---------------------------------------------------------------------------
1364
+ // Suspend the active sessionless transaction
1365
+ //---------------------------------------------------------------------------
1366
+ async suspendSessionlessTransaction() {
1367
+ this._validateSessionlessState();
1368
+ if (!this._sessionlessData)
1369
+ errors.throwErr(errors.ERR_SESSIONLESS_INACTIVE);
1370
+ const message = new messages.TransactionSwitchMessage(this);
1371
+ message.operation = constants.TNS_TPC_TXN_DETACH;
1372
+ message.flags = constants.TNS_TPC_TRANS_SESSIONLESS;
1373
+ await this._protocol._processMessage(message);
1374
+ }
1236
1375
 
1237
1376
 
1238
1377
  //---------------------------------------------------------------------------
@@ -1273,6 +1412,14 @@ class ThinConnectionImpl extends ConnectionImpl {
1273
1412
  return this.internalName;
1274
1413
  }
1275
1414
 
1415
+ //---------------------------------------------------------------------------
1416
+ // Returns the Logical Transaction ID (ltxid) associated with the connection.
1417
+ // Used with Oracle Database Transaction Guard feature.
1418
+ //---------------------------------------------------------------------------
1419
+ getLTXID() {
1420
+ return this._ltxid;
1421
+ }
1422
+
1276
1423
  //---------------------------------------------------------------------------
1277
1424
  // Returns the Oracle Database domain name associated with the connection.
1278
1425
  //---------------------------------------------------------------------------
@@ -1343,5 +1490,15 @@ class ThinConnectionImpl extends ConnectionImpl {
1343
1490
  getWarning() {
1344
1491
  return this.warning;
1345
1492
  }
1493
+
1494
+ //---------------------------------------------------------------------------
1495
+ // getQueue()
1496
+ //
1497
+ // Returns a queue for use with advanced queuing.
1498
+ //---------------------------------------------------------------------------
1499
+ getQueue(name, payloadTypeClass, payloadType) {
1500
+ return new ThinQueueImpl(this, name, payloadTypeClass, payloadType);
1501
+ }
1346
1502
  }
1503
+
1347
1504
  module.exports = ThinConnectionImpl;
@@ -55,10 +55,10 @@ class DbObjectPickleBuffer extends GrowableBuffer {
55
55
  // Reads the next byte and checks to see if the value is atomically null. If
56
56
  // not, the byte is returned to the buffer for further processing.
57
57
  //---------------------------------------------------------------------------
58
- getIsAtomicNull() {
58
+ getIsAtomicNull(isCollection) {
59
59
  const value = this.readUInt8();
60
60
  if (value === constants.TNS_OBJ_ATOMIC_NULL ||
61
- value === constants.TNS_NULL_LENGTH_INDICATOR) {
61
+ (isCollection && value === constants.TNS_NULL_LENGTH_INDICATOR)) {
62
62
  return true;
63
63
  }
64
64
  this.pos -= 1;
@@ -365,7 +365,7 @@ class ThinDbObjectImpl extends DbObjectImpl {
365
365
  // Unpacks a single value and returns it.
366
366
  //---------------------------------------------------------------------------
367
367
  _unpackValue(buf, type, typeClass) {
368
- let isNull, obj, value;
368
+ let isNull, obj, value, isCollection;
369
369
  switch (type) {
370
370
  case types.DB_TYPE_NUMBER:
371
371
  return buf.readOracleNumber();
@@ -396,14 +396,16 @@ class ThinDbObjectImpl extends DbObjectImpl {
396
396
  return buf.readBool();
397
397
  case types.DB_TYPE_OBJECT:
398
398
  case types.DB_TYPE_XMLTYPE:
399
- isNull = buf.getIsAtomicNull();
399
+ obj = new ThinDbObjectImpl(typeClass);
400
+ isCollection = obj._objType.isCollection ||
401
+ this._objType.isCollection;
402
+ isNull = buf.getIsAtomicNull(isCollection);
400
403
  if (isNull)
401
404
  return null;
402
- obj = new ThinDbObjectImpl(typeClass);
403
405
  if (obj._objType.isXmlType) {
404
406
  return readXML(obj._objType._connection, buf.readBytesWithLength());
405
407
  }
406
- if (obj._objType.isCollection || this._objType.isCollection) {
408
+ if (isCollection) {
407
409
  obj.packedData = Buffer.from(buf.readBytesWithLength());
408
410
  } else {
409
411
  obj._unpackDataFromBuf(buf);
package/lib/thin/lob.js CHANGED
@@ -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
  //
@@ -220,10 +220,9 @@ class ThinLobImpl extends LobImpl {
220
220
  const options = {
221
221
  operation: constants.TNS_LOB_OP_CREATE_TEMP,
222
222
  sourceLobImpl: this,
223
- amount: constants.TNS_DURATION_SESSION,
224
223
  destOffset: dbType._oraTypeNum,
225
224
  sourceOffset: dbType._csfrm,
226
- sendAmount: true
225
+ destLength: constants.TNS_DURATION_SESSION
227
226
  };
228
227
  await this._sendMessage(options);
229
228
  await this._getChunkSizeAsync();
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
  //
@@ -81,6 +81,7 @@ class Capabilities {
81
81
  constants.TNS_CCAP_O9LOGON_LONG_PASSWORD;
82
82
  this.compileCaps[constants.TNS_CCAP_FIELD_VERSION] = this.ttcFieldVersion;
83
83
  this.compileCaps[constants.TNS_CCAP_SERVER_DEFINE_CONV] = 1;
84
+ this.compileCaps[constants.TNS_CCAP_DEQUEUE_WITH_SELECTOR] = 1;
84
85
  this.compileCaps[constants.TNS_CCAP_TTC1] =
85
86
  constants.TNS_CCAP_FAST_BVEC | constants.TNS_CCAP_END_OF_CALL_STATUS |
86
87
  constants.TNS_CCAP_IND_RCD;
@@ -104,20 +105,25 @@ class Capabilities {
104
105
  constants.TNS_CCAP_LOB2_QUASI | constants.TNS_CCAP_LOB2_2GB_PREFETCH;
105
106
  this.compileCaps[constants.TNS_CCAP_TTC3] =
106
107
  constants.TNS_CCAP_IMPLICIT_RESULTS | constants.TNS_CCAP_BIG_CHUNK_CLR |
107
- constants.TNS_CCAP_KEEP_OUT_ORDER;
108
+ constants.TNS_CCAP_KEEP_OUT_ORDER | constants.TNS_CCAP_LTXID;
109
+ this.compileCaps[constants.TNS_CCAP_OCI3] = constants.TNS_CCAP_OCI3_OCSSYNC;
108
110
  this.compileCaps[constants.TNS_CCAP_TTC2] = constants.TNS_CCAP_ZLNP;
109
111
  this.compileCaps[constants.TNS_CCAP_OCI2] = constants.TNS_CCAP_DRCP;
110
112
  this.compileCaps[constants.TNS_CCAP_CLIENT_FN] =
111
113
  constants.TNS_CCAP_CLIENT_FN_MAX;
114
+ this.compileCaps[constants.TNS_CCAP_SESS_SIGNATURE_VERSION] =
115
+ constants.TNS_CCAP_FIELD_VERSION_12_2;
112
116
  this.compileCaps[constants.TNS_CCAP_TTC4] =
113
117
  constants.TNS_CCAP_INBAND_NOTIFICATION;
114
118
  if (nscon.endOfRequestSupport) {
115
119
  this.compileCaps[constants.TNS_CCAP_TTC4] |= constants.TNS_CCAP_END_OF_REQUEST;
116
120
  }
117
121
  this.compileCaps[constants.TNS_CCAP_CTB_FEATURE_BACKPORT] =
118
- constants.TNS_CCAP_CTB_IMPLICIT_POOL;
122
+ constants.TNS_CCAP_CTB_IMPLICIT_POOL |
123
+ constants.TNS_CCAP_CTB_OAUTH_MSG_ON_ERR;
119
124
  this.compileCaps[constants.TNS_CCAP_TTC5] =
120
- constants.TNS_CCAP_VECTOR_SUPPORT;
125
+ constants.TNS_CCAP_VECTOR_SUPPORT |
126
+ constants.TNS_CCAP_TTC5_SESSIONLESS_TXNS;
121
127
  this.compileCaps[constants.TNS_CCAP_VECTOR_FEATURES] =
122
128
  constants.TNS_CCAP_VECTOR_FEATURE_BINARY | constants.TNS_CCAP_VECTOR_FEATURE_SPARSE;
123
129
  }