oracledb 5.5.0 → 6.0.1

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 (107) hide show
  1. package/CHANGELOG.md +1 -1875
  2. package/LICENSE.txt +8 -222
  3. package/NOTICE.txt +1 -1
  4. package/README.md +38 -30
  5. package/SECURITY.md +38 -0
  6. package/build/Release/oracledb-6.0.1-darwin-x64.node +0 -0
  7. package/build/Release/oracledb-6.0.1-darwin-x64.node-buildinfo.txt +1 -0
  8. package/build/Release/oracledb-6.0.1-js-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-6.0.1-linux-arm64.node +0 -0
  10. package/build/Release/oracledb-6.0.1-linux-arm64.node-buildinfo.txt +1 -0
  11. package/build/Release/oracledb-6.0.1-linux-x64.node +0 -0
  12. package/build/Release/oracledb-6.0.1-linux-x64.node-buildinfo.txt +1 -0
  13. package/build/Release/oracledb-6.0.1-win32-x64.node +0 -0
  14. package/build/Release/oracledb-6.0.1-win32-x64.node-buildinfo.txt +1 -0
  15. package/examples/README.md +113 -0
  16. package/examples/dbconfig.js +100 -0
  17. package/examples/example.js +156 -0
  18. package/index.js +26 -0
  19. package/lib/aqDeqOptions.js +148 -8
  20. package/lib/aqEnqOptions.js +59 -7
  21. package/lib/aqMessage.js +118 -7
  22. package/lib/aqQueue.js +243 -53
  23. package/lib/connection.js +1326 -465
  24. package/lib/constants.js +203 -0
  25. package/lib/dbObject.js +346 -32
  26. package/lib/errors.js +785 -0
  27. package/lib/impl/aqDeqOptions.js +197 -0
  28. package/lib/impl/aqEnqOptions.js +89 -0
  29. package/lib/impl/aqMessage.js +134 -0
  30. package/lib/impl/aqQueue.js +81 -0
  31. package/lib/impl/connection.js +529 -0
  32. package/lib/impl/dbObject.js +159 -0
  33. package/lib/impl/index.js +59 -0
  34. package/lib/impl/lob.js +85 -0
  35. package/lib/impl/pool.js +195 -0
  36. package/lib/impl/resultset.js +223 -0
  37. package/lib/impl/sodaCollection.js +174 -0
  38. package/lib/impl/sodaDatabase.js +81 -0
  39. package/lib/impl/sodaDocCursor.js +63 -0
  40. package/lib/impl/sodaDocument.js +98 -0
  41. package/lib/impl/sodaOperation.js +109 -0
  42. package/lib/lob.js +157 -68
  43. package/lib/oracledb.js +1047 -345
  44. package/lib/pool.js +654 -465
  45. package/lib/poolStatistics.js +24 -9
  46. package/lib/queryStream.js +16 -8
  47. package/lib/resultset.js +291 -183
  48. package/lib/settings.js +191 -0
  49. package/lib/sodaCollection.js +214 -201
  50. package/lib/sodaDatabase.js +108 -71
  51. package/lib/sodaDocCursor.js +44 -32
  52. package/lib/sodaDocument.js +76 -14
  53. package/lib/sodaOperation.js +131 -118
  54. package/lib/thin/connection.js +1043 -0
  55. package/lib/thin/dbObject.js +630 -0
  56. package/lib/thin/index.js +40 -0
  57. package/lib/thin/lob.js +189 -0
  58. package/lib/thin/pool.js +481 -0
  59. package/lib/thin/protocol/buffer.js +1043 -0
  60. package/lib/thin/protocol/capabilities.js +121 -0
  61. package/lib/thin/protocol/constants.js +765 -0
  62. package/lib/thin/protocol/encryptDecrypt.js +185 -0
  63. package/lib/thin/protocol/messages/auth.js +349 -0
  64. package/lib/thin/protocol/messages/base.js +476 -0
  65. package/lib/thin/protocol/messages/commit.js +56 -0
  66. package/lib/thin/protocol/messages/dataType.js +391 -0
  67. package/lib/thin/protocol/messages/execute.js +312 -0
  68. package/lib/thin/protocol/messages/fetch.js +60 -0
  69. package/lib/thin/protocol/messages/index.js +53 -0
  70. package/lib/thin/protocol/messages/lobOp.js +201 -0
  71. package/lib/thin/protocol/messages/logOff.js +53 -0
  72. package/lib/thin/protocol/messages/ping.js +56 -0
  73. package/lib/thin/protocol/messages/protocol.js +78 -0
  74. package/lib/thin/protocol/messages/rollback.js +56 -0
  75. package/lib/thin/protocol/messages/sessionRelease.js +60 -0
  76. package/lib/thin/protocol/messages/withData.js +812 -0
  77. package/lib/thin/protocol/oson.js +661 -0
  78. package/lib/thin/protocol/packet.js +519 -0
  79. package/lib/thin/protocol/protocol.js +202 -0
  80. package/lib/thin/protocol/utils.js +89 -0
  81. package/lib/thin/resultSet.js +122 -0
  82. package/lib/thin/sqlnet/connStrategy.js +175 -0
  83. package/lib/thin/sqlnet/constants.js +199 -0
  84. package/lib/thin/sqlnet/ezConnectResolver.js +488 -0
  85. package/lib/thin/sqlnet/navNodes.js +870 -0
  86. package/lib/thin/sqlnet/networkSession.js +621 -0
  87. package/lib/thin/sqlnet/ntTcp.js +527 -0
  88. package/lib/thin/sqlnet/nvStrToNvPair.js +783 -0
  89. package/lib/thin/sqlnet/packet.js +405 -0
  90. package/lib/thin/sqlnet/paramParser.js +206 -0
  91. package/lib/thin/sqlnet/sessionAtts.js +160 -0
  92. package/lib/thin/statement.js +300 -0
  93. package/lib/thin/util.js +289 -0
  94. package/lib/transformer.js +252 -0
  95. package/lib/types.js +357 -0
  96. package/lib/util.js +196 -110
  97. package/lib/version.js +36 -0
  98. package/package/README.md +33 -30
  99. package/package/install.js +89 -64
  100. package/package/prunebinaries.js +60 -12
  101. package/package.json +3 -3
  102. package/build/Release/oracledb-5.5.0-darwin-x64.node +0 -0
  103. package/build/Release/oracledb-5.5.0-darwin-x64.node-buildinfo.txt +0 -1
  104. package/build/Release/oracledb-5.5.0-linux-x64.node +0 -0
  105. package/build/Release/oracledb-5.5.0-linux-x64.node-buildinfo.txt +0 -1
  106. package/build/Release/oracledb-5.5.0-win32-x64.node +0 -0
  107. package/build/Release/oracledb-5.5.0-win32-x64.node-buildinfo.txt +0 -1
@@ -0,0 +1,1043 @@
1
+ // Copyright (c) 2022, 2023, 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 { Buffer } = require('buffer');
30
+ const ConnectionImpl = require('../impl/connection.js');
31
+ const ThinResultSetImpl = require('./resultSet.js');
32
+ const ThinLobImpl = require("./lob.js");
33
+ const Protocol = require("./protocol/protocol.js");
34
+ const { BaseBuffer } = require('./protocol/buffer.js');
35
+ const {NetworkSession:nsi, getConnectionInfo} = require("./sqlnet/networkSession.js");
36
+ const { Statement } = require("./statement");
37
+ const thinUtil = require('./util');
38
+ const sqlNetConstants = require('./sqlnet/constants.js');
39
+ const constants = require('./protocol/constants.js');
40
+ const process = require('process');
41
+ const types = require('../types.js');
42
+ const errors = require("../errors.js");
43
+ const messages = require('./protocol/messages');
44
+
45
+ const finalizationRegistry = new global.FinalizationRegistry((heldValue) => {
46
+ heldValue.disconnect();
47
+ });
48
+
49
+ class TDSBuffer extends BaseBuffer {
50
+ }
51
+
52
+ class ThinConnectionImpl extends ConnectionImpl {
53
+
54
+ /**
55
+ * Terminates the connection
56
+ *
57
+ * @return {Promise}
58
+ */
59
+ async close() {
60
+ if (this._protocol.txnInProgress) {
61
+ await this.rollback();
62
+ }
63
+ this._protocol.callTimeout = 0; // not applicable for close
64
+ if (this._drcpEnabled) {
65
+ await this._sessRelease();
66
+ this._drcpEstablishSession = true;
67
+ }
68
+
69
+ if (this._pool && !this._dropSess) {
70
+ await this._pool.release(this);
71
+ } else {
72
+ if (!this._drcpEnabled) {
73
+ const message = new messages.LogOffMessage(this);
74
+ await this._protocol._processMessage(message);
75
+ }
76
+ this.nscon.disconnect();
77
+ }
78
+ }
79
+
80
+ async _sessRelease() {
81
+ const message = new messages.SessionReleaseMessage(this);
82
+ if (!this.isPooled()) {
83
+ message.sessReleaseMode = constants.DRCP_DEAUTHENTICATE;
84
+ }
85
+ await this._protocol._processMessage(message);
86
+ }
87
+
88
+ //---------------------------------------------------------------------------
89
+ // _determineElementObjType()
90
+ //
91
+ // Determine the element type's object type. This is needed when processing
92
+ // collections with an object as the element type since this information is
93
+ // not available in the TDS.
94
+ //---------------------------------------------------------------------------
95
+ async _determineElementObjType(info) {
96
+ const binds = [
97
+ {
98
+ name: "owner",
99
+ type: types.DB_TYPE_VARCHAR,
100
+ dir: constants.BIND_IN,
101
+ maxSize: 128,
102
+ values: [info.schema]
103
+ },
104
+ {
105
+ name: "name",
106
+ type: types.DB_TYPE_VARCHAR,
107
+ dir: constants.BIND_IN,
108
+ maxSize: 128,
109
+ values: [info.name]
110
+ },
111
+ {
112
+ name: "package_name",
113
+ type: types.DB_TYPE_VARCHAR,
114
+ dir: constants.BIND_IN,
115
+ maxSize: 128,
116
+ values: [info.packageName]
117
+ }
118
+ ];
119
+ let sql;
120
+ if (info.packageName) {
121
+ sql = `
122
+ select
123
+ elem_type_owner,
124
+ elem_type_name,
125
+ elem_type_package
126
+ from all_plsql_coll_types
127
+ where owner = :owner
128
+ and type_name = :name
129
+ and package_name = :package_name`;
130
+ } else {
131
+ binds.pop();
132
+ sql = `
133
+ select
134
+ elem_type_owner,
135
+ elem_type_name
136
+ from all_coll_types
137
+ where owner = :owner
138
+ and type_name = :name`;
139
+ }
140
+ const options = {
141
+ connection: { _impl: this },
142
+ prefetchRows: 2
143
+ };
144
+ const result = await this.execute(sql, 1, binds, options, false);
145
+ const rows = await result.resultSet.getRows(1, options);
146
+ await result.resultSet.close();
147
+ const row = rows[0];
148
+ info.elementTypeClass = this._getDbObjectType(row[0], row[1], row[2]);
149
+ if (info.elementTypeClass.partial) {
150
+ this._partialDbObjectTypes.push(info.elementTypeClass);
151
+ }
152
+ }
153
+
154
+ //---------------------------------------------------------------------------
155
+ // _parseElementType()
156
+ //
157
+ // Parses the element type from the TDS buffer.
158
+ //---------------------------------------------------------------------------
159
+ async _parseElementType(buf, info) {
160
+ let oraTypeNum, csfrm;
161
+ const attrType = buf.readUInt8();
162
+ switch (attrType) {
163
+ case constants.TNS_OBJ_TDS_TYPE_NUMBER:
164
+ case constants.TNS_OBJ_TDS_TYPE_FLOAT:
165
+ info.elementType = types.DB_TYPE_NUMBER;
166
+ break;
167
+ case constants.TNS_OBJ_TDS_TYPE_VARCHAR:
168
+ case constants.TNS_OBJ_TDS_TYPE_CHAR:
169
+ info.maxSize = buf.readUInt16BE();
170
+ oraTypeNum = (attrType === constants.TNS_OBJ_TDS_TYPE_VARCHAR) ?
171
+ constants.TNS_DATA_TYPE_VARCHAR : constants.TNS_DATA_TYPE_CHAR;
172
+ csfrm = buf.readUInt8();
173
+ info.elementType = types.getTypeByOraTypeNum(oraTypeNum, csfrm);
174
+ break;
175
+ case constants.TNS_OBJ_TDS_TYPE_RAW:
176
+ info.elementType = types.DB_TYPE_RAW;
177
+ break;
178
+ case constants.TNS_OBJ_TDS_TYPE_BINARY_FLOAT:
179
+ info.elementType = types.DB_TYPE_BINARY_FLOAT;
180
+ break;
181
+ case constants.TNS_OBJ_TDS_TYPE_BINARY_DOUBLE:
182
+ info.elementType = types.DB_TYPE_BINARY_DOUBLE;
183
+ break;
184
+ case constants.TNS_OBJ_TDS_TYPE_DATE:
185
+ info.elementType = types.DB_TYPE_DATE;
186
+ break;
187
+ case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP:
188
+ info.elementType = types.DB_TYPE_TIMESTAMP;
189
+ break;
190
+ case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP_LTZ:
191
+ info.elementType = types.DB_TYPE_TIMESTAMP_LTZ;
192
+ break;
193
+ case constants.TNS_OBJ_TDS_TYPE_TIMESTAMP_TZ:
194
+ info.elementType = types.DB_TYPE_TIMESTAMP_TZ;
195
+ break;
196
+ case constants.TNS_OBJ_TDS_TYPE_BOOLEAN:
197
+ info.elementType = types.DB_TYPE_BOOLEAN;
198
+ break;
199
+ case constants.TNS_OBJ_TDS_TYPE_CLOB:
200
+ this._determineElementTypeCharsetForm(info);
201
+ break;
202
+ case constants.TNS_OBJ_TDS_TYPE_BLOB:
203
+ info.elementType = types.DB_TYPE_BLOB;
204
+ break;
205
+ case constants.TNS_OBJ_TDS_TYPE_OBJ:
206
+ info.elementType = types.DB_TYPE_OBJECT;
207
+ await this._determineElementObjType(info);
208
+ break;
209
+ default:
210
+ errors.throwErr(errors.ERR_TDS_TYPE_NOT_SUPPORTED, attrType);
211
+ }
212
+ }
213
+
214
+ //---------------------------------------------------------------------------
215
+ // _parseTDS()
216
+ //
217
+ // Parses the TDS for the type. This is only needed for collection types, so
218
+ // if the TDS is determined to be for an object type, the remaining
219
+ // information is ignored.
220
+ //---------------------------------------------------------------------------
221
+ async _parseTDS(tds, info) {
222
+
223
+ // parse initial TDS bytes
224
+ const buf = new TDSBuffer(tds);
225
+ buf.skipBytes(4); // end offset
226
+ buf.skipBytes(2); // version op code and version
227
+ buf.skipBytes(2); // unknown
228
+
229
+ // if the number of attributes exceeds 1, the type cannot refer to a
230
+ // collection, so nothing further needs to be done
231
+ const numAttrs = buf.readUInt16BE();
232
+ if (numAttrs > 1) {
233
+ info.isCollection = false;
234
+ return;
235
+ }
236
+
237
+ // continue parsing TDS bytes to discover if type refers to a collection
238
+ buf.skipBytes(1); // TDS attributes?
239
+ buf.skipBytes(1); // start ADT op code
240
+ buf.skipBytes(2); // ADT number (always zero)
241
+ buf.skipBytes(4); // offset to index table
242
+
243
+ // if type of first attribute is not a collection, nothing further needs
244
+ // to be done
245
+ const attrType = buf.readUInt8();
246
+ info.isCollection = (attrType === constants.TNS_OBJ_TDS_TYPE_COLL);
247
+ if (!info.isCollection)
248
+ return;
249
+
250
+ // continue parsing TDS to determine element type
251
+ const elementPos = buf.readUInt32BE();
252
+ info.maxNumElements = buf.readUInt32BE();
253
+ info.collectionType = buf.readUInt8();
254
+ if (info.collectionType === constants.TNS_OBJ_PLSQL_INDEX_TABLE) {
255
+ info.collectionFlags = constants.TNS_OBJ_HAS_INDEXES;
256
+ }
257
+ buf.pos = elementPos;
258
+
259
+ await this._parseElementType(buf, info);
260
+ }
261
+
262
+ //---------------------------------------------------------------------------
263
+ // _populateDbObjectTypeInfo()
264
+ //
265
+ // Poplates type information given the name of the type.
266
+ //---------------------------------------------------------------------------
267
+ async _populateDbObjectTypeInfo(name) {
268
+
269
+ // get type information from the database
270
+ const sql = `
271
+ declare
272
+ t_Instantiable varchar2(3);
273
+ t_SuperTypeOwner varchar2(128);
274
+ t_SuperTypeName varchar2(128);
275
+ t_SubTypeRefCursor sys_refcursor;
276
+ t_Pos pls_integer;
277
+ begin
278
+ :ret_val := dbms_pickler.get_type_shape(:full_name, :oid,
279
+ :version, :tds, t_Instantiable, t_SuperTypeOwner,
280
+ t_SuperTypeName, :attrs_rc, t_SubTypeRefCursor);
281
+ :package_name := null;
282
+ if substr(:full_name, length(:full_name) - 7) = '%ROWTYPE' then
283
+ t_Pos := instr(:full_name, '.');
284
+ :schema := substr(:full_name, 1, t_Pos - 1);
285
+ :name := substr(:full_name, t_Pos + 1);
286
+ else
287
+ begin
288
+ select owner, type_name
289
+ into :schema, :name
290
+ from all_types
291
+ where type_oid = :oid;
292
+ exception
293
+ when no_data_found then
294
+ begin
295
+ select owner, package_name, type_name
296
+ into :schema, :package_name, :name
297
+ from all_plsql_types
298
+ where type_oid = :oid;
299
+ exception
300
+ when no_data_found then
301
+ null;
302
+ end;
303
+ end;
304
+ end if;
305
+ end;`;
306
+ const binds = [
307
+ {
308
+ name: "full_name",
309
+ type: types.DB_TYPE_VARCHAR,
310
+ dir: constants.BIND_INOUT,
311
+ maxSize: 500,
312
+ values: [name]
313
+ },
314
+ {
315
+ name: "ret_val",
316
+ type: types.DB_TYPE_BINARY_INTEGER,
317
+ dir: constants.BIND_OUT,
318
+ values: []
319
+ },
320
+ {
321
+ name: "oid",
322
+ type: types.DB_TYPE_RAW,
323
+ maxSize: 16,
324
+ dir: constants.BIND_OUT,
325
+ values: []
326
+ },
327
+ {
328
+ name: "version",
329
+ type: types.DB_TYPE_BINARY_INTEGER,
330
+ dir: constants.BIND_OUT,
331
+ values: []
332
+ },
333
+ {
334
+ name: "tds",
335
+ type: types.DB_TYPE_RAW,
336
+ maxSize: 2000,
337
+ dir: constants.BIND_OUT,
338
+ values: []
339
+ },
340
+ {
341
+ name: "attrs_rc",
342
+ type: types.DB_TYPE_CURSOR,
343
+ dir: constants.BIND_OUT,
344
+ values: []
345
+ },
346
+ {
347
+ name: "package_name",
348
+ type: types.DB_TYPE_VARCHAR,
349
+ maxSize: 128,
350
+ dir: constants.BIND_OUT,
351
+ values: []
352
+ },
353
+ {
354
+ name: "schema",
355
+ type: types.DB_TYPE_VARCHAR,
356
+ maxSize: 128,
357
+ dir: constants.BIND_OUT,
358
+ values: []
359
+ },
360
+ {
361
+ name: "name",
362
+ type: types.DB_TYPE_VARCHAR,
363
+ maxSize: 128,
364
+ dir: constants.BIND_OUT,
365
+ values: []
366
+ }
367
+ ];
368
+ const options = {
369
+ connection: { _impl: this },
370
+ nullifyInvalidCursor: true
371
+ };
372
+ const result = await this.execute(sql, 1, binds, options, false);
373
+ if (result.outBinds.ret_val !== 0) {
374
+ errors.throwErr(errors.ERR_INVALID_OBJECT_TYPE_NAME, name);
375
+ }
376
+
377
+ // check cache; if already present, nothing more to do!
378
+ const info = this._getDbObjectType(result.outBinds.schema,
379
+ result.outBinds.name, result.outBinds.package_name, result.outBinds.oid);
380
+ if (!info.partial)
381
+ return info;
382
+
383
+ // process TDS and attributes cursor
384
+ info.version = result.outBinds.version;
385
+ await this._parseTDS(result.outBinds.tds, info);
386
+ const attrRows = await result.outBinds.attrs_rc.getRows(1000, {});
387
+ if (attrRows.length > 0) {
388
+ info.attributes = [];
389
+ for (const row of attrRows) {
390
+ const attr = { name: row[1] };
391
+ if (row[4]) {
392
+ attr.type = types.DB_TYPE_OBJECT;
393
+ attr.typeClass = this._getDbObjectType(row[4], row[3], row[5], row[6]);
394
+ if (attr.typeClass.partial) {
395
+ this._partialDbObjectTypes.push(attr.typeClass);
396
+ }
397
+ } else {
398
+ attr.type = types.getTypeByColumnTypeName(row[3]);
399
+ }
400
+ info.attributes.push(attr);
401
+ }
402
+ }
403
+ info.partial = false;
404
+
405
+ return info;
406
+
407
+ }
408
+
409
+ //---------------------------------------------------------------------------
410
+ // _populatePartialDbObjectTypes()
411
+ //
412
+ // Populates partial types that were discovered earlier. Since populating an
413
+ // object type might result in additional object types being discovered,
414
+ // object types are popped from the partial types list until the list is
415
+ // empty.
416
+ //---------------------------------------------------------------------------
417
+ async _populatePartialDbObjectTypes() {
418
+ while (this._partialDbObjectTypes.length > 0) {
419
+ const info = this._partialDbObjectTypes.pop();
420
+ let suffix = "%ROWTYPE";
421
+ let name = info.name;
422
+ if (name.endsWith(suffix)) {
423
+ name = name.substring(0, name.length - suffix.length);
424
+ } else {
425
+ suffix = "";
426
+ }
427
+ let fullName;
428
+ if (info.packageName) {
429
+ fullName = `"${info.schema}"."${info.packageName}"."${name}"${suffix}`;
430
+ } else {
431
+ fullName = `"${info.schema}"."${name}"${suffix}`;
432
+ }
433
+ await this._populateDbObjectTypeInfo(fullName);
434
+ }
435
+ }
436
+
437
+ async commit() {
438
+ const message = new messages.CommitMessage(this);
439
+ await this._protocol._processMessage(message);
440
+ }
441
+
442
+ async breakExecution() {
443
+ await this._protocol.breakMessage();
444
+ }
445
+
446
+ _healthStatus() {
447
+ return this.nscon.recvInbandNotif();
448
+ }
449
+
450
+ isHealthy() {
451
+ try {
452
+ if (this.nscon.recvInbandNotif() === 0)
453
+ return true;
454
+ return false;
455
+ } catch {
456
+ return false;
457
+ }
458
+ }
459
+
460
+ isPooled() {
461
+ return (this._pool) ? true : false;
462
+ }
463
+
464
+ /**
465
+ *
466
+ * @param {object} params Configuration of the connection
467
+ *
468
+ * @return {Promise}
469
+ */
470
+ async connect(params) {
471
+ if (!params.connectString) {
472
+ errors.throwErr(errors.ERR_EMPTY_CONNECT_STRING);
473
+ }
474
+ thinUtil.checkCredentials(params);
475
+
476
+ this.sessionID = 0;
477
+ this.serialNum = 0;
478
+ this.autoCommit = false;
479
+ this.serverVersion = "";
480
+ this.statementCache = null;
481
+ this.currentSchema = "";
482
+ this.invokeSessionCallback = true;
483
+ this.statementCache = new Map();
484
+ this.statementCacheSize = params.stmtCacheSize;
485
+ this._numCursorsToClose = 0;
486
+ this._currentSchemaModified = false;
487
+ this._cursorsToClose = new Set();
488
+ this._tempLobsToClose = [];
489
+ this._tempLobsTotalSize = 0;
490
+ this._drcpEstablishSession = false;
491
+ this._cclass = null;
492
+ this._clientIdentifier = "";
493
+ this._clientIdentifierModified = false;
494
+ this._action = "";
495
+ this._actionModified = false;
496
+ this._dbOp = "";
497
+ this._dbOpModified = false;
498
+ this._clientInfo = "";
499
+ this._clientInfoModified = false;
500
+ this._module = "";
501
+ this._moduleModified = false;
502
+ this.serviceName = '';
503
+ this.remoteAddress = '';
504
+ this.privilege = params.privilege;
505
+ this.comboKey = null; // used in changePassword API
506
+
507
+ this.nscon = new nsi();
508
+ finalizationRegistry.register(this, this.nscon);
509
+ await this.nscon.connect(params);
510
+
511
+ this._connInfo = (this.isPooled()) ? params._connInfo.map((x) => x) :
512
+ await getConnectionInfo(params);
513
+ this._drcpEnabled = String(this._connInfo[0]).toLowerCase() === 'pooled';
514
+ this.serviceName = this._connInfo[2];
515
+ this.purity = this._connInfo[3] | constants.PURITY_DEFAULT;
516
+ this.remoteAddress = this.nscon.getOption(sqlNetConstants.REMOTEADDR);
517
+ this.connClass = params.connectionClass;
518
+
519
+ /*
520
+ * if drcp is used, use purity = NEW as the default purity for
521
+ * standalone connections and purity = SELF for connections that belong
522
+ * to a pool
523
+ */
524
+ if (this.purity === constants.PURITY_DEFAULT && this._drcpEnabled) {
525
+ if (this.isPooled()) {
526
+ this.purity = constants.PURITY_SELF;
527
+ } else {
528
+ this.purity = constants.PURITY_NEW;
529
+ }
530
+ }
531
+
532
+ this._protocol = new Protocol(this);
533
+
534
+ // check if the protocol version supported by the database is high
535
+ // enough; if not, reject the connection immediately
536
+ if (this._protocol.caps.protocolVersion < constants.TNS_VERSION_MIN_ACCEPTED) {
537
+ errors.throwErr(errors.ERR_SERVER_VERSION_NOT_SUPPORTED);
538
+ }
539
+
540
+ try {
541
+ let message = new messages.ProtocolMessage(this);
542
+ await this._protocol._processMessage(message);
543
+ message = new messages.DataTypeMessage(this);
544
+ await this._protocol._processMessage(message);
545
+ message = new messages.AuthMessage(this, params);
546
+ await this._protocol._processMessage(message); // OSESSKEY
547
+ await this._protocol._processMessage(message); // OAUTH
548
+ } catch (err) {
549
+ this.nscon.disconnect();
550
+ throw err;
551
+ }
552
+
553
+ // maintain a list of partially populated database object types
554
+ this._partialDbObjectTypes = [];
555
+
556
+ if (params.debugJDWP) {
557
+ this.jdwpData = Buffer.from(params.debugJDWP);
558
+ } else if (process.env.ORA_DEBUG_JDWP) {
559
+ this.jdwpData = Buffer.from(process.env.ORA_DEBUG_JDWP);
560
+ }
561
+ this._protocol.connInProgress = false;
562
+ }
563
+
564
+ //---------------------------------------------------------------------------
565
+ // Sets that a statement is no longer in use
566
+ //---------------------------------------------------------------------------
567
+ _returnStatement(statement) {
568
+ if (statement.bindInfoList) {
569
+ statement.bindInfoList.forEach(bindInfo => {
570
+ bindInfo.bindVar = null;
571
+ });
572
+ }
573
+ if (statement.queryVars) {
574
+ statement.queryVars.forEach(queryVar => {
575
+ queryVar.values.fill(null);
576
+ });
577
+ }
578
+ if (statement.returnToCache) {
579
+ statement.inUse = false;
580
+ } else if (statement.cursorId !== 0) {
581
+ this._addCursorToClose(statement.cursorId);
582
+ }
583
+ }
584
+
585
+ //---------------------------------------------------------------------------
586
+ // Adds the cursors that needs to be closed to the _cursorsToClose set
587
+ //---------------------------------------------------------------------------
588
+ _addCursorToClose(cursorId) {
589
+ if (this._cursorsToClose.has(cursorId)) {
590
+ const reason = `attempt to close cursor ${cursorId} twice`;
591
+ errors.throwErr(errors.ERR_INTERNAL, reason);
592
+ }
593
+ this._cursorsToClose.add(cursorId);
594
+ }
595
+
596
+ //---------------------------------------------------------------------------
597
+ // Adjusts the statement cache to remove least recently used statements
598
+ //---------------------------------------------------------------------------
599
+ _adjustStatementCache() {
600
+ while (this.statementCache.size > this.statementCacheSize) {
601
+ const sql = this.statementCache.keys().next().value;
602
+ const stmt = this.statementCache.get(sql);
603
+ this.statementCache.delete(sql);
604
+ if (stmt.inUse) {
605
+ stmt.returnToCache = false;
606
+ } else if (stmt.cursorId !== 0) {
607
+ this._addCursorToClose(stmt.cursorId);
608
+ }
609
+ }
610
+ }
611
+
612
+ //---------------------------------------------------------------------------
613
+ // Parses the sql statement and puts it into cache if keepInStmtCache
614
+ // option is true
615
+ //---------------------------------------------------------------------------
616
+ _prepare(sql, options) {
617
+ let statement = this._getStatement(sql, options.keepInStmtCache);
618
+ statement.bufferRowIndex = 0;
619
+ statement.bufferRowCount = 0;
620
+ statement.lastRowIndex = 0;
621
+ statement.moreRowsToFetch = true;
622
+ return statement;
623
+ }
624
+
625
+ //---------------------------------------------------------------------------
626
+ // Binds the values by user to the statement object
627
+ //---------------------------------------------------------------------------
628
+ async _bind(stmt, variable, pos = 0) {
629
+ let bindInfoDict = stmt.bindInfoDict;
630
+ let bindInfoList = stmt.bindInfoList;
631
+
632
+ /*
633
+ * For PL/SQL blocks, if the size of a string or bytes object exceeds
634
+ * 32,767 bytes it is converted to a BLOB/CLOB; and conversion
635
+ * needs to be established as well to return the string in the way that
636
+ * the user expects to get it
637
+ */
638
+ if (stmt.isPlSql && variable.maxSize > 32767) {
639
+ if (variable.type === types.DB_TYPE_RAW ||
640
+ variable.type === types.DB_TYPE_LONG_RAW) {
641
+ variable.type = types.DB_TYPE_BLOB;
642
+ } else if (variable.type._csfrm === constants.CSFRM_NCHAR) {
643
+ variable.type = types.DB_TYPE_NCLOB;
644
+ } else {
645
+ variable.type = types.DB_TYPE_CLOB;
646
+ }
647
+ const maxSize = variable.maxSize;
648
+ delete variable.maxSize;
649
+ variable.outConverter = async function(val) {
650
+ if (val === null) {
651
+ return null;
652
+ }
653
+ let data = await val.getData();
654
+ let len = val._length;
655
+ if (data && len > maxSize) {
656
+ errors.throwErr(errors.ERR_INSUFFICIENT_BUFFER_FOR_BINDS);
657
+ }
658
+ return data;
659
+ };
660
+ }
661
+
662
+ if (variable.type === types.DB_TYPE_CLOB ||
663
+ variable.type === types.DB_TYPE_NCLOB ||
664
+ variable.type === types.DB_TYPE_BLOB) {
665
+ for (let [index, val] of variable.values.entries()) {
666
+ if (!(val instanceof ThinLobImpl)) {
667
+ if (val && val.length > 0) {
668
+ const lobImpl = new ThinLobImpl();
669
+ await lobImpl.create(this, variable.type);
670
+ await lobImpl.write(1, val);
671
+ variable.values[index] = lobImpl;
672
+ } else {
673
+ variable.values[index] = null;
674
+ }
675
+ }
676
+ }
677
+ }
678
+
679
+ if (variable.name) {
680
+ let normalizedName;
681
+ if (variable.name.startsWith('"') && variable.name.endsWith('"')) {
682
+ normalizedName = variable.name.substring(1, variable.name.length - 1);
683
+ } else {
684
+ normalizedName = variable.name.toUpperCase();
685
+ }
686
+ if (normalizedName.startsWith(':')) {
687
+ normalizedName = variable.name.substring(1);
688
+ }
689
+ if (!bindInfoDict.has(normalizedName)) {
690
+ errors.throwErr(errors.ERR_INVALID_BIND_NAME, normalizedName);
691
+ }
692
+ bindInfoDict.get(normalizedName).forEach((bindInfo) => {
693
+ stmt._setVariable(bindInfo, variable);
694
+ });
695
+ } else {
696
+ let bindInfo = bindInfoList[pos - 1];
697
+ stmt._setVariable(bindInfo, variable);
698
+ }
699
+ }
700
+
701
+ //---------------------------------------------------------------------------
702
+ // _createResultSet()
703
+ //
704
+ // Creates a result set and performs any necessary initialization.
705
+ //---------------------------------------------------------------------------
706
+ _createResultSet(options, statement) {
707
+ const resultSet = new ThinResultSetImpl();
708
+ if (!statement) {
709
+ statement = new Statement();
710
+ }
711
+ resultSet._resultSetNew(this, statement, options);
712
+ if (statement.queryVars.length > 0) {
713
+ const metadata = thinUtil.getMetadataMany(statement.queryVars);
714
+ resultSet._setup(options, metadata);
715
+ }
716
+ return resultSet;
717
+ }
718
+
719
+ //---------------------------------------------------------------------------
720
+ // Prepares the sql given by user and binds the value to the statement object
721
+ //---------------------------------------------------------------------------
722
+ async _prepareAndBind(sql, binds, options, isParse = false) {
723
+ const stmt = this._prepare(sql, options);
724
+ let position = 0;
725
+ if (!isParse) {
726
+ const numBinds = stmt.bindInfoList.length;
727
+ const numVars = binds.length;
728
+ if (numBinds !== numVars && (numVars === 0 || !binds[0].name)) {
729
+ errors.throwErr(errors.ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, numBinds, numVars);
730
+ }
731
+ for (const variable of binds) {
732
+ await this._bind(stmt, variable, position + 1);
733
+ position += 1;
734
+ }
735
+ }
736
+ return stmt;
737
+ }
738
+
739
+ //---------------------------------------------------------------------------
740
+ // Clears the statement cache for the connection
741
+ //---------------------------------------------------------------------------
742
+ resetStatmentCache() {
743
+ this.statementCache.clear();
744
+ this._cursorsToClose.clear();
745
+ }
746
+
747
+ //---------------------------------------------------------------------------
748
+ // getDbObjectClass()
749
+ //
750
+ // Returns a database object class given its name.
751
+ //---------------------------------------------------------------------------
752
+ async getDbObjectClass(name) {
753
+ const info = await this._populateDbObjectTypeInfo(name);
754
+ await this._populatePartialDbObjectTypes();
755
+ return info;
756
+ }
757
+
758
+ //---------------------------------------------------------------------------
759
+ // Parses the sql given by User
760
+ // calls the OAL8 RPC that parses the SQL statement and returns the metadata
761
+ // information for a statment.
762
+ //---------------------------------------------------------------------------
763
+ async getStatementInfo(sql) {
764
+ let options = {};
765
+ let result = {};
766
+ let statement = await this._prepareAndBind(sql, null, options, true);
767
+ options.connection = this;
768
+ // parse the statement (but not for DDL which doesn't support it)
769
+ if (!statement.isDdl) {
770
+ const message = new messages.ExecuteMessage(this, statement, options);
771
+ message.parseOnly = true;
772
+ await this._protocol._processMessage(message);
773
+ }
774
+ if (statement.numQueryVars > 0) {
775
+ result.metaData = thinUtil.getMetadataMany(statement.queryVars);
776
+ }
777
+ result.bindNames = Array.from(statement.bindInfoDict.keys());
778
+ result.statementType = statement.statementType;
779
+ return result;
780
+ }
781
+
782
+ //---------------------------------------------------------------------------
783
+ // Prepares the sql given by the user,
784
+ // calls the OAL8 RPC that executes a SQL statement and returns the results.
785
+ //---------------------------------------------------------------------------
786
+ async execute(sql, numIters, binds, options, executeManyFlag) {
787
+ const result = {};
788
+ if (executeManyFlag) {
789
+ return await this.executeMany(sql, numIters, binds, options);
790
+ }
791
+ const statement = await this._prepareAndBind(sql, binds, options);
792
+
793
+ // send the initial request to the database
794
+ const message = new messages.ExecuteMessage(this, statement, options);
795
+ message.numExecs = 1;
796
+ await this._protocol._processMessage(message);
797
+ statement.requiresFullExecute = false;
798
+
799
+ // if a define is required, send an additional request to the database
800
+ if (statement.requiresDefine && statement.sql) {
801
+ statement.requiresFullExecute = true;
802
+ await this._protocol._processMessage(message);
803
+ statement.requiresFullExecute = false;
804
+ statement.requiresDefine = false;
805
+ }
806
+
807
+ // process message results
808
+ if (statement.numQueryVars > 0) {
809
+ result.resultSet = message.resultSet;
810
+ } else {
811
+ statement.bufferRowIndex = 0;
812
+ const bindVars = thinUtil.getBindVars(statement);
813
+ const outBinds = thinUtil.getExecuteOutBinds(bindVars);
814
+ if (outBinds) {
815
+ result.outBinds = outBinds;
816
+ }
817
+ if (statement.isPlSql) {
818
+ if (options.implicitResultSet) {
819
+ result.implicitResults = options.implicitResultSet;
820
+ }
821
+ }
822
+ if (statement.lastRowid) {
823
+ result.lastRowid = statement.lastRowid;
824
+ delete statement.lastRowid;
825
+ }
826
+ if (statement.isPlSql) {
827
+ if (statement.rowCount) {
828
+ result.rowsAffected = statement.rowCount;
829
+ }
830
+ } else {
831
+ result.rowsAffected = statement.rowCount || 0;
832
+ }
833
+ if (statement.rowCount) {
834
+ delete statement.rowCount;
835
+ }
836
+ this._returnStatement(statement);
837
+ }
838
+
839
+ return result;
840
+ }
841
+
842
+ //---------------------------------------------------------------------------
843
+ // executeMany()
844
+ //
845
+ // Prepares the sql given by the user, calls the OAL8 RPC that executes a SQL
846
+ // statement multiple times and returns the results.
847
+ //---------------------------------------------------------------------------
848
+ async executeMany(sql, numIters, binds, options) {
849
+ const statement = await this._prepareAndBind(sql, binds, options);
850
+ if (statement.isPlSql && (options.batchErrors || options.dmlRowCounts)) {
851
+ errors.throwErr(errors.ERR_EXEC_MODE_ONLY_FOR_DML);
852
+ }
853
+
854
+ // send database request
855
+ const message = new messages.ExecuteMessage(this, statement, options);
856
+ message.numExecs = numIters;
857
+ message.arrayDmlRowCounts = options.dmlRowCounts;
858
+ message.batchErrors = options.batchErrors;
859
+ if (statement.isPlSql && statement.requiresFullExecute) {
860
+ message.numExecs = 1;
861
+ await this._protocol._processMessage(message);
862
+ statement.requiresFullExecute = false;
863
+ message.numExecs = numIters - 1;
864
+ message.offset = 1;
865
+ }
866
+ if (message.numExecs > 0) {
867
+ await this._protocol._processMessage(message);
868
+ }
869
+
870
+ // process results
871
+ const returnObj = {};
872
+ statement.bufferRowIndex = 0;
873
+ const bindVars = thinUtil.getBindVars(statement);
874
+ const outBinds = thinUtil.getExecuteManyOutBinds(bindVars, numIters);
875
+ if (outBinds) {
876
+ returnObj.outBinds = outBinds;
877
+ }
878
+ const rowsAffected = !statement.isPlSql ? statement.rowCount : undefined;
879
+ if (rowsAffected) {
880
+ returnObj.rowsAffected = rowsAffected;
881
+ delete statement.rowCount;
882
+ }
883
+ if (options.dmlRowCounts) {
884
+ returnObj.dmlRowCounts = options.dmlRowCounts;
885
+ }
886
+ if (options.batchErrors) {
887
+ returnObj.batchErrors = options.batchErrors;
888
+ }
889
+ this._returnStatement(statement);
890
+
891
+ return returnObj;
892
+ }
893
+
894
+ //---------------------------------------------------------------------------
895
+ // Get the statement object from the statement cache for the SQL if it exists
896
+ // else prepare a new statement object for the SQL. If a statement is already
897
+ // in use a copy will be made and returned (and will not be returned to the
898
+ // cache). If a statement is being executed for the first time after releasing
899
+ // a DRCP session, a copy will also be made (and will not be returned to the
900
+ // cache) since it is unknown at this point whether the original session or a
901
+ // new session is going to be used.
902
+ //---------------------------------------------------------------------------
903
+ _getStatement(sql, cacheStatement = false) {
904
+ let statement = this.statementCache.get(sql);
905
+ if (!statement) {
906
+ statement = new Statement();
907
+ statement._prepare(sql);
908
+ if (cacheStatement && !this._drcpEstablishSession && !statement.isDdl &&
909
+ this.statementCacheSize > 0) {
910
+ statement.returnToCache = true;
911
+ this.statementCache.set(sql, statement);
912
+ this._adjustStatementCache();
913
+ }
914
+ } else if (statement.inUse || !cacheStatement ||
915
+ this._drcpEstablishSession) {
916
+ if (!cacheStatement) {
917
+ this.statementCache.delete(sql);
918
+ statement.returnToCache = false;
919
+ }
920
+ if (statement.inUse || this._drcpEstablishSession) {
921
+ statement = statement._copy();
922
+ }
923
+ } else {
924
+ this.statementCache.delete(sql);
925
+ this.statementCache.set(sql, statement);
926
+ }
927
+ statement.inUse = true;
928
+ return statement;
929
+ }
930
+
931
+ //---------------------------------------------------------------------------
932
+ // Calls the ping RPC for Oracle Database
933
+ //---------------------------------------------------------------------------
934
+ async ping() {
935
+ const message = new messages.PingMessage(this);
936
+ await this._protocol._processMessage(message);
937
+ }
938
+
939
+ //---------------------------------------------------------------------------
940
+ // Calls the Rollback RPC for Oracle Database
941
+ //---------------------------------------------------------------------------
942
+ async rollback() {
943
+ const message = new messages.RollbackMessage(this);
944
+ await this._protocol._processMessage(message);
945
+ }
946
+
947
+ //---------------------------------------------------------------------------
948
+ // Returns the Oracle Server version
949
+ //---------------------------------------------------------------------------
950
+ getOracleServerVersion() {
951
+ return this.serverVersion;
952
+ }
953
+
954
+ //---------------------------------------------------------------------------
955
+ // Returns the Oracle Server version string
956
+ //---------------------------------------------------------------------------
957
+ getOracleServerVersionString() {
958
+ return this.serverVersionString;
959
+ }
960
+
961
+ setCurrentSchema(schema) {
962
+ this._currentSchemaModified = true;
963
+ this.currentSchema = schema;
964
+ }
965
+
966
+ getCurrentSchema() {
967
+ return this.currentSchema;
968
+ }
969
+
970
+ setClientId(clientId) {
971
+ this._clientIdentifierModified = true;
972
+ this._clientIdentifier = clientId;
973
+ }
974
+
975
+ setDbOp(dbOp) {
976
+ this._dbOpModified = true;
977
+ this._dbOp = dbOp;
978
+ }
979
+
980
+ setClientInfo(clientInfo) {
981
+ this._clientInfoModified = true;
982
+ this._clientInfo = clientInfo;
983
+ }
984
+
985
+ setModule(module) {
986
+ this._moduleModified = true;
987
+ this._module = module;
988
+
989
+ /*
990
+ * setting the module by itself results in an error so always force
991
+ * action to be set as well (which eliminates this error)
992
+ */
993
+ this._actionModified = true;
994
+ }
995
+
996
+ setAction(action) {
997
+ this._actionModified = true;
998
+ this._action = action;
999
+ }
1000
+
1001
+ async changePassword(user, password, newPassword) {
1002
+ const config = {
1003
+ user: user,
1004
+ newPassword: newPassword,
1005
+ password: password,
1006
+ changePassword: true
1007
+ };
1008
+ const message = new messages.AuthMessage(this, config);
1009
+ await this._protocol._processMessage(message); // OAUTH
1010
+ }
1011
+
1012
+ async createLob(dbType) {
1013
+ const lobImpl = new ThinLobImpl();
1014
+ await lobImpl.create(this, dbType);
1015
+ return lobImpl;
1016
+ }
1017
+
1018
+ //---------------------------------------------------------------------------
1019
+ // Returns the statement cache size for the statement cache maintained by
1020
+ // the connection object
1021
+ //---------------------------------------------------------------------------
1022
+ getStmtCacheSize() {
1023
+ return this.statementCacheSize;
1024
+ }
1025
+
1026
+ setCallTimeout(timeout) {
1027
+ this._protocol.callTimeout = timeout;
1028
+ }
1029
+
1030
+ getCallTimeout() {
1031
+ return this._protocol.callTimeout;
1032
+ }
1033
+
1034
+ //---------------------------------------------------------------------------
1035
+ // Returns getTag. Actual tag returned by db must be a string.
1036
+ //---------------------------------------------------------------------------
1037
+ getTag() {
1038
+ return '';
1039
+ }
1040
+
1041
+ }
1042
+
1043
+ module.exports = ThinConnectionImpl;