oracledb 6.9.0 → 7.0.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 (100) hide show
  1. package/LICENSE.txt +1 -1
  2. package/NOTICE.txt +1 -1
  3. package/README.md +9 -9
  4. package/build/Release/oracledb-7.0.0-darwin-arm64.node +0 -0
  5. package/build/Release/oracledb-7.0.0-darwin-arm64.node-buildinfo.txt +1 -0
  6. package/build/Release/oracledb-7.0.0-js-buildinfo.txt +1 -0
  7. package/build/Release/oracledb-7.0.0-linux-arm64.node +0 -0
  8. package/build/Release/oracledb-7.0.0-linux-arm64.node-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-7.0.0-linux-x64.node +0 -0
  10. package/build/Release/oracledb-7.0.0-linux-x64.node-buildinfo.txt +1 -0
  11. package/build/Release/oracledb-7.0.0-win32-x64.node +0 -0
  12. package/build/Release/oracledb-7.0.0-win32-x64.node-buildinfo.txt +1 -0
  13. package/eslint.config.js +39 -0
  14. package/lib/aqDeqOptions.js +18 -1
  15. package/lib/aqQueue.js +10 -6
  16. package/lib/connection.js +418 -39
  17. package/lib/dbObject.js +25 -4
  18. package/lib/endUserSecurityContext.js +176 -0
  19. package/lib/errors.js +99 -8
  20. package/lib/impl/aqDeqOptions.js +10 -1
  21. package/lib/impl/connection.js +76 -2
  22. package/lib/impl/datahandlers/buffer.js +68 -5
  23. package/lib/impl/datahandlers/oson.js +7 -2
  24. package/lib/impl/datahandlers/vector.js +25 -5
  25. package/lib/impl/lob.js +9 -0
  26. package/lib/impl/parserHelpers.js +443 -0
  27. package/lib/impl/resultset.js +1 -1
  28. package/lib/impl/sodaDocument.js +5 -14
  29. package/lib/lob.js +27 -14
  30. package/lib/obfuscation.js +97 -0
  31. package/lib/oracledb.js +163 -49
  32. package/lib/parser.js +204 -0
  33. package/lib/pipeline.js +113 -0
  34. package/lib/pool.js +203 -147
  35. package/lib/poolStatistics.js +1 -1
  36. package/lib/resultset.js +42 -21
  37. package/lib/settings.js +1 -0
  38. package/lib/sodaCollection.js +12 -8
  39. package/lib/sodaDatabase.js +35 -8
  40. package/lib/sodaDocument.js +24 -6
  41. package/lib/sodaOperation.js +7 -6
  42. package/lib/thin/aq.js +475 -0
  43. package/lib/thin/connection.js +518 -54
  44. package/lib/thin/dbObject.js +7 -1
  45. package/lib/thin/lob.js +20 -1
  46. package/lib/thin/pool.js +42 -62
  47. package/lib/thin/protocol/capabilities.js +21 -4
  48. package/lib/thin/protocol/constants.js +143 -2
  49. package/lib/thin/protocol/encryptDecrypt.js +45 -10
  50. package/lib/thin/protocol/messages/aqArray.js +238 -0
  51. package/lib/thin/protocol/messages/aqBase.js +305 -0
  52. package/lib/thin/protocol/messages/aqDeq.js +164 -0
  53. package/lib/thin/protocol/messages/aqEnq.js +135 -0
  54. package/lib/thin/protocol/messages/auth.js +18 -28
  55. package/lib/thin/protocol/messages/base.js +245 -4
  56. package/lib/thin/protocol/messages/dataType.js +8 -3
  57. package/lib/thin/protocol/messages/directPathLoadStream.js +377 -0
  58. package/lib/thin/protocol/messages/directPathOp.js +65 -0
  59. package/lib/thin/protocol/messages/directPathPrepare.js +158 -0
  60. package/lib/thin/protocol/messages/endPipeline.js +56 -0
  61. package/lib/thin/protocol/messages/execute.js +9 -7
  62. package/lib/thin/protocol/messages/index.js +9 -1
  63. package/lib/thin/protocol/messages/protocol.js +2 -2
  64. package/lib/thin/protocol/messages/withData.js +10 -142
  65. package/lib/thin/protocol/packet.js +120 -28
  66. package/lib/thin/protocol/protocol.js +110 -25
  67. package/lib/thin/protocol/utils.js +1 -26
  68. package/lib/thin/resultSet.js +13 -19
  69. package/lib/thin/sqlnet/ezConnectResolver.js +2 -2
  70. package/lib/thin/sqlnet/networkSession.js +63 -41
  71. package/lib/thin/sqlnet/ntTcp.js +24 -0
  72. package/lib/thin/sqlnet/nvStrToNvPair.js +50 -25
  73. package/lib/thin/sqlnet/packet.js +25 -4
  74. package/lib/thin/sqlnet/paramParser.js +10 -7
  75. package/lib/thin/statementCache.js +2 -4
  76. package/lib/traceHandler.js +59 -2
  77. package/lib/transformer.js +8 -3
  78. package/lib/types.js +36 -22
  79. package/lib/util.js +6 -19
  80. package/lib/version.js +3 -3
  81. package/package/install.js +5 -5
  82. package/package/prunebinaries.js +1 -1
  83. package/package.json +1 -1
  84. package/plugins/configProviders/awsCommon.js +125 -0
  85. package/plugins/configProviders/awss3/index.js +138 -0
  86. package/plugins/configProviders/awssecretsmanager/index.js +147 -0
  87. package/plugins/configProviders/azure/index.js +7 -5
  88. package/plugins/token/extensionAzure/index.js +0 -1
  89. package/plugins/token/extensionOci/index.js +0 -1
  90. package/build/Release/oracledb-6.9.0-darwin-arm64.node +0 -0
  91. package/build/Release/oracledb-6.9.0-darwin-arm64.node-buildinfo.txt +0 -1
  92. package/build/Release/oracledb-6.9.0-darwin-x64.node +0 -0
  93. package/build/Release/oracledb-6.9.0-darwin-x64.node-buildinfo.txt +0 -1
  94. package/build/Release/oracledb-6.9.0-js-buildinfo.txt +0 -1
  95. package/build/Release/oracledb-6.9.0-linux-arm64.node +0 -0
  96. package/build/Release/oracledb-6.9.0-linux-arm64.node-buildinfo.txt +0 -1
  97. package/build/Release/oracledb-6.9.0-linux-x64.node +0 -0
  98. package/build/Release/oracledb-6.9.0-linux-x64.node-buildinfo.txt +0 -1
  99. package/build/Release/oracledb-6.9.0-win32-x64.node +0 -0
  100. package/build/Release/oracledb-6.9.0-win32-x64.node-buildinfo.txt +0 -1
@@ -372,12 +372,23 @@ class BaseBuffer {
372
372
  //
373
373
  // Reads a boolean value from the buffer and returns a Boolean.
374
374
  //---------------------------------------------------------------------------
375
- readBool() {
375
+ readBool(fromDBObject) {
376
376
  const buf = this.readBytesWithLength();
377
377
  if (!buf) {
378
378
  return null;
379
379
  }
380
- return (buf[buf.length - 1] === 1);
380
+ if (fromDBObject) {
381
+ // inside a database object, the boolean is represented as a
382
+ // big endian 32-bit integer (either 0 or 1)
383
+ return (buf[buf.length - 1] === 1);
384
+ } else {
385
+ // outside a database object, the boolean is represented as an
386
+ // encoded integer (0x00 for false and 0x0101 for true); older
387
+ // versions like 12.1 also return 0x8101 (-1) for null values
388
+ // but this can safely be ignored as the null indicator is sent
389
+ // as a separate field afterwards.
390
+ return (buf[0] === 1);
391
+ }
381
392
  }
382
393
 
383
394
  //---------------------------------------------------------------------------
@@ -397,6 +408,18 @@ class BaseBuffer {
397
408
  return buf;
398
409
  }
399
410
 
411
+ //---------------------------------------------------------------------------
412
+ // readBytesAndLength()
413
+ //
414
+ // Reads the UB4 length from the buffer and then returns a Buffer containing
415
+ // the specified number of bytes.
416
+ //---------------------------------------------------------------------------
417
+ readBytesAndLength() {
418
+ const numBytes = this.readUB4();
419
+ if (numBytes > 0)
420
+ return this.readBytesWithLength();
421
+ }
422
+
400
423
  //---------------------------------------------------------------------------
401
424
  // readBytesWithLength()
402
425
  //
@@ -548,6 +571,17 @@ class BaseBuffer {
548
571
  return Buffer.from(buf).swap16().toString('utf16le');
549
572
  }
550
573
 
574
+ //---------------------------------------------------------------------------
575
+ // readStrAndLength()
576
+ //
577
+ // Reads a length from the buffer and then, if the length is non-zero, reads
578
+ // string from the buffer and returns it.
579
+ //---------------------------------------------------------------------------
580
+ readStrAndLength() {
581
+ const buf = this.readBytesAndLength();
582
+ return buf ? buf.toString() : null;
583
+ }
584
+
551
585
  //---------------------------------------------------------------------------
552
586
  // readUB2()
553
587
  //
@@ -897,7 +931,7 @@ class BaseBuffer {
897
931
  //
898
932
  // Writes the number (in string form) in Oracle Number format to the buffer.
899
933
  //---------------------------------------------------------------------------
900
- writeOracleNumber(value) {
934
+ writeOracleNumber(value, writeLength = true) {
901
935
 
902
936
  // determine if number is negative
903
937
  let isNegative = false;
@@ -961,8 +995,11 @@ class BaseBuffer {
961
995
  } else if (value.length === 0 && exponent === 0) {
962
996
  exponentOnWire = 128;
963
997
  }
964
- let pos = this.reserveBytes(numPairs + 2 + appendSentinel);
965
- this.buf[pos++] = numPairs + 1 + appendSentinel;
998
+ let pos = this.reserveBytes(numPairs + 1 + writeLength + appendSentinel);
999
+
1000
+ if (writeLength) {
1001
+ this.buf[pos++] = numPairs + 1 + appendSentinel;
1002
+ }
966
1003
  this.buf[pos++] = exponentOnWire;
967
1004
  for (let i = 0; i < value.length; i += 2) {
968
1005
  const base100Digit = Number(value.substring(i, i + 2));
@@ -997,6 +1034,32 @@ class BaseBuffer {
997
1034
  this.buf.writeInt32BE(n, pos);
998
1035
  }
999
1036
 
1037
+ //---------------------------------------------------------------------------
1038
+ // writeSB4()
1039
+ //
1040
+ // Writes a signed integer (up to 4 bytes in length) in variable length
1041
+ // format to the buffer.
1042
+ //---------------------------------------------------------------------------
1043
+ writeSB4(value) {
1044
+ let sign = 0;
1045
+ if (value < 0) {
1046
+ value = -value;
1047
+ sign = 0x80;
1048
+ }
1049
+ if (value === 0) {
1050
+ this.writeUInt8(0);
1051
+ } else if (value <= 0xff) {
1052
+ this.writeUInt8(1 | sign);
1053
+ this.writeUInt8(value);
1054
+ } else if (value <= 0xffff) {
1055
+ this.writeUInt8(2 | sign);
1056
+ this.writeUInt16BE(value);
1057
+ } else {
1058
+ this.writeUInt8(4 | sign);
1059
+ this.writeUInt32BE(value);
1060
+ }
1061
+ }
1062
+
1000
1063
  //---------------------------------------------------------------------------
1001
1064
  // writeUB4()
1002
1065
  //
@@ -549,8 +549,13 @@ class OsonTreeSegment extends GrowableBuffer {
549
549
 
550
550
  // handle numbers
551
551
  } else if (typeof value === 'number') {
552
- this.writeUInt8(constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8);
553
- this.writeOracleNumber(value.toString());
552
+ if (Number.isNaN(value)) {
553
+ this.writeUInt8(constants.TNS_JSON_TYPE_BINARY_DOUBLE);
554
+ this.writeBinaryDouble(value);
555
+ } else {
556
+ this.writeUInt8(constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8);
557
+ this.writeOracleNumber(value.toString());
558
+ }
554
559
 
555
560
  // handle strings
556
561
  } else if (typeof value === 'string') {
@@ -37,6 +37,21 @@ const types = require("../../types.js");
37
37
  */
38
38
  class VectorDecoder extends BaseBuffer {
39
39
 
40
+ //---------------------------------------------------------------------------
41
+ // _verifyVectorDataSize()
42
+ //
43
+ // Verifies that the VECTOR image contains enough bytes for the declared
44
+ // element count before allocating a typed array.
45
+ //---------------------------------------------------------------------------
46
+ _verifyVectorDataSize(numElements, elementSize) {
47
+ const numBytes = numElements * elementSize;
48
+ const numBytesLeft = this.numBytesLeft();
49
+ if (numBytes > numBytesLeft) {
50
+ errors.throwErr(errors.ERR_UNEXPECTED_END_OF_DATA, numBytes,
51
+ numBytesLeft);
52
+ }
53
+ }
54
+
40
55
  //---------------------------------------------------------------------------
41
56
  // decode()
42
57
  //
@@ -56,7 +71,7 @@ class VectorDecoder extends BaseBuffer {
56
71
  const flags = this.readUInt16BE();
57
72
  const vectorFormat = this.readUInt8();
58
73
  let numElements = this.readUInt32BE();
59
- let elementSize, value, sparseValue;
74
+ let arrayType, elementSize, sparseValue;
60
75
 
61
76
  // For binary vector, NORM will never be present but space for NORM is reserved to keep
62
77
  // same header lengh across the different vector formats.
@@ -69,6 +84,7 @@ class VectorDecoder extends BaseBuffer {
69
84
  sparseValue = {};
70
85
  sparseValue.numDimensions = numElements; // vector dimensions.
71
86
  numElements = this.readUInt16BE(); // actual elements.
87
+ this._verifyVectorDataSize(numElements, 4);
72
88
  sparseValue.indices = new Uint32Array(numElements);
73
89
  for (let i = 0; i < numElements; i++) {
74
90
  sparseValue.indices[i] = this.readUInt32BE();
@@ -76,22 +92,26 @@ class VectorDecoder extends BaseBuffer {
76
92
  }
77
93
 
78
94
  if (vectorFormat === constants.VECTOR_FORMAT_FLOAT32) {
95
+ arrayType = Float32Array;
79
96
  elementSize = 4;
80
- value = new Float32Array(numElements);
81
97
  } else if (vectorFormat === constants.VECTOR_FORMAT_FLOAT64) {
98
+ arrayType = Float64Array;
82
99
  elementSize = 8;
83
- value = new Float64Array(numElements);
84
100
  } else if (vectorFormat === constants.VECTOR_FORMAT_INT8) {
101
+ arrayType = Int8Array;
85
102
  elementSize = 1;
86
- value = new Int8Array(numElements);
87
103
  } else if (vectorFormat === constants.VECTOR_FORMAT_BINARY) {
104
+ arrayType = Uint8Array;
88
105
  elementSize = 1;
89
106
  // The number of dimensions are assumed to be multiple of 8.
107
+ if (numElements % 8 !== 0)
108
+ errors.throwErr(errors.ERR_UNEXPECTED_DATA, numElements);
90
109
  numElements = numElements / 8;
91
- value = new Uint8Array(numElements);
92
110
  } else {
93
111
  errors.throwErr(errors.ERR_VECTOR_FORMAT_NOT_SUPPORTED, vectorFormat);
94
112
  }
113
+ this._verifyVectorDataSize(numElements, elementSize);
114
+ const value = new arrayType(numElements);
95
115
 
96
116
  if (sparseFormat) {
97
117
  sparseValue.values = value;
package/lib/impl/lob.js CHANGED
@@ -81,6 +81,15 @@ class LobImpl extends BaseImpl {
81
81
  errors.throwNotImplemented("getting directory and filename of LOB(BFILE)");
82
82
  }
83
83
 
84
+ //---------------------------------------------------------------------------
85
+ // trim()
86
+ //
87
+ // trims non-BFILE LOBs to a smaller size.
88
+ //----------------------------------------------------------------------------
89
+ trim() {
90
+ errors.throwNotImplemented("trimming LOBs");
91
+ }
92
+
84
93
  //---------------------------------------------------------------------------
85
94
  // read()
86
95
  //
@@ -0,0 +1,443 @@
1
+ // Copyright (c) 2026, Oracle and/or its affiliates.
2
+
3
+ //-----------------------------------------------------------------------------
4
+ //
5
+ // This software is dual-licensed to you under the Universal Permissive License
6
+ // (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7
+ // 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
8
+ // either license.
9
+ //
10
+ // If you elect to accept the software under the Apache License, Version 2.0,
11
+ // the following applies:
12
+ //
13
+ // Licensed under the Apache License, Version 2.0 (the "License");
14
+ // you may not use this file except in compliance with the License.
15
+ // You may obtain a copy of the License at
16
+ //
17
+ // https://www.apache.org/licenses/LICENSE-2.0
18
+ //
19
+ // Unless required by applicable law or agreed to in writing, software
20
+ // distributed under the License is distributed on an "AS IS" BASIS,
21
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ // See the License for the specific language governing permissions and
23
+ // limitations under the License.
24
+ //
25
+ //-----------------------------------------------------------------------------
26
+
27
+ 'use strict';
28
+
29
+ const { createNVPair } = require('../thin/sqlnet/nvStrToNvPair.js');
30
+ const EZConnectResolver = require('../thin/sqlnet/ezConnectResolver.js');
31
+ const { NLParamParser, tnsnamesFilePath } = require('../thin/sqlnet/paramParser.js');
32
+ const errors = require('../errors.js');
33
+ const process = require('process');
34
+
35
+ const NJS_PREFIX = 'njs.';
36
+ const RHS_LIST = 2;
37
+ const NUMBER_KEYS = new Set([
38
+ 'poolIncrement', 'poolMax', 'poolMin', 'poolTimeout', 'queueTimeout',
39
+ 'maxLifetimeSession', 'poolMaxPerShard', 'poolPingInterval',
40
+ 'poolPingTimeout', 'stmtCacheSize', 'mode', 'getMode'
41
+ ]);
42
+
43
+ const BOOLEAN_KEYS = new Set([
44
+ 'events', 'externalAuth', 'homogeneous', 'useTcpFastOpen',
45
+ 'disableOob', 'sodaMetaDataCache'
46
+ ]);
47
+
48
+ const STRING_KEYS = new Set([
49
+ 'driverName', 'edition', 'machine', 'osUser', 'program', 'terminal'
50
+ ]);
51
+
52
+ const nlParamParser = new NLParamParser();
53
+ const childLookupCache = new WeakMap();
54
+
55
+ /**
56
+ * Formats a boolean value as a string 'true' or 'false'.
57
+ * @param {boolean} value - The boolean value to format.
58
+ * @returns {string} 'true' if value is true, 'false' otherwise.
59
+ */
60
+ function formatBooleanTrueFalse(value) {
61
+ return value ? 'true' : 'false';
62
+ }
63
+
64
+ const CONNECT_OVERRIDES = [
65
+ // DESCRIPTION-level parameters
66
+ { opt: 'sdu', section: ['DESCRIPTION', 'SDU'], fmt: String },
67
+ { opt: 'failover', section: ['DESCRIPTION', 'FAILOVER'] },
68
+ { opt: 'loadBalance', section: ['DESCRIPTION', 'LOAD_BALANCE'] },
69
+ { opt: 'retryCount', section: ['DESCRIPTION', 'RETRY_COUNT'], fmt: String },
70
+ { opt: 'retryDelay', section: ['DESCRIPTION', 'RETRY_DELAY'], fmt: String },
71
+ { opt: 'sourceRoute', section: ['DESCRIPTION', 'SOURCE_ROUTE'] },
72
+ { opt: 'transportConnectTimeout', section: ['DESCRIPTION', 'TRANSPORT_CONNECT_TIMEOUT'], fmt: String },
73
+ { opt: 'useSNI', section: ['DESCRIPTION', 'USE_SNI'], fmt: formatBooleanTrueFalse },
74
+
75
+ // CONNECT_DATA parameters
76
+ { opt: 'poolBoundary', section: ['DESCRIPTION', 'CONNECT_DATA', 'POOL_BOUNDARY'] },
77
+ { opt: 'poolConnectionClass', section: ['DESCRIPTION', 'CONNECT_DATA', 'POOL_CONNECTION_CLASS'] },
78
+ { opt: 'poolName', section: ['DESCRIPTION', 'CONNECT_DATA', 'POOL_NAME'] },
79
+ { opt: 'poolPurity', section: ['DESCRIPTION', 'CONNECT_DATA', 'POOL_PURITY'] },
80
+ { opt: 'expireTime', section: ['DESCRIPTION', 'CONNECT_DATA', 'EXPIRE_TIME'], fmt: String },
81
+
82
+ // SECURITY parameters
83
+ { opt: 'walletLocation', section: ['DESCRIPTION', 'SECURITY', 'MY_WALLET_DIRECTORY'] },
84
+ { opt: 'sslServerCertDn', section: ['DESCRIPTION', 'SECURITY', 'SSL_SERVER_CERT_DN'] },
85
+ { opt: 'sslServerDnMatch', section: ['DESCRIPTION', 'SECURITY', 'SSL_SERVER_DN_MATCH'] },
86
+
87
+ // ADDRESS parameters
88
+ { opt: 'httpsProxy', section: ['DESCRIPTION', 'ADDRESS', 'HTTPS_PROXY'], perAddress: true },
89
+ { opt: 'httpsProxyPort', section: ['DESCRIPTION', 'ADDRESS', 'HTTPS_PROXY_PORT'], perAddress: true, fmt: String }
90
+ ];
91
+
92
+ /**
93
+ * Returns the uppercase name of the node if it exists and is a string.
94
+ * @param {Object} node - The node to get the name from.
95
+ * @returns {string|undefined} Uppercase name or undefined.
96
+ */
97
+ function getUpperName(node) {
98
+ if (!node || typeof node.name !== 'string') {
99
+ return undefined;
100
+ }
101
+ return node.name.toUpperCase();
102
+ }
103
+ // Drops the cached child-name lookup for a node so the next search rebuilds it after structural changes.
104
+ function invalidateChildLookup(node) {
105
+ if (node) {
106
+ childLookupCache.delete(node);
107
+ }
108
+ }
109
+ // Builds (or reuses) a per-parent map from uppercase child names to their node arrays for quick lookups.
110
+ function getChildLookup(parent) {
111
+ if (!parent || !Array.isArray(parent.list) || parent.list.length === 0) {
112
+ return null;
113
+ }
114
+ let lookup = childLookupCache.get(parent);
115
+ if (!lookup) {
116
+ lookup = Object.create(null);
117
+ for (const child of parent.list) {
118
+ const key = getUpperName(child);
119
+ if (!key) {
120
+ continue;
121
+ }
122
+ if (lookup[key]) {
123
+ lookup[key].push(child);
124
+ } else {
125
+ lookup[key] = [child];
126
+ }
127
+ }
128
+ childLookupCache.set(parent, lookup);
129
+ }
130
+ return lookup;
131
+ }
132
+ // Uses the cached lookup to return the first child whose name matches the provided string, or undefined if absent.
133
+ function findFirstChild(parent, name) {
134
+ if (typeof name !== 'string') {
135
+ return undefined;
136
+ }
137
+ const lookup = getChildLookup(parent);
138
+ if (!lookup) {
139
+ return undefined;
140
+ }
141
+ const key = name.toUpperCase();
142
+ const list = lookup[key];
143
+ return list ? list[0] : undefined;
144
+ }
145
+
146
+ /**
147
+ * Collects all DESCRIPTION nodes from the root NVPair tree.
148
+ * Traverses the tree using a stack to find DESCRIPTION nodes.
149
+ * @param {Object} root - The root NVPair node.
150
+ * @returns {Array} Array of DESCRIPTION nodes.
151
+ */
152
+ function collectDescriptions(root) {
153
+ if (!root || typeof root.name !== 'string') {
154
+ return [];
155
+ }
156
+ const stack = [root];
157
+ const descriptions = [];
158
+ while (stack.length) {
159
+ const node = stack.pop();
160
+ if (!node || typeof node.name !== 'string') {
161
+ continue;
162
+ }
163
+ const name = node.name.toUpperCase();
164
+ if (name === 'DESCRIPTION' && node.rhsType === RHS_LIST) {
165
+ descriptions.push(node);
166
+ continue;
167
+ }
168
+ if (name === 'DESCRIPTION_LIST' && node.list) {
169
+ for (let i = 0; i < node.list.length; i++) {
170
+ stack.push(node.list[i]);
171
+ }
172
+ }
173
+ }
174
+ return descriptions;
175
+ }
176
+
177
+ /**
178
+ * Ensures a child node with the given name exists under the parent.
179
+ * Creates it if necessary.
180
+ * @param {Object} parent - The parent NVPair node.
181
+ * @param {string} name - The name of the child node.
182
+ * @returns {Object} The child node.
183
+ */
184
+ function ensureNode(parent, name) {
185
+ const key = name.toUpperCase();
186
+ if (parent.rhsType === RHS_LIST && Array.isArray(parent.list)) {
187
+ const cached = findFirstChild(parent, key);
188
+ if (cached) {
189
+ return cached;
190
+ }
191
+ }
192
+ const created = new parent.constructor(key);
193
+ parent.addListElement(created);
194
+ invalidateChildLookup(parent);
195
+ return created;
196
+ }
197
+
198
+ /**
199
+ * Finds all child nodes with the given name under the parent.
200
+ * @param {Object} parent - The parent NVPair node.
201
+ * @param {string} name - The name to search for.
202
+ * @returns {Array} Array of matching child nodes.
203
+ */
204
+ function findChildren(parent, name) {
205
+ if (typeof name !== 'string') {
206
+ return [];
207
+ }
208
+ const lookup = getChildLookup(parent);
209
+ if (!lookup) {
210
+ return [];
211
+ }
212
+ const key = name.toUpperCase();
213
+ const matches = lookup[key];
214
+ if (!matches || matches.length === 0) {
215
+ return [];
216
+ }
217
+ return matches.slice();
218
+ }
219
+
220
+ /**
221
+ * Places a parameter node in the correct position within a DESCRIPTION list.
222
+ * Positions before ADDRESS, ADDRESS_LIST, or CONNECT_DATA.
223
+ * @param {Object} description - The DESCRIPTION NVPair node.
224
+ * @param {Object} node - The parameter node to place.
225
+ */
226
+ function placeDescriptionParam(description, node) {
227
+ const list = description.list;
228
+ const idx = list.indexOf(node);
229
+ if (idx > -1) {
230
+ list.splice(idx, 1);
231
+ }
232
+ let pos = 0;
233
+ while (pos < list.length) {
234
+ const childName = getUpperName(list[pos]);
235
+ if (childName === 'ADDRESS' || childName === 'ADDRESS_LIST' || childName === 'CONNECT_DATA') {
236
+ break;
237
+ }
238
+ pos++;
239
+ }
240
+ list.splice(pos, 0, node);
241
+ invalidateChildLookup(description);
242
+ }
243
+
244
+ /**
245
+ * Applies overrides from the options object to the connectString.
246
+ * Handles NVPair format, TNS alias resolution, and updates the options.connectString in place.
247
+ * Only adds parameters if not already present in the descriptor.
248
+ * Supports EZConnect strings, TNS aliases (via tnsnames.ora), and full descriptors.
249
+ *
250
+ * @param {Object} options - The connection options object containing connectString and override parameters.
251
+ * @example
252
+ * // Example with full descriptor
253
+ * const options = {
254
+ * connectString: '(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=host)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=service)))',
255
+ * sdu: 8192,
256
+ * retryCount: 3
257
+ * };
258
+ * await applyConnectStringOverrides(options);
259
+ * // options.connectString now includes (SDU=8192) and (RETRY_COUNT=3) if not already present
260
+ *
261
+ * // Example with TNS alias
262
+ * const options = {
263
+ * connectString: 'mydb',
264
+ * configDir: '/path/to/network/admin',
265
+ * sdu: 8192
266
+ * };
267
+ * await applyConnectStringOverrides(options);
268
+ * // Resolves 'mydb' from tnsnames.ora and applies SDU if not present
269
+ *
270
+ * // Example with EZConnect
271
+ * const options = {
272
+ * connectString: 'tcps://host:1521/service',
273
+ * walletLocation: '/path/to/wallet'
274
+ * };
275
+ * await applyConnectStringOverrides(options);
276
+ * // Converts to descriptor and adds (SECURITY=(MY_WALLET_DIRECTORY=/path/to/wallet))
277
+ */
278
+ async function applyConnectStringOverrides(options) {
279
+ const connectString = options.connectString;
280
+ const trimmed = connectString.trim();
281
+ let root;
282
+ if (trimmed.startsWith('(')) {
283
+ // Parse direct NVPair format
284
+ root = createNVPair(trimmed);
285
+ } else {
286
+ const configDir = options.configDir || process.env.TNS_ADMIN || '';
287
+ const resolved = await resolveConnectStr(trimmed, configDir);
288
+ root = (typeof resolved === 'string') ? createNVPair(resolved) : resolved;
289
+ }
290
+
291
+ const descriptions = collectDescriptions(root);
292
+ if (!descriptions.length) return;
293
+
294
+ const configured = [];
295
+ for (const def of CONNECT_OVERRIDES) {
296
+ const raw = options[def.opt];
297
+ if (raw === undefined) {
298
+ continue;
299
+ }
300
+ const formatter = def.fmt || (v => v);
301
+ const value = formatter(raw);
302
+ configured.push({ def, value });
303
+ }
304
+
305
+ if (!configured.length) return;
306
+
307
+ for (const description of descriptions) {
308
+ for (const entry of configured) {
309
+ const def = entry.def;
310
+ const value = entry.value;
311
+ const { section, perAddress } = def;
312
+ if (perAddress) {
313
+ const addresses = findChildren(description, section[1]);
314
+ if (!addresses.length) continue;
315
+ for (const addr of addresses) {
316
+ const existingNode = findFirstChild(addr, section[2]);
317
+ if (!existingNode) {
318
+ ensureNode(addr, section[2]).setAtom = value;
319
+ }
320
+ }
321
+ continue;
322
+ }
323
+ let node = description;
324
+ let existing = false;
325
+ for (let k = 1; k < section.length; k++) {
326
+ const child = findFirstChild(node, section[k]);
327
+ if (!child) break;
328
+ node = child;
329
+ if (k === section.length - 1) existing = true;
330
+ }
331
+ if (!existing) {
332
+ for (let k = 1; k < section.length; k++) {
333
+ node = ensureNode(node, section[k]);
334
+ }
335
+ node.setAtom = value;
336
+ if (section.length === RHS_LIST && section[0] === 'DESCRIPTION') {
337
+ placeDescriptionParam(description, node);
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ options.connectString = root.toString();
344
+ }
345
+
346
+ /**
347
+ * Merges extended parameters from the EZConnect resolver into the options object.
348
+ * Only processes keys starting with 'njs.' and coerces values based on predefined sets.
349
+ * @param {Object} options - The options object to merge into.
350
+ * @param {Object} resolver - The EZConnect resolver containing connection properties.
351
+ * @returns {Object} The updated options object.
352
+ * @example
353
+ * const resolver = new EZConnectResolver('tcp://host:port/service?njs.stmtCacheSize=10&njs.events=true');
354
+ * const options = {};
355
+ * const merged = mergeExtendedParams(options, resolver);
356
+ * // merged = { stmtCacheSize: 10, events: true }
357
+ */
358
+ function mergeExtendedParams(options, resolver) {
359
+ for (const [rawKey, rawValue] of resolver.connectionProps.entries()) {
360
+ let key = rawKey;
361
+ if (key.startsWith(NJS_PREFIX)) {
362
+ key = key.slice(NJS_PREFIX.length);
363
+ } else {
364
+ continue;
365
+ }
366
+ let value = rawValue;
367
+ if (NUMBER_KEYS.has(key)) {
368
+ value = Number(rawValue);
369
+ if (isNaN(value) || !Number.isInteger(value)) continue;
370
+ } else if (BOOLEAN_KEYS.has(key)) {
371
+ const lower = rawValue.toLowerCase();
372
+ value = (lower === 'true' || lower === 'on' || lower === 'yes');
373
+ } else if (STRING_KEYS.has(key)) {
374
+ value = rawValue;
375
+ } else {
376
+ continue;
377
+ }
378
+ options[key] = value;
379
+ }
380
+ return options;
381
+ }
382
+
383
+ /**
384
+ * Resolves an EZConnect string to a connect descriptor.
385
+ * @param {string} connStr - The EZConnect string.
386
+ * @returns {Object} Object containing connectString and resolver.
387
+ * @example
388
+ * const result = getConnectDescriptorFromEZConnect('tcp://host:1521/service');
389
+ * // result = { connectString: '(DESCRIPTION=...)' , resolver: EZConnectResolver instance }
390
+ */
391
+ function getConnectDescriptorFromEZConnect(connStr) {
392
+ if (connStr && ((connStr.indexOf(')') == -1) || (connStr.indexOf('(') != 0))) {
393
+ if (connStr.indexOf(':') !== -1 || connStr.indexOf('/') !== -1) {
394
+ const resolver = new EZConnectResolver(connStr);
395
+ return { connectString: resolver.getResolvedUrl(), resolver };
396
+ }
397
+ }
398
+ return { connectString: connStr, resolver: null };
399
+ }
400
+
401
+ /**
402
+ * Resolves a connect string, handling EZConnect or TNS aliases.
403
+ * @param {string} connectString - The connect string to resolve.
404
+ * @param {string} configDir - Directory for tnsnames.ora.
405
+ * @returns {Promise<Object|string>} Resolved connect descriptor or string.
406
+ * @example
407
+ * const resolved = await resolveConnectStr('mydb', '/path/to/network/admin');
408
+ * // Resolves 'mydb' from tnsnames.ora in the given directory
409
+ */
410
+ async function resolveConnectStr(connectString, configDir) {
411
+ const connStr = connectString.trim();
412
+ let resolvedVal = connStr;
413
+ if ((connStr.indexOf(')') === -1) || (connStr.indexOf('(') != 0)) {
414
+ if ((connStr.indexOf(':') != -1) || (connStr.indexOf('/') != -1)) {
415
+ const ezcnObj = new EZConnectResolver(connStr);
416
+ resolvedVal = ezcnObj.getResolvedUrl();
417
+ return resolvedVal;
418
+ } else {
419
+ //try tns alias
420
+ const namesFilePath = tnsnamesFilePath(configDir);
421
+ const p = await nlParamParser.initializeNlpa(namesFilePath);
422
+ resolvedVal = p.get(connStr.toUpperCase());
423
+ if (!resolvedVal)
424
+ errors.throwErr(errors.ERR_TNS_ENTRY_NOT_FOUND, connStr, configDir ? configDir + '/tnsnames.ora' : process.env.TNS_ADMIN + '/tnsnames.ora');
425
+ if (resolvedVal.rhsType == 1) {
426
+ const rString = resolvedVal.atom;
427
+ if ((rString.indexOf(':') != -1) || (rString.indexOf('/') != -1)) {
428
+ return new EZConnectResolver(rString).getResolvedUrl();
429
+ }
430
+ }
431
+ resolvedVal = resolvedVal.getListElement(0);
432
+ }
433
+
434
+ }
435
+ return resolvedVal;
436
+ }
437
+
438
+ module.exports = {
439
+ applyConnectStringOverrides,
440
+ mergeExtendedParams,
441
+ getConnectDescriptorFromEZConnect,
442
+ resolveConnectStr
443
+ };
@@ -287,7 +287,7 @@ class ResultSetImpl extends BaseImpl {
287
287
  const fetchArraySize = 100;
288
288
  // fetch all rows
289
289
  let rowsFetched = [];
290
- while (true) { // eslint-disable-line
290
+ while (true) {
291
291
  // constant default value for fetchArraySize
292
292
  const rows = await this.getRows(fetchArraySize, {});
293
293
  if (rows) {