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.
- package/README.md +16 -6
- package/build/Release/oracledb-26.0.0-darwin-arm64.node +0 -0
- package/build/Release/oracledb-26.0.0-darwin-arm64.node-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-js-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-linux-arm64.node +0 -0
- package/build/Release/oracledb-26.0.0-linux-arm64.node-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-linux-x64.node +0 -0
- package/build/Release/oracledb-26.0.0-linux-x64.node-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-win32-x64.node +0 -0
- package/build/Release/oracledb-26.0.0-win32-x64.node-buildinfo.txt +1 -0
- package/lib/connection.js +71 -8
- package/lib/constants.js +4 -0
- package/lib/endUserSecurityContext.js +20 -8
- package/lib/errors.js +70 -5
- package/lib/impl/connection.js +36 -0
- package/lib/impl/datahandlers/buffer.js +91 -18
- package/lib/impl/datahandlers/oson.js +3 -2
- package/lib/impl/resultset.js +32 -11
- package/lib/oracledb.js +34 -0
- package/lib/pool.js +6 -2
- package/lib/queryStream.js +13 -5
- package/lib/resultset.js +10 -3
- package/lib/securityContextProvider.js +206 -0
- package/lib/thin/aq.js +2 -3
- package/lib/thin/connection.js +381 -19
- package/lib/thin/dbObject.js +17 -8
- package/lib/thin/pool.js +3 -2
- package/lib/thin/protocol/capabilities.js +37 -4
- package/lib/thin/protocol/constants.js +49 -1
- package/lib/thin/protocol/messages/auth.js +17 -6
- package/lib/thin/protocol/messages/base.js +74 -16
- package/lib/thin/protocol/messages/dataType.js +46 -0
- package/lib/thin/protocol/messages/directPathLoadStream.js +3 -2
- package/lib/thin/protocol/messages/execute.js +10 -4
- package/lib/thin/protocol/messages/index.js +4 -0
- package/lib/thin/protocol/messages/notification.js +281 -0
- package/lib/thin/protocol/messages/subscribe.js +181 -0
- package/lib/thin/protocol/messages/withData.js +21 -7
- package/lib/thin/protocol/packet.js +2 -4
- package/lib/thin/protocol/protocol.js +7 -4
- package/lib/thin/resultSet.js +40 -20
- package/lib/thin/sqlnet/ezConnectResolver.js +7 -0
- package/lib/thin/sqlnet/navNodes.js +3 -3
- package/lib/thin/sqlnet/ntTcp.js +24 -8
- package/lib/thin/statement.js +6 -1
- package/lib/thin/statementCache.js +34 -6
- package/lib/thin/subscr.js +363 -0
- package/lib/transformer.js +60 -17
- package/lib/util.js +46 -8
- package/lib/version.js +2 -2
- package/package.json +1 -1
- package/plugins/configProviders/gcpstorage/index.js +121 -0
- package/plugins/configProviders/ociobject/index.js +1 -1
- package/plugins/token/cache.js +154 -0
- package/plugins/token/endUserSecurityProvider/index.js +377 -0
- package/plugins/token/extensionAzure/index.js +160 -28
- package/plugins/token/extensionOci/index.js +221 -16
- package/plugins/vectorsdk/OracleVecDB.js +346 -0
- package/plugins/vectorsdk/index.js +49 -0
- package/plugins/vectorsdk/utils.js +370 -0
- package/plugins/vectorsdk/vectorEmbedding.js +223 -0
- package/plugins/vectorsdk/vectorIndex.js +177 -0
- package/plugins/vectorsdk/vectorRecords.js +406 -0
- package/plugins/vectorsdk/vectorSchema.js +408 -0
- package/plugins/vectorsdk/vectorSearch.js +538 -0
- package/build/Release/oracledb-7.0.1-darwin-arm64.node +0 -0
- package/build/Release/oracledb-7.0.1-darwin-arm64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-js-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-linux-arm64.node +0 -0
- package/build/Release/oracledb-7.0.1-linux-arm64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-linux-x64.node +0 -0
- package/build/Release/oracledb-7.0.1-linux-x64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-win32-x64.node +0 -0
- 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");
|
package/lib/thin/sqlnet/ntTcp.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
+
this.proxyReq.once('error', (err) => {
|
|
185
186
|
proxyConnectErrCause = err.message;
|
|
186
187
|
resolve();
|
|
187
188
|
});
|
|
188
|
-
|
|
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 (
|
|
201
|
-
|
|
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
|
|
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
|
}
|
package/lib/thin/statement.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright (c) 2022,
|
|
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
|
-
//
|
|
182
|
-
// Write the list of cursors to
|
|
209
|
+
// writeCursors()
|
|
210
|
+
// Write the specified list of cursors to the buffer and clear the list.
|
|
183
211
|
//---------------------------------------------------------------------------
|
|
184
|
-
|
|
185
|
-
buf.writeUB4(
|
|
186
|
-
for (const cursorNum of
|
|
212
|
+
writeCursors(buf, cursors) {
|
|
213
|
+
buf.writeUB4(cursors.size);
|
|
214
|
+
for (const cursorNum of cursors) {
|
|
187
215
|
buf.writeUB4(cursorNum);
|
|
188
216
|
}
|
|
189
|
-
|
|
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;
|