oracledb 7.0.1 → 26.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 (74) hide show
  1. package/README.md +16 -6
  2. package/build/Release/oracledb-26.0.0-darwin-arm64.node +0 -0
  3. package/build/Release/oracledb-26.0.0-darwin-arm64.node-buildinfo.txt +1 -0
  4. package/build/Release/oracledb-26.0.0-js-buildinfo.txt +1 -0
  5. package/build/Release/oracledb-26.0.0-linux-arm64.node +0 -0
  6. package/build/Release/oracledb-26.0.0-linux-arm64.node-buildinfo.txt +1 -0
  7. package/build/Release/oracledb-26.0.0-linux-x64.node +0 -0
  8. package/build/Release/oracledb-26.0.0-linux-x64.node-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-26.0.0-win32-x64.node +0 -0
  10. package/build/Release/oracledb-26.0.0-win32-x64.node-buildinfo.txt +1 -0
  11. package/lib/connection.js +71 -8
  12. package/lib/constants.js +4 -0
  13. package/lib/endUserSecurityContext.js +20 -8
  14. package/lib/errors.js +70 -5
  15. package/lib/impl/connection.js +36 -0
  16. package/lib/impl/datahandlers/buffer.js +91 -18
  17. package/lib/impl/datahandlers/oson.js +3 -2
  18. package/lib/impl/resultset.js +32 -11
  19. package/lib/oracledb.js +34 -0
  20. package/lib/pool.js +6 -2
  21. package/lib/queryStream.js +13 -5
  22. package/lib/resultset.js +10 -3
  23. package/lib/securityContextProvider.js +206 -0
  24. package/lib/thin/aq.js +2 -3
  25. package/lib/thin/connection.js +381 -19
  26. package/lib/thin/dbObject.js +17 -8
  27. package/lib/thin/pool.js +3 -2
  28. package/lib/thin/protocol/capabilities.js +37 -4
  29. package/lib/thin/protocol/constants.js +49 -1
  30. package/lib/thin/protocol/messages/auth.js +17 -6
  31. package/lib/thin/protocol/messages/base.js +74 -16
  32. package/lib/thin/protocol/messages/dataType.js +46 -0
  33. package/lib/thin/protocol/messages/directPathLoadStream.js +3 -2
  34. package/lib/thin/protocol/messages/execute.js +10 -4
  35. package/lib/thin/protocol/messages/index.js +4 -0
  36. package/lib/thin/protocol/messages/notification.js +281 -0
  37. package/lib/thin/protocol/messages/subscribe.js +181 -0
  38. package/lib/thin/protocol/messages/withData.js +21 -7
  39. package/lib/thin/protocol/packet.js +2 -4
  40. package/lib/thin/protocol/protocol.js +7 -4
  41. package/lib/thin/resultSet.js +40 -20
  42. package/lib/thin/sqlnet/ezConnectResolver.js +7 -0
  43. package/lib/thin/sqlnet/navNodes.js +3 -3
  44. package/lib/thin/sqlnet/ntTcp.js +24 -8
  45. package/lib/thin/statement.js +6 -1
  46. package/lib/thin/statementCache.js +34 -6
  47. package/lib/thin/subscr.js +363 -0
  48. package/lib/transformer.js +60 -17
  49. package/lib/util.js +46 -8
  50. package/lib/version.js +2 -2
  51. package/package.json +1 -1
  52. package/plugins/configProviders/gcpstorage/index.js +121 -0
  53. package/plugins/configProviders/ociobject/index.js +1 -1
  54. package/plugins/token/cache.js +154 -0
  55. package/plugins/token/endUserSecurityProvider/index.js +377 -0
  56. package/plugins/token/extensionAzure/index.js +160 -28
  57. package/plugins/token/extensionOci/index.js +221 -16
  58. package/plugins/vectorsdk/OracleVecDB.js +346 -0
  59. package/plugins/vectorsdk/index.js +49 -0
  60. package/plugins/vectorsdk/utils.js +370 -0
  61. package/plugins/vectorsdk/vectorEmbedding.js +223 -0
  62. package/plugins/vectorsdk/vectorIndex.js +177 -0
  63. package/plugins/vectorsdk/vectorRecords.js +406 -0
  64. package/plugins/vectorsdk/vectorSchema.js +408 -0
  65. package/plugins/vectorsdk/vectorSearch.js +538 -0
  66. package/build/Release/oracledb-7.0.1-darwin-arm64.node +0 -0
  67. package/build/Release/oracledb-7.0.1-darwin-arm64.node-buildinfo.txt +0 -1
  68. package/build/Release/oracledb-7.0.1-js-buildinfo.txt +0 -1
  69. package/build/Release/oracledb-7.0.1-linux-arm64.node +0 -0
  70. package/build/Release/oracledb-7.0.1-linux-arm64.node-buildinfo.txt +0 -1
  71. package/build/Release/oracledb-7.0.1-linux-x64.node +0 -0
  72. package/build/Release/oracledb-7.0.1-linux-x64.node-buildinfo.txt +0 -1
  73. package/build/Release/oracledb-7.0.1-win32-x64.node +0 -0
  74. package/build/Release/oracledb-7.0.1-win32-x64.node-buildinfo.txt +0 -1
@@ -290,11 +290,14 @@ class EZConnectResolver {
290
290
  // by default for TCPS protocol.
291
291
  const serverDNMatch = this.urlProps.get("SSL_SERVER_DN_MATCH");
292
292
  const serverCertDN = this.urlProps.get("SSL_SERVER_CERT_DN");
293
+ const allowWeakDNMatch = this.urlProps.get("SSL_ALLOW_WEAK_DN_MATCH");
293
294
  const walletDir = this.urlProps.get("MY_WALLET_DIRECTORY");
294
295
  if (serverDNMatch != null)
295
296
  securityInfo.push(`(SSL_SERVER_DN_MATCH=${serverDNMatch})`);
296
297
  if (serverCertDN != null)
297
298
  securityInfo.push(`(SSL_SERVER_CERT_DN=${serverCertDN})`);
299
+ if (allowWeakDNMatch != null)
300
+ securityInfo.push(`(SSL_ALLOW_WEAK_DN_MATCH=${allowWeakDNMatch})`);
298
301
  if (walletDir != null && walletDir.toUpperCase() !== 'SYSTEM')
299
302
  securityInfo.push(`(MY_WALLET_DIRECTORY=${walletDir})`);
300
303
  }
@@ -454,6 +457,10 @@ class EZConnectResolver {
454
457
  aliasMap.set("recv_timeout", "RECV_TIMEOUT");
455
458
  aliasMap.set("ssl_server_cert_dn", "SSL_SERVER_CERT_DN");
456
459
  aliasMap.set("ssl_server_dn_match", "SSL_SERVER_DN_MATCH");
460
+ aliasMap.set("tls_server_cert_dn", "SSL_SERVER_CERT_DN");
461
+ aliasMap.set("tls_server_dn_match", "SSL_SERVER_DN_MATCH");
462
+ aliasMap.set("ssl_allow_weak_dn_match", "SSL_ALLOW_WEAK_DN_MATCH");
463
+ aliasMap.set("tls_allow_weak_dn_match", "SSL_ALLOW_WEAK_DN_MATCH");
457
464
  aliasMap.set("wallet_location", "MY_WALLET_DIRECTORY");
458
465
  aliasMap.set("pool_connection_class", "POOL_CONNECTION_CLASS");
459
466
  aliasMap.set("pool_purity", "POOL_PURITY");
@@ -315,13 +315,13 @@ class Description {
315
315
  let tmpnv;
316
316
  for (let i = 0; i < listsize; i++) {
317
317
  tmpnv = childnv.getListElement(i);
318
- if (tmpnv.name.toUpperCase() == "SSL_SERVER_CERT_DN") {
318
+ if (tmpnv.name.toUpperCase() == "SSL_SERVER_CERT_DN" || tmpnv.name.toUpperCase() == "TLS_SERVER_CERT_DN") {
319
319
  this.params.sslServerCertDN = tmpnv.valueToString();
320
- } else if (tmpnv.name.toUpperCase() == "SSL_SERVER_DN_MATCH") {
320
+ } else if (tmpnv.name.toUpperCase() == "SSL_SERVER_DN_MATCH" || tmpnv.name.toUpperCase() == "TLS_SERVER_DN_MATCH") {
321
321
  this.params.sslServerDNMatch = (tmpnv.atom.toLowerCase() == "yes"
322
322
  || tmpnv.atom.toLowerCase() == "on"
323
323
  || tmpnv.atom.toLowerCase() == "true");
324
- } else if (tmpnv.name.toUpperCase() == "SSL_ALLOW_WEAK_DN_MATCH") {
324
+ } else if (tmpnv.name.toUpperCase() == "SSL_ALLOW_WEAK_DN_MATCH" || tmpnv.name.toUpperCase() == "TLS_ALLOW_WEAK_DN_MATCH") {
325
325
  this.params.sslAllowWeakDNMatch = (tmpnv.atom.toLowerCase() == "yes"
326
326
  || tmpnv.atom.toLowerCase() == "on"
327
327
  || tmpnv.atom.toLowerCase() == "true");
@@ -74,6 +74,7 @@ class NTTCP {
74
74
  this.largeSDU = false;
75
75
  this.streamNum = streamNum++;
76
76
  this.packetNum = 1;
77
+ this.proxyReq = null; // track in-flight proxy CONNECT request for abort on timeout
77
78
  this.doDNMatch = true;
78
79
  }
79
80
 
@@ -157,7 +158,7 @@ class NTTCP {
157
158
  address.port = DEFAULT_PORT;
158
159
  }
159
160
 
160
- let connectErrCause, proxyConnectErrCause, req;
161
+ let connectErrCause, proxyConnectErrCause;
161
162
  const httpsProxy = address.httpsProxy || this.atts.httpsProxy;
162
163
  let httpsProxyPort = address.httpsProxyPort || this.atts.httpsProxyPort;
163
164
 
@@ -166,13 +167,13 @@ class NTTCP {
166
167
  if (!httpsProxyPort) {
167
168
  httpsProxyPort = DEFAULT_HTTPS_PROXY_PORT;
168
169
  }
169
- req = http.request({
170
+ this.proxyReq = http.request({
170
171
  host: httpsProxy,
171
172
  port: httpsProxyPort,
172
173
  method: 'CONNECT',
173
174
  path: address.host + ':' + address.port,
174
175
  });
175
- req.once('connect', (res, socket) => {
176
+ this.proxyReq.once('connect', (res, socket) => {
176
177
  if (res.statusCode == 200) {
177
178
  this.connected = true;
178
179
  this.stream = socket;
@@ -181,11 +182,11 @@ class NTTCP {
181
182
  }
182
183
  resolve();
183
184
  });
184
- req.once('error', (err) => {
185
+ this.proxyReq.once('error', (err) => {
185
186
  proxyConnectErrCause = err.message;
186
187
  resolve();
187
188
  });
188
- req.end();
189
+ this.proxyReq.end();
189
190
  } else {
190
191
  this.stream = net.connect(address.port, address.host, () => {
191
192
  this.connected = true;
@@ -197,8 +198,10 @@ class NTTCP {
197
198
  });
198
199
  }
199
200
  });
200
- if (req)
201
- req.removeAllListeners();
201
+ if (this.proxyReq) {
202
+ this.proxyReq.removeAllListeners();
203
+ this.proxyReq = null;
204
+ }
202
205
  if (!this.connected) {
203
206
  if (proxyConnectErrCause) {
204
207
  errors.throwErr(errors.ERR_PROXY_CONNECTION_FAILURE, httpsProxy, httpsProxyPort, this.atts.connectionId, proxyConnectErrCause);
@@ -260,14 +263,27 @@ class NTTCP {
260
263
  * @returns Promise
261
264
  */
262
265
  disconnect(type) { /* Disconnect function for TCP sockets */
263
- if (this.connected && !this.err) {
266
+ // Always tear down the socket if present, even if not yet connected
267
+ if (this.stream) {
264
268
  if (type == constants.NSFIMM)
265
269
  this.stream.destroy();
266
270
  else
267
271
  this.stream.end();
268
272
  }
273
+
274
+ // Tear down any in-flight proxy CONNECT request after its socket. Keep an
275
+ // error listener for a socket error already pending when it is destroyed.
276
+ if (this.proxyReq) {
277
+ this.proxyReq.removeAllListeners();
278
+ this.proxyReq.once('error', () => {});
279
+ this.proxyReq.destroy();
280
+ this.proxyReq = null;
281
+ }
282
+
269
283
  this.stream = null;
270
284
  this.connected = false;
285
+ this.err = this.err || (type == constants.NSFIMM);
286
+ this._notifyWaiters();
271
287
  this.drainWaiter = null;
272
288
  this.readWaiter = null;
273
289
  }
@@ -1,4 +1,4 @@
1
- // Copyright (c) 2022, 2024, Oracle and/or its affiliates.
1
+ // Copyright (c) 2022, 2026, Oracle and/or its affiliates.
2
2
 
3
3
  //-----------------------------------------------------------------------------
4
4
  //
@@ -363,6 +363,9 @@ class Statement {
363
363
  this.bufferRowCount = 0;
364
364
  this.pendingClear = false;
365
365
  this.statementType = constants.STMT_TYPE_UNKNOWN;
366
+
367
+ // CQN registration id attached during query registration execute.
368
+ this._registrationId = 0n;
366
369
  }
367
370
 
368
371
  //---------------------------------------------------------------------------
@@ -394,6 +397,7 @@ class Statement {
394
397
  }
395
398
  }
396
399
  copiedStatement.returnToCache = false;
400
+ copiedStatement._registrationId = this._registrationId;
397
401
  return copiedStatement;
398
402
  }
399
403
 
@@ -555,6 +559,7 @@ class Statement {
555
559
  this.numQueryVars = 0;
556
560
  this.bufferRowCount = 0;
557
561
  this.bufferRowIndex = 0;
562
+ this._registrationId = 0n;
558
563
  }
559
564
 
560
565
  //---------------------------------------------------------------------------
@@ -32,10 +32,34 @@ class StatementCache {
32
32
  constructor(maxSize) {
33
33
  this._cachedStatements = new Map();
34
34
  this._maxSize = maxSize;
35
+ this._cursorsToCancel = new Set();
35
36
  this._cursorsToClose = new Set();
36
37
  this._openCursors = new Set();
37
38
  }
38
39
 
40
+ //---------------------------------------------------------------------------
41
+ // _addCursorToCancel()
42
+ //
43
+ // Add the statement's cursor to the list of cursors that need to be
44
+ // cancelled.
45
+ //---------------------------------------------------------------------------
46
+ _addCursorToCancel(statement) {
47
+ const cursorId = statement.cursorId;
48
+
49
+ if (cursorId <= 0 || !statement.isQuery || !statement.moreRowsToFetch) {
50
+ return;
51
+ }
52
+
53
+ if (this._cursorsToClose.has(cursorId)) {
54
+ return;
55
+ }
56
+
57
+ this._cursorsToCancel.add(cursorId);
58
+ // Keep the cached statement tracked in _openCursors so its cursor state can
59
+ // be invalidated if the underlying DRCP session changes.
60
+ statement.moreRowsToFetch = false;
61
+ }
62
+
39
63
  //---------------------------------------------------------------------------
40
64
  // _addCursorToClose()
41
65
  //
@@ -49,6 +73,8 @@ class StatementCache {
49
73
  if (statement.cursorId != 0) {
50
74
  this._cursorsToClose.add(statement.cursorId);
51
75
  }
76
+ // Closing the cursor supersedes a pending cancellation.
77
+ this._cursorsToCancel.delete(statement.cursorId);
52
78
  this._openCursors.delete(statement);
53
79
  }
54
80
 
@@ -86,6 +112,7 @@ class StatementCache {
86
112
  stmt._clearState();
87
113
  }
88
114
  this._openCursors = newOpenCursors;
115
+ this._cursorsToCancel.clear();
89
116
  this._cursorsToClose.clear();
90
117
  }
91
118
 
@@ -169,6 +196,7 @@ class StatementCache {
169
196
  });
170
197
  }
171
198
  if (statement.returnToCache) {
199
+ this._addCursorToCancel(statement);
172
200
  statement.inUse = false;
173
201
  } else {
174
202
  this._addCursorToClose(statement);
@@ -178,15 +206,15 @@ class StatementCache {
178
206
  }
179
207
 
180
208
  //---------------------------------------------------------------------------
181
- // writeCursorsToClose()
182
- // Write the list of cursors to close to the buffer.
209
+ // writeCursors()
210
+ // Write the specified list of cursors to the buffer and clear the list.
183
211
  //---------------------------------------------------------------------------
184
- writeCursorsToClose(buf) {
185
- buf.writeUB4(this._cursorsToClose.size);
186
- for (const cursorNum of this._cursorsToClose.keys()) {
212
+ writeCursors(buf, cursors) {
213
+ buf.writeUB4(cursors.size);
214
+ for (const cursorNum of cursors) {
187
215
  buf.writeUB4(cursorNum);
188
216
  }
189
- this._cursorsToClose.clear();
217
+ cursors.clear();
190
218
  }
191
219
  }
192
220
 
@@ -0,0 +1,363 @@
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 errors = require("../errors.js");
30
+ const constants = require("../constants.js");
31
+ const protocolUtils = require("./protocol/utils.js");
32
+ const protocolConstants = require("./protocol/constants.js");
33
+ const messages = require("./protocol/messages");
34
+
35
+ class ThinSubscrImpl {
36
+
37
+ /**
38
+ * Creates a thin subscription implementation instance.
39
+ */
40
+ constructor(connImpl, options) {
41
+ this._connImpl = connImpl;
42
+ this.callback = options.callback;
43
+ this.namespace = options.namespace;
44
+ this.name = options.name;
45
+ this.timeout = options.timeout || 0;
46
+ this.operations = options.operations || 0;
47
+ this.qos = options.qos || 0;
48
+ this.groupingClass = options.groupingClass || 0;
49
+ this.groupingValue = options.groupingValue || 0;
50
+ this.groupingType = options.groupingType || 0;
51
+ this.regId = 0n;
52
+ this.clientId = null;
53
+ this._notifyConnImpl = null;
54
+ this._notifyLoopPromise = null;
55
+ this._unsubscribing = false;
56
+ this._notifyLoopActive = false;
57
+ this._listenerStopped = false;
58
+ this._registered = false;
59
+ this._onRetire = options._onRetire;
60
+ }
61
+
62
+ /**
63
+ * Invokes the user callback safely for a decoded notification.
64
+ */
65
+ _invokeCallback(message) {
66
+ if (typeof this.callback !== 'function') {
67
+ return;
68
+ }
69
+ try {
70
+ this.callback(message);
71
+ } catch {
72
+ // swallow callback exceptions so notification loop remains active
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Retires a subscription after server deregistration or successful explicit
78
+ * unsubscribe, removing the public entry that retains its callback.
79
+ */
80
+ _retire() {
81
+ this._registered = false;
82
+ this._notifyLoopActive = false;
83
+ this._listenerStopped = false;
84
+ if (this._onRetire) {
85
+ this._onRetire(this);
86
+ this._onRetire = null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Closes and releases the dedicated EMON notification connection.
92
+ */
93
+ _closeNotifyConnection() {
94
+ const notifyConnImpl = this._notifyConnImpl;
95
+ this._notifyConnImpl = null;
96
+ if (notifyConnImpl?.nscon) {
97
+ notifyConnImpl.nscon.forceDisconnect();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Internal method that registers the subscription with the database
103
+ * and stores returned IDs.
104
+ */
105
+ async _register() {
106
+ const message = new messages.SubscrMessage(this._connImpl, this);
107
+ await this._connImpl._protocol._processMessage(message);
108
+ this.regId = message.registrationId;
109
+ this.clientId = message.clientId;
110
+ this._registered = true;
111
+ this._listenerStopped = false;
112
+ }
113
+
114
+ /**
115
+ * Marks the EMON listener as unavailable while retaining the server
116
+ * registration for explicit unregister or same-name replacement. This is
117
+ * used for abnormal/recovery cases, not normal notification completion.
118
+ */
119
+ _stopListener() {
120
+ this._notifyLoopActive = false;
121
+ this._listenerStopped = true;
122
+ }
123
+
124
+ /**
125
+ * Internal method that registers a CQN query against an existing
126
+ * subscription registration.
127
+ */
128
+ async _registerQuery(sql, binds) {
129
+ const options = {
130
+ _subscriptionRegId: this.regId,
131
+ keepInStmtCache: false,
132
+ autoCommit: false,
133
+ resultSet: true,
134
+ fetchArraySize: 1,
135
+ prefetchRows: 1
136
+ };
137
+ options.connection = {
138
+ _impl: this._connImpl,
139
+ _getDbObjectClass: (dbObjectClass) => dbObjectClass
140
+ };
141
+ const statement = this._connImpl._prepare(sql, options);
142
+ const internalTempLobs = [];
143
+ let statementReturned = false;
144
+
145
+ try {
146
+ if (!statement.isQuery) {
147
+ errors.throwErr(errors.ERR_NOT_A_QUERY);
148
+ }
149
+ const result = await this._connImpl._execute(statement, 1,
150
+ binds || [], options, false, internalTempLobs);
151
+
152
+ if (result?.resultSet) {
153
+ await result.resultSet.close();
154
+ statementReturned = true;
155
+ }
156
+ } finally {
157
+ if (!statementReturned) {
158
+ this._connImpl._returnStatement(statement);
159
+ }
160
+
161
+ for (const lob of internalTempLobs) {
162
+ this._connImpl._tempLobsToClose.push(lob._locator);
163
+ this._connImpl._tempLobsTotalSize += lob._locator.length;
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Starts the dedicated notification channel by establishing a separate EMON
170
+ * connection, sending a single notify request, and launching the async
171
+ * receive/decode loop until the subscription is unsubscribed, deregistered,
172
+ * or the listener fails.
173
+ */
174
+ async _startNotifyLoop() {
175
+
176
+ // Use a separate EMON connection for notifications.
177
+ const notifyParams = await this._connImpl._getNotificationConnectParams();
178
+ const NotifyConnImpl = this._connImpl.constructor;
179
+ const notifyConnImpl = new NotifyConnImpl();
180
+
181
+ // Marking it as notification connection so that we do not cache
182
+ // connection parameters. Only the user-facing connection retains
183
+ // obfuscated parameters for future notification connections.
184
+ notifyConnImpl._isNotificationConnection = true;
185
+
186
+ // Retain user-facing connection metadata on the internal connection.
187
+ notifyConnImpl._connectString = this._connImpl._connectString;
188
+ notifyConnImpl._user = this._connImpl._user;
189
+ this._notifyConnImpl = notifyConnImpl;
190
+ try {
191
+ await notifyConnImpl.connect(notifyParams);
192
+
193
+ // clientId links this listener to the registered subscription.
194
+ const notifyMessage = new messages.NotifyMessage(notifyConnImpl, this);
195
+ notifyMessage.clientId = this.clientId;
196
+ await notifyConnImpl._protocol._encodeMessage(notifyMessage);
197
+
198
+ // Unsubscribe, deregistration, or listener failure stops this loop and
199
+ // closes the notification connection.
200
+ this._notifyLoopActive = true;
201
+ this._notifyLoopPromise = this._runNotifyLoop(notifyConnImpl,
202
+ notifyMessage);
203
+ this._notifyLoopPromise.catch(() => {
204
+ // prevent unhandled rejection if listener fails asynchronously
205
+ });
206
+ } catch (err) {
207
+ // Setup can fail before _runNotifyLoop() owns cleanup.
208
+ if (this._notifyConnImpl === notifyConnImpl) {
209
+ this._closeNotifyConnection();
210
+ }
211
+ throw err;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Runs the notification receive/decode loop for the dedicated channel.
217
+ */
218
+ async _runNotifyLoop(notifyConnImpl, notifyMessage) {
219
+ const protocol = notifyConnImpl._protocol;
220
+ const readBuf = protocol.readBuf;
221
+ try {
222
+ notifyMessage.preProcess();
223
+ await readBuf.waitForPackets();
224
+
225
+ // Decode records as packets arrive until unsubscribe or deregistration.
226
+ while (this._notifyLoopActive) {
227
+ try {
228
+ notifyMessage.decode(readBuf);
229
+ } catch (err) {
230
+ if (err instanceof protocolUtils.OutOfPacketsError) {
231
+ await readBuf.waitForPackets();
232
+ readBuf.restorePoint();
233
+ continue;
234
+ }
235
+ if (!this._unsubscribing) {
236
+
237
+ // Listener failure while still subscribed is unexpected.
238
+ // Throw the error.
239
+ throw err;
240
+ }
241
+
242
+ // Listener interruption during explicit unsubscribe is expected.
243
+ return;
244
+ }
245
+ }
246
+ } catch (err) {
247
+ if (!this._unsubscribing) {
248
+ // The server registration can still exist after an EMON failure. Keep
249
+ // it in the public registry so unsubscribe() can deregister it.
250
+ this._stopListener();
251
+ }
252
+ throw err;
253
+ } finally {
254
+ // Close the EMON connection whenever the loop ends without going
255
+ // through unsubscribe(), so its socket cannot keep the Node.js event
256
+ // loop alive.
257
+ if (this._notifyConnImpl === notifyConnImpl) {
258
+ this._notifyLoopActive = false;
259
+ this._closeNotifyConnection();
260
+ }
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Internal method for creating a subscription. Each new subscription uses a
266
+ * dedicated client-initiated EMON connection to receive notifications. CQN
267
+ * registrations with the same name reuse this listener to add queries.
268
+ */
269
+ async subscribe(options) {
270
+ if (this.namespace === constants.SUBSCR_NAMESPACE_AQ && !this.qos) {
271
+ // Default AQ registrations request query notifications.
272
+ this.qos = constants.SUBSCR_QOS_QUERY;
273
+ }
274
+ await this._register();
275
+ try {
276
+ await this._startNotifyLoop();
277
+ if (this.namespace === constants.SUBSCR_NAMESPACE_DBCHANGE) {
278
+ await this._registerQuery(options.sql, options.binds);
279
+ }
280
+ } catch (err) {
281
+ try {
282
+ await this.unsubscribe(this._connImpl);
283
+ } catch {
284
+ // preserve the original subscribe failure
285
+ }
286
+ throw err;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Registers an additional query for an existing DBCHANGE subscription.
292
+ */
293
+ async registerQuery(options) {
294
+ if (this.namespace === constants.SUBSCR_NAMESPACE_AQ) {
295
+ errors.throwErr(errors.ERR_NOT_IMPLEMENTED,
296
+ "registering a query on AQ subscriptions");
297
+ }
298
+ if (!this._registered || this._listenerStopped) {
299
+ errors.throwErr(errors.ERR_INVALID_SUBSCR);
300
+ }
301
+ await this._registerQuery(options.sql, options.binds);
302
+ }
303
+
304
+ /**
305
+ * Internal method for destroying the subscription.
306
+ */
307
+ async unsubscribe(connImpl) {
308
+ if (!this.regId) {
309
+ errors.throwErr(errors.ERR_INVALID_SUBSCR);
310
+ }
311
+ this._unsubscribing = true;
312
+ let unregisterError;
313
+ let shouldRetire = false;
314
+ try {
315
+ if (this._registered) {
316
+ const message = new messages.SubscrMessage(connImpl, this);
317
+ message.opCode = protocolConstants.TNS_SUBSCR_OP_UNREGISTER;
318
+ message.registrationId = this.regId;
319
+ message.clientId = this.clientId;
320
+ await connImpl._protocol._processMessage(message);
321
+ }
322
+ shouldRetire = true;
323
+ } catch (err) {
324
+ unregisterError = err;
325
+ if (err.code === errors.getErr(errors.ERR_INVALID_SUBSCR).code) {
326
+ // The server already discarded this registration. Remove the public
327
+ // entry as well so its callback is not retained and the name can be
328
+ // subscribed again.
329
+ shouldRetire = true;
330
+ }
331
+ } finally {
332
+ this._notifyLoopActive = false;
333
+ if (shouldRetire) {
334
+ this._retire();
335
+ this.regId = 0n;
336
+ this.clientId = null;
337
+ } else {
338
+ // The EMON socket is closed below, so this registration cannot be
339
+ // reused without first creating a replacement listener. Retain the
340
+ // registration so the caller can retry UNREGISTER.
341
+ this._stopListener();
342
+ }
343
+ // Interrupt the background wait by force-closing the notification socket.
344
+ this._closeNotifyConnection();
345
+ if (this._notifyLoopPromise) {
346
+ try {
347
+ await this._notifyLoopPromise;
348
+ } catch {
349
+ // ignore listener errors during explicit unsubscribe
350
+ }
351
+ this._notifyLoopPromise = null;
352
+ }
353
+ if (!shouldRetire) {
354
+ this._unsubscribing = false;
355
+ }
356
+ }
357
+ if (unregisterError) {
358
+ throw unregisterError;
359
+ }
360
+ }
361
+ }
362
+
363
+ module.exports = ThinSubscrImpl;