node-firebird 2.9.0 → 2.11.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 (50) hide show
  1. package/README.md +211 -6
  2. package/lib/index.d.ts +5 -0
  3. package/lib/index.js +32 -1
  4. package/lib/pool.js +1 -1
  5. package/lib/srp.d.ts +3 -3
  6. package/lib/types.d.ts +165 -5
  7. package/lib/uri.js +2 -2
  8. package/lib/wire/connection.d.ts +92 -59
  9. package/lib/wire/connection.js +279 -58
  10. package/lib/wire/const.d.ts +9 -1
  11. package/lib/wire/const.js +23 -9
  12. package/lib/wire/database.d.ts +51 -26
  13. package/lib/wire/database.js +53 -20
  14. package/lib/wire/eventConnection.js +5 -3
  15. package/lib/wire/query-stream.d.ts +18 -0
  16. package/lib/wire/query-stream.js +73 -0
  17. package/lib/wire/serialize.d.ts +20 -2
  18. package/lib/wire/serialize.js +7 -0
  19. package/lib/wire/service.d.ts +42 -0
  20. package/lib/wire/service.js +145 -0
  21. package/lib/wire/socket.d.ts +3 -1
  22. package/lib/wire/socket.js +5 -2
  23. package/lib/wire/statement.d.ts +31 -19
  24. package/lib/wire/statement.js +1 -5
  25. package/lib/wire/transaction.d.ts +30 -18
  26. package/lib/wire/transaction.js +23 -4
  27. package/lib/wire/wire-types.d.ts +116 -0
  28. package/lib/wire/wire-types.js +10 -0
  29. package/lib/wire/xsqlvar.d.ts +57 -18
  30. package/lib/wire/xsqlvar.js +59 -0
  31. package/package.json +1 -1
  32. package/src/index.ts +36 -4
  33. package/src/messages.ts +1 -1
  34. package/src/pool.ts +1 -1
  35. package/src/srp.ts +6 -6
  36. package/src/types.ts +162 -5
  37. package/src/unix-crypt.ts +9 -9
  38. package/src/uri.ts +2 -2
  39. package/src/wire/connection.ts +475 -234
  40. package/src/wire/const.ts +23 -9
  41. package/src/wire/database.ts +101 -54
  42. package/src/wire/eventConnection.ts +8 -5
  43. package/src/wire/query-stream.ts +80 -0
  44. package/src/wire/serialize.ts +31 -0
  45. package/src/wire/service.ts +188 -6
  46. package/src/wire/socket.ts +17 -8
  47. package/src/wire/statement.ts +37 -29
  48. package/src/wire/transaction.ts +57 -32
  49. package/src/wire/wire-types.ts +127 -0
  50. package/src/wire/xsqlvar.ts +85 -7
@@ -3,7 +3,7 @@ import os from 'os';
3
3
  import path from 'path';
4
4
 
5
5
  import { XdrWriter, BlrWriter, XdrReader, BitSet, BlrReader } from './serialize';
6
- import { doCallback, doError } from '../callback';
6
+ import { doCallback, doError, type Callback, type SimpleCallback } from '../callback';
7
7
  import * as srp from '../srp';
8
8
  import * as crypt from '../unix-crypt';
9
9
  import Const from './const';
@@ -14,6 +14,8 @@ import Statement from './statement';
14
14
  import Transaction from './transaction';
15
15
  import { lookupMessages, noop, parseDate } from '../utils';
16
16
  import Socket from './socket';
17
+ import type { QueueCallback, QueueEntry, WireResponse, InternalOptions, InternalQueryOptions, BatchCb, Quad, AcceptPacket } from './wire-types';
18
+ import type { BatchOptions, BatchResult, QueryParams } from '../types';
17
19
 
18
20
  function parseValueIfJson(value: any, options: any) {
19
21
  if (options && options.jsonAsObject && typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
@@ -26,6 +28,115 @@ function parseValueIfJson(value: any, options: any) {
26
28
  return value;
27
29
  }
28
30
 
31
+ /**
32
+ * Resolve the prepared-statement cache limit from the connection options:
33
+ * `statementCacheSize` (new), or the legacy `cacheQuery`/`maxCachedQuery`
34
+ * pair (which had no eviction; it now gets a bounded LRU). 0 = disabled.
35
+ */
36
+ /**
37
+ * Build the isc_dpb_search_path value from the defaultSchema / searchPath
38
+ * options (Firebird 6.0 / protocol 20+). There is no "default schema" DPB
39
+ * tag in Firebird: CURRENT_SCHEMA is simply the first existing schema of
40
+ * the search path, so defaultSchema is implemented by putting it at the
41
+ * front of the list. When only defaultSchema is given, PUBLIC is kept as a
42
+ * fallback so unqualified names outside the new schema still resolve (the
43
+ * server always appends SYSTEM itself). Returns null when neither option
44
+ * is set.
45
+ */
46
+ function buildSchemaSearchPath(options: InternalOptions): string | null {
47
+ var list: string[] = [];
48
+ if (options.searchPath) {
49
+ list = Array.isArray(options.searchPath)
50
+ ? options.searchPath.slice()
51
+ : String(options.searchPath).split(',').map(function(s: string) { return s.trim(); }).filter(Boolean);
52
+ }
53
+ var def = options.defaultSchema;
54
+ if (def) {
55
+ if (list.length === 0) {
56
+ // defaultSchema alone: keep PUBLIC as a fallback after it
57
+ list = def === 'PUBLIC' ? ['PUBLIC'] : [def, 'PUBLIC'];
58
+ } else {
59
+ // explicit searchPath: respect it, just move defaultSchema first
60
+ list = [def].concat(list.filter(function(s) { return s !== def; }));
61
+ }
62
+ }
63
+ return list.length ? list.join(',') : null;
64
+ }
65
+
66
+ function statementCacheLimit(options: InternalOptions): number {
67
+ const size = options && options.statementCacheSize;
68
+ if (size && size > 0) {
69
+ return Math.floor(size);
70
+ }
71
+ if (options && options.cacheQuery) {
72
+ const legacy = options.maxCachedQuery;
73
+ return legacy && legacy > 0 ? Math.floor(legacy) : 100;
74
+ }
75
+ return 0;
76
+ }
77
+
78
+ const SQL_TYPE_NAMES: Record<number, string> = {
79
+ [Const.SQL_TEXT]: 'TEXT',
80
+ [Const.SQL_VARYING]: 'VARYING',
81
+ [Const.SQL_SHORT]: 'SHORT',
82
+ [Const.SQL_LONG]: 'LONG',
83
+ [Const.SQL_FLOAT]: 'FLOAT',
84
+ [Const.SQL_DOUBLE]: 'DOUBLE',
85
+ [Const.SQL_D_FLOAT]: 'D_FLOAT',
86
+ [Const.SQL_TIMESTAMP]: 'TIMESTAMP',
87
+ [Const.SQL_BLOB]: 'BLOB',
88
+ [Const.SQL_ARRAY]: 'ARRAY',
89
+ [Const.SQL_QUAD]: 'QUAD',
90
+ [Const.SQL_TYPE_TIME]: 'TIME',
91
+ [Const.SQL_TYPE_DATE]: 'DATE',
92
+ [Const.SQL_INT64]: 'INT64',
93
+ [Const.SQL_INT128]: 'INT128',
94
+ [Const.SQL_TIMESTAMP_TZ]: 'TIMESTAMP_TZ',
95
+ [Const.SQL_TIMESTAMP_TZ_EX]: 'TIMESTAMP_TZ_EX',
96
+ [Const.SQL_TIME_TZ]: 'TIME_TZ',
97
+ [Const.SQL_TIME_TZ_EX]: 'TIME_TZ_EX',
98
+ [Const.SQL_DEC16]: 'DEC16',
99
+ [Const.SQL_DEC34]: 'DEC34',
100
+ [Const.SQL_BOOLEAN]: 'BOOLEAN',
101
+ [Const.SQL_NULL]: 'NULL',
102
+ };
103
+
104
+ /**
105
+ * Run the user's typeCast hook (options.typeCast) for one column value.
106
+ * The hook receives the column metadata and a next() returning the value
107
+ * the driver would produce by default (after blobAsText/jsonAsObject);
108
+ * whatever it returns becomes the value in the row. Rows may be decoded
109
+ * more than once when a response spans TCP packets, so the hook must be
110
+ * a pure function of its inputs.
111
+ */
112
+ function applyTypeCast(options: InternalOptions, meta: Partial<Xsql.SQLVarBase>, defaultValue: any) {
113
+ const typeCast = options && options.typeCast;
114
+ if (typeof typeCast !== 'function') {
115
+ return defaultValue;
116
+ }
117
+ const column = {
118
+ type: meta.type!,
119
+ typeName: SQL_TYPE_NAMES[meta.type!] || 'UNKNOWN',
120
+ subType: meta.subType,
121
+ scale: meta.scale,
122
+ length: meta.length,
123
+ field: meta.field,
124
+ relation: meta.relation,
125
+ alias: meta.alias,
126
+ };
127
+ // A hook exception must never escape into the row-decode loop: there it
128
+ // would be mistaken for an incomplete packet and desync the response
129
+ // queue (the same failure mode as issue #341). Fall back to the default
130
+ // value instead and tell the user.
131
+ try {
132
+ return typeCast(column, function () { return defaultValue; });
133
+ } catch (err: any) {
134
+ console.warn('[node-firebird] typeCast hook threw for column "%s" (%s): %s — using default value',
135
+ column.alias || column.field, column.typeName, err && err.message);
136
+ return defaultValue;
137
+ }
138
+ }
139
+
29
140
  /***************************************
30
141
  *
31
142
  * Connection
@@ -39,10 +150,12 @@ class Connection {
39
150
  static parseValueIfJson: typeof parseValueIfJson;
40
151
  static describe: typeof describe;
41
152
 
42
- db: any;
43
- svc: any;
44
- options: any;
45
- accept: any;
153
+ db: Database;
154
+ svc: ServiceManager | undefined;
155
+ options: InternalOptions;
156
+ /** protocol negotiation result (op_accept / op_cond_accept / op_accept_data);
157
+ * populated during the connect/attach handshake */
158
+ accept!: AcceptPacket;
46
159
  error: any;
47
160
  dbhandle: number | undefined;
48
161
  svchandle: number | undefined;
@@ -51,10 +164,12 @@ class Connection {
51
164
 
52
165
  _msg: XdrWriter;
53
166
  _blr: BlrWriter;
54
- _queue: any[];
167
+ /** response queue: one entry per expected server response (see wire-types) */
168
+ _queue: QueueEntry[];
55
169
  _pending: string[];
56
- _socket: any;
57
- _xdr: any;
170
+ _socket: Socket;
171
+ /** partially received packet buffered between 'data' events */
172
+ _xdr: XdrReader | undefined;
58
173
  _isOpened: boolean;
59
174
  _isClosed: boolean;
60
175
  _isDetach: boolean;
@@ -66,16 +181,18 @@ class Connection {
66
181
  _detachAuto: any;
67
182
  _retry_connection_id: any;
68
183
  _retry_connection_interval: number;
69
- _max_cached_query: number;
70
- _cache_query: Record<string, any> | null;
184
+ _statementCacheSize: number;
185
+ _statementCache: Map<string, Statement> | null;
71
186
  _messageFile: string;
72
187
  _authStartTime: number | undefined;
73
188
  _pendingAccept: any;
74
189
  _inlineBlobs: Map<string, Buffer> | undefined;
75
190
 
76
- constructor(host: string, port: number, callback: any, options: any, db?: any, svc?: any) {
191
+ constructor(host: string, port: number, callback: SimpleCallback | undefined, options: InternalOptions, db?: Database, svc?: ServiceManager) {
77
192
  var self = this;
78
- this.db = db;
193
+ // db is absent for service-manager connections; the wire core only
194
+ // touches it on database attachments, where it is always set
195
+ this.db = db!;
79
196
  this.svc = svc
80
197
  this._msg = new XdrWriter(32);
81
198
  this._blr = new BlrWriter(32);
@@ -83,7 +200,9 @@ class Connection {
83
200
  this._detachTimeout;
84
201
  this._detachCallback;
85
202
  this._detachAuto;
86
- this._socket = new Socket(port, host);
203
+ this._socket = new Socket(port, host,
204
+ options.enableKeepAlive !== false,
205
+ options.keepAliveInitialDelay);
87
206
  this._pending = [];
88
207
  this._isOpened = false;
89
208
  this._isClosed = false;
@@ -96,32 +215,66 @@ class Connection {
96
215
  // the same values.
97
216
  if (options && !options.user) options.user = Const.DEFAULT_USER;
98
217
  if (options && !options.password) options.password = Const.DEFAULT_PASSWORD;
99
- if (options && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
100
- if (options && options.blobReadChunkSize > 65535) options.blobReadChunkSize = 65535;
218
+ if (options && options.blobChunkSize && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
219
+ if (options && options.blobReadChunkSize && options.blobReadChunkSize > 65535) options.blobReadChunkSize = 65535;
101
220
  this.options = options;
102
221
  this._bind_events(host, port, callback);
103
222
  this.error;
104
223
  this._retry_connection_id;
105
224
  this._retry_connection_interval = options.retryConnectionInterval || 1000;
106
- this._max_cached_query = options.maxCachedQuery || -1;
107
- this._cache_query = options.cacheQuery?{}:null;
225
+ this._statementCacheSize = statementCacheLimit(options);
226
+ this._statementCache = this._statementCacheSize > 0 ? new Map() : null;
108
227
  this._messageFile = options.messageFile || path.join(__dirname, 'firebird.msg');
109
228
  }
110
229
 
111
230
 
112
- _setcachedquery(query, statement) {
113
- if (this._cache_query){
114
- if (this._max_cached_query === -1 || this._max_cached_query > Object.keys(this._cache_query).length){
115
- this._cache_query[query] = statement;
116
- }
231
+ /**
232
+ * Take an idle prepared statement for `query` out of the cache, or null.
233
+ * The statement leaves the cache while in use, so concurrent callers of
234
+ * the same query never share a server-side cursor — they simply prepare
235
+ * a fresh statement and the spare is dropped when released.
236
+ */
237
+ takeCachedStatement(query: string) {
238
+ const cache = this._statementCache;
239
+ if (!cache) {
240
+ return null;
117
241
  }
118
-
119
-
242
+ const statement = cache.get(query);
243
+ if (!statement) {
244
+ return null;
245
+ }
246
+ cache.delete(query);
247
+ return statement;
120
248
  }
121
249
 
250
+ /**
251
+ * Return a statement after use. With the statement cache enabled the
252
+ * statement goes back into the cache as most-recently-used (closing its
253
+ * cursor but keeping the prepared handle), evicting the least-recently
254
+ * used statement over the limit. Failed statements, DDL and spares for
255
+ * an already-cached query are dropped instead.
256
+ */
257
+ releaseStatement(statement: Statement, callback?: QueueCallback) {
258
+ const cache = this._statementCache;
259
+ const cacheable = cache &&
260
+ statement.query &&
261
+ !statement._failed &&
262
+ statement.type !== Const.isc_info_sql_stmt_ddl &&
263
+ !cache.has(statement.query);
264
+
265
+ if (!cacheable) {
266
+ this.dropStatement(statement, callback);
267
+ return;
268
+ }
122
269
 
123
- getCachedQuery(query) {
124
- return this._cache_query ? this._cache_query[query] : null;
270
+ cache!.set(statement.query, statement);
271
+ while (cache!.size > this._statementCacheSize) {
272
+ const oldestKey = cache!.keys().next().value!;
273
+ const oldest = cache!.get(oldestKey)!;
274
+ cache!.delete(oldestKey);
275
+ this.dropStatement(oldest, undefined);
276
+ }
277
+ this.closeStatement(statement, callback);
125
278
  }
126
279
 
127
280
 
@@ -131,7 +284,7 @@ class Connection {
131
284
  // be transparently resumed on a reconnect: a fresh attach() hands out new
132
285
  // transaction/statement handles, so nothing the server sends back could
133
286
  // ever match a callback queued against the old connection.
134
- _rejectPending(err) {
287
+ _rejectPending(err: any) {
135
288
  var queue = this._queue;
136
289
  this._queue = [];
137
290
  this._pending = [];
@@ -142,7 +295,7 @@ class Connection {
142
295
  }
143
296
 
144
297
 
145
- _bind_events(host, port, callback) {
298
+ _bind_events(host: string, port: number, callback: SimpleCallback | undefined) {
146
299
 
147
300
  var self = this;
148
301
 
@@ -168,17 +321,19 @@ class Connection {
168
321
 
169
322
  self._retry_connection_id = setTimeout(function() {
170
323
  self._socket.removeAllListeners();
171
- self._socket = null;
324
+ // transiently null while the replacement Connection is built
325
+ // (restored by the Object.assign(self, ctx) below)
326
+ self._socket = null!;
172
327
 
173
- var ctx = new Connection(host, port, function(err) {
174
- ctx.connect(self.options, function(err) {
328
+ var ctx = new Connection(host, port, function(err: any) {
329
+ ctx.connect(self.options, function(err: any) {
175
330
 
176
331
  if (err) {
177
332
  self.db.emit('error', err);
178
333
  return;
179
334
  }
180
335
 
181
- ctx.attach(self.options, function(err) {
336
+ ctx.attach(self.options, function(err: any) {
182
337
 
183
338
  if (err) {
184
339
  self.db.emit('error', err);
@@ -197,7 +352,7 @@ class Connection {
197
352
 
198
353
  });
199
354
 
200
- self._socket.on('error', function(e) {
355
+ self._socket.on('error', function(e: any) {
201
356
 
202
357
  self.error = e;
203
358
 
@@ -216,8 +371,8 @@ class Connection {
216
371
  callback();
217
372
  });
218
373
 
219
- self._socket.on('data', function (data) {
220
- var xdr;
374
+ self._socket.on('data', function (data: any) {
375
+ var xdr: any;
221
376
  var hadSavedBuffer = Boolean(self._xdr);
222
377
 
223
378
  if (!self._xdr) {
@@ -337,7 +492,7 @@ class Connection {
337
492
 
338
493
 
339
494
 
340
- sendOpContAuth(authData, authDataEnc, pluginName) {
495
+ sendOpContAuth(authData: string, authDataEnc: BufferEncoding, pluginName: string) {
341
496
  var msg = this._msg;
342
497
  msg.pos = 0;
343
498
 
@@ -352,7 +507,7 @@ class Connection {
352
507
  }
353
508
 
354
509
 
355
- sendOpCrypt(encryptPlugin) {
510
+ sendOpCrypt(encryptPlugin: string) {
356
511
  var msg = this._msg;
357
512
  msg.pos = 0;
358
513
 
@@ -364,7 +519,7 @@ class Connection {
364
519
  }
365
520
 
366
521
 
367
- sendOpCryptKeyCallback(pluginData) {
522
+ sendOpCryptKeyCallback(pluginData: BlrWriter) {
368
523
  var msg = this._msg;
369
524
  msg.pos = 0;
370
525
 
@@ -381,7 +536,7 @@ class Connection {
381
536
  * makes that operation fail with isc_cancelled (GDSCode.CANCELLED); the
382
537
  * op_cancel packet itself has no response, so nothing is queued here.
383
538
  */
384
- cancelOperation(kind, callback) {
539
+ cancelOperation(kind?: number | SimpleCallback, callback?: SimpleCallback) {
385
540
  if (typeof kind === 'function') {
386
541
  callback = kind;
387
542
  kind = undefined;
@@ -408,7 +563,7 @@ class Connection {
408
563
 
409
564
 
410
565
  /** Write a prebuilt packet and queue its response callback. */
411
- _queueEventBuffer(buffer, callback) {
566
+ _queueEventBuffer(buffer: Buffer, callback: QueueCallback | undefined) {
412
567
  if (this._isClosed) {
413
568
  if (callback)
414
569
  callback(new Error('Connection is closed.'));
@@ -420,7 +575,7 @@ class Connection {
420
575
  }
421
576
 
422
577
 
423
- _queueEvent(callback, defer = false) {
578
+ _queueEvent(callback: QueueCallback | undefined, defer = false) {
424
579
  var self = this;
425
580
 
426
581
  if (self._isClosed) {
@@ -449,7 +604,7 @@ class Connection {
449
604
  }
450
605
 
451
606
 
452
- connect(options, callback) {
607
+ connect(options: InternalOptions, callback: Callback<AcceptPacket> | undefined) {
453
608
  var pluginName = options.pluginName || Const.AUTH_PLUGIN_LIST[0];
454
609
  var msg = this._msg;
455
610
  var blr = this._blr;
@@ -460,7 +615,7 @@ class Connection {
460
615
  msg.pos = 0;
461
616
  blr.pos = 0;
462
617
 
463
- blr.addString(Const.CNCT_login, options.user, Const.DEFAULT_ENCODING);
618
+ blr.addString(Const.CNCT_login, options.user!, Const.DEFAULT_ENCODING);
464
619
  blr.addString(Const.CNCT_plugin_name, pluginName, Const.DEFAULT_ENCODING);
465
620
  blr.addString(Const.CNCT_plugin_list, Const.AUTH_PLUGIN_LIST.join(','), Const.DEFAULT_ENCODING);
466
621
 
@@ -474,7 +629,7 @@ class Connection {
474
629
  specificData = this.clientKeys.public.toString(16);
475
630
  blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
476
631
  } else if (pluginName === Const.AUTH_PLUGIN_LEGACY) {
477
- specificData = crypt.crypt(options.password, Const.LEGACY_AUTH_SALT).substring(2);
632
+ specificData = crypt.crypt(options.password!, Const.LEGACY_AUTH_SALT).substring(2);
478
633
  blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
479
634
  } else {
480
635
  doError(new Error('Invalide auth plugin \'' + pluginName + '\''), callback);
@@ -490,10 +645,17 @@ class Connection {
490
645
  msg.addInt(Const.CONNECT_VERSION3);
491
646
  msg.addInt(Const.ARCHITECTURE_GENERIC);
492
647
  msg.addString(options.database || options.filename, Const.DEFAULT_ENCODING);
493
- var maxProtocols = options.maxNegotiatedProtocols !== undefined ? options.maxNegotiatedProtocols : 10;
648
+ // Send the full list by default. Servers parse every entry and ignore
649
+ // versions they do not know (verified back to Firebird 2.5), so the
650
+ // list length itself is harmless; the option remains as an escape
651
+ // hatch to cap negotiation at an older protocol.
652
+ var maxProtocols = options.maxNegotiatedProtocols !== undefined ? options.maxNegotiatedProtocols : Const.SUPPORTED_PROTOCOL.length;
494
653
  var protocolsToSend = Const.SUPPORTED_PROTOCOL;
495
654
  if (protocolsToSend.length > maxProtocols) {
496
- protocolsToSend = protocolsToSend.slice(-maxProtocols);
655
+ // keep the FIRST N entries: the list is ordered oldest→newest, so
656
+ // capping the count caps the newest protocol offered (e.g. 10 =
657
+ // stop at protocol 19, the documented pre-Firebird-6 behavior)
658
+ protocolsToSend = protocolsToSend.slice(0, maxProtocols);
497
659
  }
498
660
 
499
661
  msg.addInt(protocolsToSend.length); // Count of Protocol version understood count.
@@ -512,7 +674,7 @@ class Connection {
512
674
  }
513
675
 
514
676
  var self = this;
515
- function cb(err, ret) {
677
+ function cb(err: any, ret: any) {
516
678
  if (err) {
517
679
  doError(err, callback);
518
680
  return;
@@ -534,11 +696,11 @@ class Connection {
534
696
 
535
697
  var selectedPlugin = 'Arc4';
536
698
  if (ret.keys) {
537
- var serverPlugins = ret.keys.split(',').map(function(s) { return s.trim().toLowerCase(); });
699
+ var serverPlugins = ret.keys.split(',').map(function(s: any) { return s.trim().toLowerCase(); });
538
700
  var preferred = ['chacha64', 'chacha', 'arc4'];
539
701
  for (var i = 0; i < preferred.length; i++) {
540
702
  if (serverPlugins.indexOf(preferred[i]) !== -1) {
541
- var mapping = {
703
+ var mapping: Record<string, string> = {
542
704
  chacha64: 'ChaCha64',
543
705
  chacha: 'ChaCha',
544
706
  arc4: 'Arc4'
@@ -559,7 +721,7 @@ class Connection {
559
721
  }
560
722
 
561
723
  self._pending.push('crypt');
562
- self._queue.push(function(cryptErr, response) {
724
+ self._queue.push(function(cryptErr: any, response: any) {
563
725
  if (cryptErr) {
564
726
  doError(cryptErr, callback);
565
727
  return;
@@ -593,7 +755,7 @@ class Connection {
593
755
  }
594
756
 
595
757
 
596
- attach(options: any, callback?: any, db?: any) {
758
+ attach(options: InternalOptions, callback?: Callback<Database>, db?: Database) {
597
759
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
598
760
 
599
761
  var database = options.database || options.filename;
@@ -657,31 +819,19 @@ class Connection {
657
819
  }
658
820
 
659
821
  // Firebird 6.0 SQL Schema parameters (Protocol 20+).
660
- // These DPB tags configure the session's current schema and the
661
- // schema search path for unqualified object name resolution.
662
822
  if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
663
- if (options.defaultSchema) {
664
- // Sets CURRENT_SCHEMA for the session. Equivalent to issuing
665
- // SET SCHEMA <name> immediately after connecting.
666
- blr.addString(Const.isc_dpb_default_schema, options.defaultSchema, Const.DEFAULT_ENCODING);
667
- }
668
- if (options.searchPath) {
669
- // Comma-separated ordered schema name list, like PostgreSQL's
670
- // search_path. Unqualified object references are resolved by
671
- // scanning schemas in this order.
672
- const sp = Array.isArray(options.searchPath)
673
- ? options.searchPath.join(',')
674
- : String(options.searchPath);
823
+ const sp = buildSchemaSearchPath(options);
824
+ if (sp) {
675
825
  blr.addString(Const.isc_dpb_search_path, sp, Const.DEFAULT_ENCODING);
676
826
  }
677
827
  }
678
-
828
+
679
829
  msg.addInt(Const.op_attach);
680
830
  msg.addInt(0); // Database Object ID
681
831
  msg.addString(database, Const.DEFAULT_ENCODING);
682
832
  msg.addBlr(this._blr);
683
833
 
684
- function cb(err, ret) {
834
+ function cb(err: any, ret: any) {
685
835
  if (err) {
686
836
  doError(err, callback);
687
837
  return;
@@ -708,7 +858,7 @@ class Connection {
708
858
  }
709
859
 
710
860
 
711
- detach(callback) {
861
+ detach(callback: Callback | undefined) {
712
862
 
713
863
  var self = this;
714
864
 
@@ -724,7 +874,7 @@ class Connection {
724
874
  msg.addInt(Const.op_detach);
725
875
  msg.addInt(0); // Database Object ID
726
876
 
727
- self._queueEvent(function(err, ret) {
877
+ self._queueEvent(function(err: any, ret: any) {
728
878
  clearTimeout(self._retry_connection_id);
729
879
  delete(self.dbhandle);
730
880
  if (callback)
@@ -733,7 +883,7 @@ class Connection {
733
883
  }
734
884
 
735
885
 
736
- createDatabase(options, callback) {
886
+ createDatabase(options: InternalOptions, callback: Callback<Database> | undefined) {
737
887
  // Mirror attach(): honour the lowercase_keys option so that db.query()
738
888
  // called on a freshly-created database returns the expected column case.
739
889
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
@@ -796,16 +946,18 @@ class Connection {
796
946
 
797
947
  // Firebird 6.0 SQL Schema parameters (Protocol 20+).
798
948
  if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
799
- if (options.defaultSchema) {
800
- blr.addString(Const.isc_dpb_default_schema, options.defaultSchema, Const.DEFAULT_ENCODING);
801
- }
802
- if (options.searchPath) {
803
- const sp = Array.isArray(options.searchPath)
804
- ? options.searchPath.join(',')
805
- : String(options.searchPath);
949
+ const sp = buildSchemaSearchPath(options);
950
+ if (sp) {
806
951
  blr.addString(Const.isc_dpb_search_path, sp, Const.DEFAULT_ENCODING);
807
952
  }
808
953
  }
954
+
955
+ if (options.owner) {
956
+ // Firebird 6.0+ (issue #7718): create the database owned by a
957
+ // different user (requires superuser rights). Older servers
958
+ // ignore unknown DPB tags, so this is safe to always send.
959
+ blr.addString(Const.isc_dpb_owner, options.owner, Const.DEFAULT_ENCODING);
960
+ }
809
961
 
810
962
  blr.addNumeric(Const.isc_dpb_sql_dialect, 3);
811
963
  blr.addNumeric(Const.isc_dpb_force_write, 1);
@@ -821,7 +973,7 @@ class Connection {
821
973
 
822
974
  var self = this;
823
975
 
824
- function cb(err, ret) {
976
+ function cb(err: any, ret: any) {
825
977
 
826
978
  if (ret)
827
979
  self.dbhandle = ret.handle;
@@ -838,15 +990,15 @@ class Connection {
838
990
  }
839
991
 
840
992
 
841
- dropDatabase(callback) {
993
+ dropDatabase(callback: SimpleCallback | undefined) {
842
994
  var msg = this._msg;
843
995
  msg.pos = 0;
844
996
 
845
997
  msg.addInt(Const.op_drop_database);
846
- msg.addInt(this.dbhandle);
998
+ msg.addInt(this.dbhandle!);
847
999
 
848
1000
  var self = this;
849
- this._queueEvent(function(err) {
1001
+ this._queueEvent(function(err: any) {
850
1002
  self.detach(function() {
851
1003
  self.disconnect();
852
1004
 
@@ -857,7 +1009,7 @@ class Connection {
857
1009
  }
858
1010
 
859
1011
 
860
- throwClosed(callback) {
1012
+ throwClosed(callback: ((err: Error, ...args: any[]) => void) | undefined) {
861
1013
  var err = new Error('Connection is closed.');
862
1014
  this.db.emit('error', err);
863
1015
  if (callback)
@@ -866,7 +1018,9 @@ class Connection {
866
1018
  }
867
1019
 
868
1020
 
869
- startTransaction(options, callback) {
1021
+ /** `options` is a resolved options object, a bare isolation array, or
1022
+ * the callback itself when no options are given. */
1023
+ startTransaction(options: any, callback?: any) {
870
1024
 
871
1025
  if (typeof(options) === 'function') {
872
1026
  var tmp = options;
@@ -939,7 +1093,7 @@ class Connection {
939
1093
  }*/
940
1094
 
941
1095
  msg.addInt(Const.op_transaction);
942
- msg.addInt(this.dbhandle);
1096
+ msg.addInt(this.dbhandle!);
943
1097
  msg.addBlr(blr);
944
1098
  callback.response = new Transaction(this);
945
1099
 
@@ -948,7 +1102,7 @@ class Connection {
948
1102
  }
949
1103
 
950
1104
 
951
- commit(transaction, callback) {
1105
+ commit(transaction: Transaction, callback: QueueCallback | undefined) {
952
1106
 
953
1107
  if (this._isClosed)
954
1108
  return this.throwClosed(callback);
@@ -965,7 +1119,7 @@ class Connection {
965
1119
  }
966
1120
 
967
1121
 
968
- rollback(transaction, callback) {
1122
+ rollback(transaction: Transaction, callback: QueueCallback | undefined) {
969
1123
 
970
1124
  if (this._isClosed)
971
1125
  return this.throwClosed(callback);
@@ -982,7 +1136,7 @@ class Connection {
982
1136
  }
983
1137
 
984
1138
 
985
- commitRetaining(transaction, callback) {
1139
+ commitRetaining(transaction: Transaction, callback: QueueCallback | undefined) {
986
1140
 
987
1141
  if (this._isClosed)
988
1142
  return this.throwClosed(callback);
@@ -998,7 +1152,7 @@ class Connection {
998
1152
  }
999
1153
 
1000
1154
 
1001
- rollbackRetaining(transaction, callback) {
1155
+ rollbackRetaining(transaction: Transaction, callback: QueueCallback | undefined) {
1002
1156
 
1003
1157
  if (this._isClosed)
1004
1158
  return this.throwClosed(callback);
@@ -1014,7 +1168,7 @@ class Connection {
1014
1168
  }
1015
1169
 
1016
1170
 
1017
- allocateStatement(callback) {
1171
+ allocateStatement(callback: QueueCallback) {
1018
1172
 
1019
1173
  if (this._isClosed)
1020
1174
  return this.throwClosed(callback);
@@ -1025,13 +1179,13 @@ class Connection {
1025
1179
  var msg = this._msg;
1026
1180
  msg.pos = 0;
1027
1181
  msg.addInt(Const.op_allocate_statement);
1028
- msg.addInt(this.dbhandle);
1182
+ msg.addInt(this.dbhandle!);
1029
1183
  callback.response = new Statement(this);
1030
1184
  this._queueEvent(callback);
1031
1185
  }
1032
1186
 
1033
1187
 
1034
- dropStatement(statement, callback) {
1188
+ dropStatement(statement: Statement, callback: QueueCallback | undefined) {
1035
1189
 
1036
1190
  if (this._isClosed)
1037
1191
  return this.throwClosed(callback);
@@ -1049,7 +1203,7 @@ class Connection {
1049
1203
  }
1050
1204
 
1051
1205
 
1052
- closeStatement(statement, callback) {
1206
+ closeStatement(statement: Statement, callback: QueueCallback | undefined) {
1053
1207
 
1054
1208
  if (this._isClosed)
1055
1209
  return this.throwClosed(callback);
@@ -1067,16 +1221,15 @@ class Connection {
1067
1221
  }
1068
1222
 
1069
1223
 
1070
- allocateAndPrepareStatement(transaction, query, plan, callback) {
1224
+ allocateAndPrepareStatement(transaction: Transaction, query: string, plan: boolean, callback: Callback<Statement>) {
1071
1225
  var self = this;
1072
- var mainCallback: any = function(err: any, ret: any) {
1226
+ var mainCallback: QueueCallback = function(err: any, ret: any) {
1073
1227
  if (!err) {
1074
1228
  mainCallback.response.handle = ret.handle;
1075
1229
  describe(ret.buffer, mainCallback.response);
1076
1230
  mainCallback.response.query = query;
1077
1231
  self.db.emit('query', query);
1078
1232
  ret = mainCallback.response;
1079
- self._setcachedquery(query, ret);
1080
1233
  }
1081
1234
 
1082
1235
  if (callback)
@@ -1093,7 +1246,7 @@ class Connection {
1093
1246
  blr.pos = 0;
1094
1247
 
1095
1248
  msg.addInt(Const.op_allocate_statement);
1096
- msg.addInt(this.dbhandle);
1249
+ msg.addInt(this.dbhandle!);
1097
1250
  mainCallback.lazy_count = 1;
1098
1251
 
1099
1252
  const describeBytes = this.accept.protocolVersion >= Const.PROTOCOL_VERSION20 ? Const.DESCRIBE_WITH_SCHEMA : Const.DESCRIBE;
@@ -1111,6 +1264,12 @@ class Connection {
1111
1264
  msg.addString(query, Const.DEFAULT_ENCODING);
1112
1265
  msg.addBlr(blr);
1113
1266
  msg.addInt(65535); // buffer_length
1267
+ if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
1268
+ // p_sqlst_flags (IStatement::PREPARE_* bits, none needed) — the
1269
+ // server blocks reading this field if it is missing, which was
1270
+ // the protocol-20 "prepare hang"
1271
+ msg.addInt(0);
1272
+ }
1114
1273
  mainCallback.lazy_count += 1;
1115
1274
 
1116
1275
  mainCallback.response = new Statement(this);
@@ -1118,13 +1277,13 @@ class Connection {
1118
1277
  }
1119
1278
 
1120
1279
 
1121
- prepare(transaction, query, plan, callback) {
1280
+ prepare(transaction: Transaction, query: string, plan: boolean, callback: Callback<Statement>) {
1122
1281
  var self = this;
1123
1282
 
1124
1283
  if (this.accept.protocolMinimumType === Const.ptype_lazy_send) { // V11 Statement or higher
1125
1284
  self.allocateAndPrepareStatement(transaction, query, plan, callback);
1126
1285
  } else { // V10 Statement
1127
- self.allocateStatement(function (err, statement) {
1286
+ self.allocateStatement(function (err: any, statement: Statement) {
1128
1287
  if (err) {
1129
1288
  doError(err, callback);
1130
1289
  return;
@@ -1137,7 +1296,8 @@ class Connection {
1137
1296
 
1138
1297
 
1139
1298
 
1140
- prepareStatement(transaction, statement, query, plan, callback) {
1299
+ /** `plan` may be the callback itself when no plan flag is given. */
1300
+ prepareStatement(transaction: Transaction, statement: Statement, query: string, plan: boolean | Callback<Statement>, callback?: Callback<Statement>) {
1141
1301
 
1142
1302
  if (this._isClosed)
1143
1303
  return this.throwClosed(callback);
@@ -1166,16 +1326,18 @@ class Connection {
1166
1326
  msg.addString(query, Const.DEFAULT_ENCODING);
1167
1327
  msg.addBlr(blr);
1168
1328
  msg.addInt(65535); // buffer_length
1329
+ if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
1330
+ msg.addInt(0); // p_sqlst_flags (see allocateAndPrepareStatement)
1331
+ }
1169
1332
 
1170
1333
  var self = this;
1171
- this._queueEvent(function(err, ret) {
1334
+ this._queueEvent(function(err: any, ret: any) {
1172
1335
 
1173
1336
  if (!err) {
1174
1337
  describe(ret.buffer, statement);
1175
1338
  statement.query = query;
1176
1339
  self.db.emit('query', query);
1177
1340
  ret = statement;
1178
- self._setcachedquery(query, ret);
1179
1341
  }
1180
1342
 
1181
1343
  if (callback)
@@ -1198,7 +1360,7 @@ class Connection {
1198
1360
  * { recordCount, updateCounts, errors: [{recordNumber, error}],
1199
1361
  * errorRecordNumbers, success }.
1200
1362
  */
1201
- executeBatch(transaction, statement, rows, callback, options) {
1363
+ executeBatch(transaction: Transaction, statement: Statement, rows: QueryParams[], callback: BatchCb | undefined, options?: BatchOptions) {
1202
1364
  options = options || {};
1203
1365
 
1204
1366
  if (this._isClosed)
@@ -1244,7 +1406,7 @@ class Connection {
1244
1406
 
1245
1407
  var self = this;
1246
1408
  var encoders = built.encoders;
1247
- var chunkSize = options.chunkSize > 0 ? options.chunkSize : 500;
1409
+ var chunkSize = options.chunkSize && options.chunkSize > 0 ? options.chunkSize : 500;
1248
1410
  var chunkCount = Math.ceil(rows.length / chunkSize);
1249
1411
 
1250
1412
  var failure: any = null;
@@ -1264,7 +1426,7 @@ class Connection {
1264
1426
  }
1265
1427
 
1266
1428
  var detailed = completion ? completion.detailedErrors : [];
1267
- var errorRecordNumbers = detailed.map(function(e) { return e.recordNumber; })
1429
+ var errorRecordNumbers = detailed.map(function(e: any) { return e.recordNumber; })
1268
1430
  .concat(completion ? completion.errorRecordNumbers : []);
1269
1431
 
1270
1432
  if (callback) callback(undefined, {
@@ -1312,7 +1474,8 @@ class Connection {
1312
1474
  msg.addInt(end - start);
1313
1475
 
1314
1476
  for (var i = start; i < end; i++) {
1315
- var row = rows[i];
1477
+ // validated as an array of input.length values above
1478
+ var row = rows[i] as any[];
1316
1479
 
1317
1480
  var nullBits = new BitSet();
1318
1481
  for (var j = 0; j < input.length; j++) {
@@ -1371,7 +1534,7 @@ class Connection {
1371
1534
  for (var p = 0; p < packets.length; p++) {
1372
1535
  this._pending.push('executeBatch');
1373
1536
  if (p === execIndex) {
1374
- this._queueEventBuffer(packets[p], function(err, ret) {
1537
+ this._queueEventBuffer(packets[p], function(err: any, ret: any) {
1375
1538
  if (!err && ret && ret.batchCompletion) {
1376
1539
  completion = ret.batchCompletion;
1377
1540
  settle();
@@ -1382,13 +1545,14 @@ class Connection {
1382
1545
  } else if (p === packets.length - 1) {
1383
1546
  this._queueEventBuffer(packets[p], function() {});
1384
1547
  } else {
1385
- this._queueEventBuffer(packets[p], function(err) { settle(err); });
1548
+ this._queueEventBuffer(packets[p], function(err: any) { settle(err); });
1386
1549
  }
1387
1550
  }
1388
1551
  }
1389
1552
 
1390
1553
 
1391
- executeStatement(transaction, statement, params, callback, custom) {
1554
+ /** `params` may be the callback itself when the statement has no parameters. */
1555
+ executeStatement(transaction: Transaction, statement: Statement, params: any, callback?: QueueCallback, custom?: InternalQueryOptions) {
1392
1556
 
1393
1557
  if (this._isClosed)
1394
1558
  return this.throwClosed(callback);
@@ -1412,14 +1576,14 @@ class Connection {
1412
1576
  op = Const.op_execute2;
1413
1577
  }
1414
1578
 
1415
- function PrepareParams(params, input, callback) {
1579
+ function PrepareParams(params: any[], input: Xsql.SQLVarBase[], callback: (prms: any[]) => void) {
1416
1580
 
1417
1581
  var value, meta;
1418
1582
  var ret = new Array(params.length);
1419
1583
 
1420
- function putBlobData(index, value, callback) {
1584
+ function putBlobData(index: any, value: any, callback: any) {
1421
1585
 
1422
- self.createBlob2(transaction, function(err, blob) {
1586
+ self.createBlob2(transaction, function(err: any, blob: any) {
1423
1587
 
1424
1588
  var b;
1425
1589
  var isStream = value.readable;
@@ -1447,7 +1611,7 @@ class Connection {
1447
1611
  var isReading = false;
1448
1612
  var isEnd = false;
1449
1613
 
1450
- value.on('data', function(chunk) {
1614
+ value.on('data', function(chunk: any) {
1451
1615
  // Optimization: If chunk is smaller than transfer size, send directly
1452
1616
  if (chunk.length <= chunkSize) {
1453
1617
  self.batchSegments(blob, chunk, function () {
@@ -1488,7 +1652,7 @@ class Connection {
1488
1652
  });
1489
1653
  }
1490
1654
 
1491
- function step(i) {
1655
+ function step(i: any) {
1492
1656
  if (i === params.length) {
1493
1657
  callback(ret);
1494
1658
  return;
@@ -1605,12 +1769,12 @@ class Connection {
1605
1769
 
1606
1770
  if (!(params instanceof Array)) {
1607
1771
  if (params !== undefined && typeof params === 'object' && params !== null) {
1608
- var mappedParams = [];
1772
+ var mappedParams: any[] = [];
1609
1773
  for (var i = 0; i < input.length; i++) {
1610
1774
  mappedParams.push(undefined);
1611
1775
  }
1612
1776
  var matchedCount = 0;
1613
- var nameMap = {};
1777
+ var nameMap: Record<string, number> = {};
1614
1778
  for (var i = 0; i < input.length; i++) {
1615
1779
  var name = input[i].alias || input[i].field;
1616
1780
  if (name) {
@@ -1641,11 +1805,11 @@ class Connection {
1641
1805
 
1642
1806
  if (params.length !== input.length) {
1643
1807
  self._pending.pop();
1644
- callback(new Error('Expected parameters: (params=' + params.length + ' vs. expected=' + input.length + ') - ' + statement.query));
1808
+ callback!(new Error('Expected parameters: (params=' + params.length + ' vs. expected=' + input.length + ') - ' + statement.query));
1645
1809
  return;
1646
1810
  }
1647
1811
 
1648
- PrepareParams(params, input, function(prms) {
1812
+ PrepareParams(params, input, function(prms: any) {
1649
1813
  self.sendExecute(op, statement, transaction, callback, prms);
1650
1814
  });
1651
1815
 
@@ -1656,7 +1820,7 @@ class Connection {
1656
1820
  }
1657
1821
 
1658
1822
 
1659
- sendExecute(op: number, statement: any, transaction: any, callback: any, parameters?: any[]) {
1823
+ sendExecute(op: number, statement: Statement, transaction: Transaction, callback: QueueCallback | undefined, parameters?: any[]) {
1660
1824
  var msg = this._msg;
1661
1825
  var blr = this._blr;
1662
1826
  msg.pos = 0;
@@ -1736,14 +1900,15 @@ class Connection {
1736
1900
  msg.addInt(statement.options?.maxInlineBlobSize !== undefined ? statement.options.maxInlineBlobSize : (this.options?.maxInlineBlobSize || 0)); // p_sqldata_inline_blob_size
1737
1901
  }
1738
1902
 
1739
- callback.statement = statement;
1903
+ callback!.statement = statement;
1740
1904
  this._queueEvent(callback);
1741
1905
  }
1742
1906
 
1743
1907
 
1744
1908
 
1745
1909
 
1746
- fetch(statement, transaction, count, callback) {
1910
+ /** `count` may be the callback itself when no fetch size is given. */
1911
+ fetch(statement: Statement, transaction: Transaction, count: any, callback?: QueueCallback) {
1747
1912
 
1748
1913
  var msg = this._msg;
1749
1914
  var blr = this._blr;
@@ -1763,12 +1928,12 @@ class Connection {
1763
1928
  msg.addInt(0); // message number
1764
1929
  msg.addInt(count || Const.DEFAULT_FETCHSIZE); // fetch count
1765
1930
 
1766
- callback.statement = statement;
1931
+ callback!.statement = statement;
1767
1932
  this._queueEvent(callback);
1768
1933
  }
1769
1934
 
1770
1935
 
1771
- fetchScroll(statement, transaction, direction, offset, count, callback) {
1936
+ fetchScroll(statement: Statement, transaction: Transaction, direction: string | number, offset: any, count: any, callback?: QueueCallback) {
1772
1937
  if (typeof count === 'function') {
1773
1938
  callback = count;
1774
1939
  count = undefined;
@@ -1814,18 +1979,18 @@ class Connection {
1814
1979
  msg.addInt(dirInt); // fetch operation
1815
1980
  msg.addInt(offsetVal); // fetch position (offset)
1816
1981
 
1817
- callback.statement = statement;
1982
+ callback!.statement = statement;
1818
1983
  this._queueEvent(callback);
1819
1984
  }
1820
1985
 
1821
1986
 
1822
- fetchAll(statement, transaction, callback) {
1987
+ fetchAll(statement: Statement, transaction: Transaction, callback: Callback<any[]>) {
1823
1988
  const self = this;
1824
1989
  const custom = statement.options || {};
1825
1990
  const asStream = custom.asStream && custom.on;
1826
- const data = asStream ? null : [];
1991
+ const data: any[] | null = asStream ? null : [];
1827
1992
  let streamIndex = 0;
1828
- const loop = (err, ret) => {
1993
+ const loop = (err: any, ret: any) => {
1829
1994
  if (err) {
1830
1995
  callback(err);
1831
1996
  return;
@@ -1837,24 +2002,28 @@ class Connection {
1837
2002
  // which causes a server-side deadlock when many rows contain
1838
2003
  // BLOBs and blobAsText is true. See issue #387.
1839
2004
  const arrBlobFns = ret.arrBlob || [];
1840
- const readBlobsSequentially = (index, results) => {
2005
+ const readBlobsSequentially = (index: any, results: any) => {
1841
2006
  if (index >= arrBlobFns.length) {
1842
2007
  return Promise.resolve(results);
1843
2008
  }
1844
- return arrBlobFns[index](transaction).then((v) => {
2009
+ return arrBlobFns[index](transaction).then((v: any) => {
1845
2010
  results.push(v);
1846
2011
  return readBlobsSequentially(index + 1, results);
1847
2012
  });
1848
2013
  };
1849
2014
 
1850
- readBlobsSequentially(0, []).then((arrBlob) => {
2015
+ readBlobsSequentially(0, []).then((arrBlob: any) => {
1851
2016
  for (let i = 0; i < arrBlob.length; i++) {
1852
2017
  const blob = arrBlob[i];
1853
- ret.data[blob.row][blob.column] = parseValueIfJson(blob.value, statement.connection.options);
2018
+ // nestTables === true rows: the value lives in the
2019
+ // per-table sub-object, not on the row itself
2020
+ Xsql.nestCell(ret.data[blob.row], blob.table)[blob.column] = applyTypeCast(
2021
+ statement.connection.options, blob.meta || {},
2022
+ parseValueIfJson(blob.value, statement.connection.options));
1854
2023
  }
1855
2024
 
1856
2025
  doSynchronousLoop(ret.data, (row, _i, next) => {
1857
- const pos = asStream ? streamIndex++ : (data.push(row) - 1);
2026
+ const pos = asStream ? streamIndex++ : (data!.push(row) - 1);
1858
2027
  if (asStream) {
1859
2028
  executeStreamRow(custom, row, pos, statement.output, next);
1860
2029
  } else {
@@ -1888,7 +2057,7 @@ class Connection {
1888
2057
 
1889
2058
 
1890
2059
 
1891
- openBlob(blob, transaction, callback) {
2060
+ openBlob(blob: Quad, transaction: Transaction, callback: QueueCallback) {
1892
2061
  var msg = this._msg;
1893
2062
  msg.pos = 0;
1894
2063
  msg.addInt(Const.op_open_blob);
@@ -1898,7 +2067,7 @@ class Connection {
1898
2067
  }
1899
2068
 
1900
2069
 
1901
- closeBlob(blob, callback, defer = true) {
2070
+ closeBlob(blob: any, callback?: QueueCallback, defer = true) {
1902
2071
  var msg = this._msg;
1903
2072
  msg.pos = 0;
1904
2073
  msg.addInt(Const.op_close_blob);
@@ -1907,7 +2076,7 @@ class Connection {
1907
2076
  }
1908
2077
 
1909
2078
 
1910
- getSegment(blob, callback) {
2079
+ getSegment(blob: any, callback: QueueCallback) {
1911
2080
  var msg = this._msg;
1912
2081
  msg.pos = 0;
1913
2082
  msg.addInt(Const.op_get_segment);
@@ -1918,7 +2087,7 @@ class Connection {
1918
2087
  }
1919
2088
 
1920
2089
 
1921
- createBlob2(transaction, callback) {
2090
+ createBlob2(transaction: Transaction, callback: QueueCallback) {
1922
2091
  var msg = this._msg;
1923
2092
  msg.pos = 0;
1924
2093
  msg.addInt(Const.op_create_blob2);
@@ -1930,7 +2099,7 @@ class Connection {
1930
2099
  }
1931
2100
 
1932
2101
 
1933
- batchSegments(blob, buffer, callback) {
2102
+ batchSegments(blob: any, buffer: Buffer, callback: QueueCallback) {
1934
2103
  var msg = this._msg;
1935
2104
  var blr = this._blr;
1936
2105
  msg.pos = 0;
@@ -1944,7 +2113,7 @@ class Connection {
1944
2113
  }
1945
2114
 
1946
2115
 
1947
- svcattach(options: any, callback?: any, svc?: any) {
2116
+ svcattach(options: InternalOptions, callback?: Callback<ServiceManager>, svc?: ServiceManager) {
1948
2117
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
1949
2118
  var database = options.database || options.filename;
1950
2119
  var user = options.user || Const.DEFAULT_USER;
@@ -1979,7 +2148,7 @@ class Connection {
1979
2148
 
1980
2149
  var self = this;
1981
2150
 
1982
- function cb(err, ret) {
2151
+ function cb(err: any, ret: any) {
1983
2152
 
1984
2153
  if (err) {
1985
2154
  doError(err, callback);
@@ -2005,19 +2174,19 @@ class Connection {
2005
2174
  }
2006
2175
 
2007
2176
 
2008
- svcstart(spbaction, callback) {
2177
+ svcstart(spbaction: BlrWriter, callback: QueueCallback | undefined) {
2009
2178
  var msg = this._msg;
2010
2179
  var blr = this._blr;
2011
2180
  msg.pos = 0;
2012
2181
  msg.addInt(Const.op_service_start);
2013
- msg.addInt(this.svchandle);
2182
+ msg.addInt(this.svchandle!);
2014
2183
  msg.addInt(0)
2015
2184
  msg.addBlr(spbaction);
2016
2185
  this._queueEvent(callback);
2017
2186
  }
2018
2187
 
2019
2188
 
2020
- svcquery(spbquery, resultbuffersize, timeout,callback) {
2189
+ svcquery(spbquery: number[], resultbuffersize: number, timeout: number | undefined, callback: QueueCallback | undefined) {
2021
2190
  if (resultbuffersize > Const.MAX_BUFFER_SIZE) {
2022
2191
  doError(new Error('Buffer is too big'), callback);
2023
2192
  return;
@@ -2030,7 +2199,7 @@ class Connection {
2030
2199
  blr.addByte(Const.isc_spb_current_version);
2031
2200
  //blr.addByteInt32(Const.isc_info_svc_timeout, timeout);
2032
2201
  msg.addInt(Const.op_service_info);
2033
- msg.addInt(this.svchandle);
2202
+ msg.addInt(this.svchandle!);
2034
2203
  msg.addInt(0);
2035
2204
  msg.addBlr(blr);
2036
2205
  blr.pos = 0
@@ -2041,7 +2210,7 @@ class Connection {
2041
2210
  }
2042
2211
 
2043
2212
 
2044
- svcdetach(callback) {
2213
+ svcdetach(callback: Callback | undefined) {
2045
2214
  var self = this;
2046
2215
 
2047
2216
  if (self._isClosed) {
@@ -2056,9 +2225,9 @@ class Connection {
2056
2225
 
2057
2226
  msg.pos = 0;
2058
2227
  msg.addInt(Const.op_service_detach);
2059
- msg.addInt(this.svchandle); // Database Object ID
2228
+ msg.addInt(this.svchandle!); // Database Object ID
2060
2229
 
2061
- self._queueEvent(function (err, ret) {
2230
+ self._queueEvent(function (err: any, ret: any) {
2062
2231
  delete (self.svchandle);
2063
2232
  if (callback)
2064
2233
  callback(err, ret);
@@ -2067,7 +2236,7 @@ class Connection {
2067
2236
 
2068
2237
 
2069
2238
 
2070
- auxConnection(eventid, callback) {
2239
+ auxConnection(eventid: number | Callback, callback?: Callback) {
2071
2240
  if (typeof eventid === 'function') {
2072
2241
  // Preserve the older auxConnection(callback) call shape; plain
2073
2242
  // auxiliary connections historically used event id 0.
@@ -2081,13 +2250,13 @@ class Connection {
2081
2250
  msg.pos = 0;
2082
2251
  msg.addInt(Const.op_connect_request);
2083
2252
  msg.addInt(1); // async
2084
- msg.addInt(self.dbhandle);
2253
+ msg.addInt(self.dbhandle!);
2085
2254
  msg.addInt(eventid);
2086
2255
  if (process.env.FIREBIRD_DEBUG) {
2087
2256
  console.log('[fb-debug] auxConnection: sending op_connect_request(53) dbhandle=%d eventid=%d queue_before=%d xdr_saved=%s',
2088
2257
  self.dbhandle, eventid, self._queue.length, Boolean(self._xdr));
2089
2258
  }
2090
- function cb(err, ret) {
2259
+ function cb(err: any, ret: any) {
2091
2260
 
2092
2261
  if (err) {
2093
2262
  if (process.env.FIREBIRD_DEBUG) {
@@ -2108,13 +2277,13 @@ class Connection {
2108
2277
  socket_info.family, socket_info.port, socket_info.host, self._queue.length);
2109
2278
  }
2110
2279
 
2111
- callback(undefined, socket_info);
2280
+ callback!(undefined, socket_info);
2112
2281
  }
2113
2282
  this._queueEvent(cb);
2114
2283
  }
2115
2284
 
2116
2285
 
2117
- queEvents(events, eventid, callback) {
2286
+ queEvents(events: Record<string, number>, eventid: number, callback: Callback) {
2118
2287
  var self = this;
2119
2288
  if (this._isClosed)
2120
2289
  return this.throwClosed(callback);
@@ -2123,7 +2292,7 @@ class Connection {
2123
2292
  blr.pos = 0;
2124
2293
  msg.pos = 0;
2125
2294
  msg.addInt(Const.op_que_events);
2126
- msg.addInt(this.dbhandle);
2295
+ msg.addInt(this.dbhandle!);
2127
2296
  // prepare EPB
2128
2297
  blr.addByte(1) // epb_version
2129
2298
  for (var event in events) {
@@ -2137,7 +2306,7 @@ class Connection {
2137
2306
  msg.addInt(0); // args
2138
2307
  msg.addInt(eventid);
2139
2308
 
2140
- function cb(err, ret) {
2309
+ function cb(err: any, ret: any) {
2141
2310
  if (err) {
2142
2311
  doError(err, callback);
2143
2312
  return;
@@ -2150,17 +2319,17 @@ class Connection {
2150
2319
  }
2151
2320
 
2152
2321
 
2153
- closeEvents(eventid, callback) {
2322
+ closeEvents(eventid: number, callback: Callback) {
2154
2323
  var self = this;
2155
2324
  if (this._isClosed)
2156
2325
  return this.throwClosed(callback);
2157
2326
  var msg = self._msg;
2158
2327
  msg.pos = 0;
2159
2328
  msg.addInt(Const.op_cancel_events);
2160
- msg.addInt(self.dbhandle);
2329
+ msg.addInt(self.dbhandle!);
2161
2330
  msg.addInt(eventid);
2162
2331
 
2163
- function cb(err, ret) {
2332
+ function cb(err: any, ret: any) {
2164
2333
  if (err) {
2165
2334
  doError(err, callback);
2166
2335
  return;
@@ -2179,7 +2348,7 @@ const opcodeNames = Object.fromEntries(
2179
2348
  Object.entries(Const).filter(([k]) => k.startsWith('op_')).map(([k, v]) => [v, k])
2180
2349
  );
2181
2350
 
2182
- function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any, cb: (err?: any, obj?: any) => void) {
2351
+ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cnx: Connection, lowercase_keys: boolean | undefined, cb: (err?: any, obj?: any) => void) {
2183
2352
  try {
2184
2353
  do {
2185
2354
  var r = data.r || data.readInt();
@@ -2200,7 +2369,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2200
2369
  cnx._inlineBlobs = new Map();
2201
2370
  }
2202
2371
  const cacheKey = `${blob_id.high}:${blob_id.low}`;
2203
- cnx._inlineBlobs.set(cacheKey, blob_data);
2372
+ cnx._inlineBlobs.set(cacheKey, blob_data!);
2204
2373
  r = Const.op_dummy; // Continue loop to read next opcode
2205
2374
  }
2206
2375
  } while (r === Const.op_dummy);
@@ -2210,7 +2379,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2210
2379
  r, opcodeNames[r] || 'unknown', data.pos, data.buffer.length);
2211
2380
  }
2212
2381
 
2213
- var item, op, response;
2382
+ var item, op, response: any;
2214
2383
 
2215
2384
  switch (r) {
2216
2385
  case Const.op_response:
@@ -2221,7 +2390,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2221
2390
  response = {};
2222
2391
  }
2223
2392
 
2224
- let loop = function (err) {
2393
+ let loop = function (err: any) {
2225
2394
  if (err) {
2226
2395
  return cb(err);
2227
2396
  } else {
@@ -2282,13 +2451,28 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2282
2451
  }
2283
2452
  case Const.op_fetch_response:
2284
2453
  case Const.op_sql_response:
2285
- var statement = callback.statement;
2454
+ // fetch/sql_response entries always carry their statement
2455
+ var statement = callback!.statement!;
2286
2456
  var output = statement.output;
2287
2457
  var custom = statement.options || {};
2288
2458
  var isOpFetch = r === Const.op_fetch_response;
2289
2459
  var _xdrpos;
2290
2460
  statement.nbrowsfetched = statement.nbrowsfetched || 0;
2291
2461
 
2462
+ // The f* decode state is only meaningful within a single
2463
+ // decode call: incomplete packets are re-decoded from scratch
2464
+ // on a fresh XdrReader (see the 'data' handler). State left by
2465
+ // an earlier packet in the same data event (e.g. fstatus=100 /
2466
+ // fcount=0 from a completed fetch) would make this decode
2467
+ // consume just the opcode and desync every later response.
2468
+ delete data.fstatus;
2469
+ delete data.fcount;
2470
+ delete data.fcolumn;
2471
+ delete data.frow;
2472
+ delete data.frows;
2473
+ delete data.fcols;
2474
+ delete data.ftables;
2475
+
2292
2476
  if (isOpFetch && data.fop) { // could be set when a packet is not complete
2293
2477
  data.readBuffer(68); // ??
2294
2478
  op = data.readInt(); // ??
@@ -2309,56 +2493,74 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2309
2493
  data.frows = data.frows || [];
2310
2494
 
2311
2495
  if (custom.asObject && !data.fcols) {
2312
- if (lowercase_keys) {
2313
- data.fcols = output.map((column) => column.alias.toLowerCase());
2314
- } else {
2315
- data.fcols = output.map((column) => column.alias);
2496
+ const nest = Xsql.resolveNestTables(custom, cnx.options);
2497
+ const columnKeys = Xsql.computeColumnKeys(output, nest, lowercase_keys);
2498
+ data.fcols = columnKeys.map((k) => k.key);
2499
+ if (nest === true) {
2500
+ // computeColumnKeys always sets table when nesting
2501
+ data.ftables = columnKeys.map((k) => k.table!);
2316
2502
  }
2317
2503
  }
2318
2504
 
2319
- const arrBlob = [];
2505
+ const arrBlob: any[] = [];
2320
2506
  const lowerV13 = statement.connection.accept.protocolVersion < Const.PROTOCOL_VERSION13;
2321
2507
 
2508
+ // op_sql_response (op_execute2) is always followed by an
2509
+ // op_response carrying the execute status vector. The row loop
2510
+ // below consumes it after the last row, but with zero rows
2511
+ // (e.g. INSERT ... RETURNING failing on a constraint) it stays
2512
+ // in the buffer, shifting every later response to the wrong
2513
+ // callback and poisoning the connection (issue #341).
2514
+ var sqlResponseTrailerPending = !isOpFetch && !data.fcount;
2515
+
2322
2516
  while (data.fcount && (data.fstatus !== 100)) {
2323
2517
  let nullBitSet;
2324
2518
  if (!lowerV13) {
2325
2519
  const nullBitsLen = Math.floor((output.length + 7) / 8);
2326
- nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false));
2520
+ nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false)!);
2327
2521
  data.readBuffer((4 - nullBitsLen) & 3, false); // Skip padding
2328
2522
  }
2329
2523
 
2330
2524
  for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
2331
2525
  item = output[data.fcolumn];
2332
2526
 
2333
- if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
2334
- if (custom.asObject) {
2335
- data.frow[data.fcols[data.fcolumn]] = null;
2336
- } else {
2337
- data.frow[data.fcolumn] = null;
2338
- }
2527
+ if (!lowerV13 && nullBitSet!.get(data.fcolumn)) {
2528
+ const nullKey = custom.asObject ? data.fcols![data.fcolumn!] : data.fcolumn;
2529
+ // ftables is only set when nestTables === true, so
2530
+ // the default path writes straight into the row
2531
+ (data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn!]) : data.frow)[nullKey] =
2532
+ applyTypeCast(cnx.options, item, null);
2339
2533
 
2340
2534
  continue;
2341
2535
  }
2342
2536
 
2343
2537
  try {
2344
2538
  _xdrpos = data.pos;
2345
- const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
2539
+ const key = custom.asObject ? data.fcols![data.fcolumn!] : data.fcolumn;
2346
2540
  const row = data.frows.length;
2347
2541
  let value = item.decode(data, lowerV13, cnx.options);
2542
+ // text blobs resolved by blobAsText run through the
2543
+ // typeCast hook once the text arrives (see fetchAll),
2544
+ // not here where the value is still a pending fetch
2545
+ let pendingTextBlob = false;
2348
2546
 
2349
2547
  if (item.type === Const.SQL_BLOB && value !== null) {
2350
2548
  if (item.subType === Const.isc_blob_text && cnx.options.blobAsText) {
2351
- value = fetch_blob_async_transaction(statement, value, key, row);
2549
+ value = fetch_blob_async_transaction(statement, value, key, row, item,
2550
+ data.ftables && data.ftables[data.fcolumn!]);
2352
2551
  arrBlob.push(value);
2552
+ pendingTextBlob = true;
2353
2553
  } else {
2354
2554
  value = fetch_blob_async(statement, value, key, row);
2355
2555
  }
2356
2556
  }
2357
2557
 
2358
- data.frow[key] = parseValueIfJson(value, cnx.options);
2558
+ (data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn!]) : data.frow)[key] = pendingTextBlob
2559
+ ? value
2560
+ : applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
2359
2561
  } catch (e) {
2360
2562
  // uncomplete packet read
2361
- data.pos = _xdrpos;
2563
+ data.pos = _xdrpos!;
2362
2564
  data.r = r;
2363
2565
  return cb(new Error('Packet is not complete'));
2364
2566
  }
@@ -2403,6 +2605,17 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2403
2605
  statement.nbrowsfetched++;
2404
2606
  }
2405
2607
 
2608
+ if (sqlResponseTrailerPending) {
2609
+ op = data.readInt();
2610
+ if (op === Const.op_response) {
2611
+ response = {};
2612
+ parseOpResponse(data, response);
2613
+ if (response.status) {
2614
+ return cb(null, response);
2615
+ }
2616
+ }
2617
+ }
2618
+
2406
2619
  // ToDo: emit "result" with blob subtype string decoded
2407
2620
  statement.connection.db.emit('result', data.frows, arrBlob);
2408
2621
  return cb(null, {data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob});
@@ -2427,7 +2640,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2427
2640
  }
2428
2641
 
2429
2642
  if (r === Const.op_cond_accept || r === Const.op_accept_data) {
2430
- var d = new BlrReader(data.readArray());
2643
+ var d = new BlrReader(data.readArray()!);
2431
2644
  accept.pluginName = data.readString(Const.DEFAULT_ENCODING);
2432
2645
  var is_authenticated = data.readInt();
2433
2646
  var keys = data.readString(Const.DEFAULT_ENCODING); // keys
@@ -2447,7 +2660,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2447
2660
  }
2448
2661
 
2449
2662
  if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
2450
- var crypto = {
2663
+ var crypto: Record<string, string> = {
2451
2664
  Srp: 'sha1',
2452
2665
  Srp256: 'sha256',
2453
2666
  Srp384: 'sha384',
@@ -2458,7 +2671,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2458
2671
  if (!d.buffer) {
2459
2672
  cnx._pendingAccept = accept;
2460
2673
  cnx.sendOpContAuth(
2461
- cnx.clientKeys.public.toString(16),
2674
+ cnx.clientKeys!.public.toString(16),
2462
2675
  Const.DEFAULT_ENCODING,
2463
2676
  accept.pluginName
2464
2677
  );
@@ -2489,20 +2702,20 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2489
2702
 
2490
2703
  if (process.env.FIREBIRD_DEBUG) {
2491
2704
  console.log('--- DEBUG SRP Handshake ---');
2492
- console.log('salt:', cnx.serverKeys.salt);
2493
- console.log('server public key:', cnx.serverKeys.public.toString(16));
2494
- console.log('client public key:', cnx.clientKeys.public.toString(16));
2705
+ console.log('salt:', cnx.serverKeys!.salt);
2706
+ console.log('server public key:', cnx.serverKeys!.public.toString(16));
2707
+ console.log('client public key:', cnx.clientKeys!.public.toString(16));
2495
2708
  console.log('hashAlgo:', accept.srpAlgo);
2496
2709
  }
2497
2710
 
2498
2711
  const _t1 = Date.now();
2499
2712
  var proof = srp.clientProof(
2500
- cnx.options.user.toUpperCase(),
2501
- cnx.options.password,
2502
- cnx.serverKeys.salt,
2503
- cnx.clientKeys.public,
2504
- cnx.serverKeys.public,
2505
- cnx.clientKeys.private,
2713
+ cnx.options.user!.toUpperCase(),
2714
+ cnx.options.password!,
2715
+ cnx.serverKeys!.salt,
2716
+ cnx.clientKeys!.public,
2717
+ cnx.serverKeys!.public,
2718
+ cnx.clientKeys!.private,
2506
2719
  accept.srpAlgo
2507
2720
  );
2508
2721
 
@@ -2519,7 +2732,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2519
2732
  accept.authData = proof.authData.toString(16);
2520
2733
  accept.sessionKey = proof.clientSessionKey;
2521
2734
  } else if (accept.pluginName === Const.AUTH_PLUGIN_LEGACY) {
2522
- accept.authData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
2735
+ accept.authData = crypt.crypt(cnx.options.password!, Const.LEGACY_AUTH_SALT).substring(2);
2523
2736
  } else {
2524
2737
  return cb(new Error('Unknow auth plugin : ' + accept.pluginName));
2525
2738
  }
@@ -2557,7 +2770,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2557
2770
 
2558
2771
  return cb(undefined, accept);
2559
2772
  case Const.op_cont_auth:
2560
- var d = new BlrReader(data.readArray());
2773
+ var d = new BlrReader(data.readArray()!);
2561
2774
  var pluginName = data.readString(Const.DEFAULT_ENCODING);
2562
2775
  data.readString(Const.DEFAULT_ENCODING); // plist
2563
2776
  data.readString(Const.DEFAULT_ENCODING); // pkey
@@ -2584,7 +2797,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2584
2797
  // the proof with the new plugin's hash algorithm - rather than
2585
2798
  // as the server's M2 proof, otherwise the client silently waits
2586
2799
  // forever for an op_accept the server will never send (#254).
2587
- if (!cnx.serverKeys || cnx.serverKeys.pluginName !== pluginName) {
2800
+ if (!cnx.serverKeys || cnx.serverKeys!.pluginName !== pluginName) {
2588
2801
  // Check buffer contains salt
2589
2802
  var saltLen = d.buffer.readUInt16LE(0);
2590
2803
  if (saltLen > 32 * 2) {
@@ -2607,7 +2820,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2607
2820
  pluginName: pluginName
2608
2821
  };
2609
2822
 
2610
- var crypto = {
2823
+ var crypto: Record<string, string> = {
2611
2824
  Srp: 'sha1',
2612
2825
  Srp256: 'sha256',
2613
2826
  Srp384: 'sha384',
@@ -2617,20 +2830,20 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2617
2830
 
2618
2831
  if (process.env.FIREBIRD_DEBUG) {
2619
2832
  console.log('--- DEBUG SRP Handshake ---');
2620
- console.log('salt:', cnx.serverKeys.salt);
2621
- console.log('server public key:', cnx.serverKeys.public.toString(16));
2622
- console.log('client public key:', cnx.clientKeys.public.toString(16));
2833
+ console.log('salt:', cnx.serverKeys!.salt);
2834
+ console.log('server public key:', cnx.serverKeys!.public.toString(16));
2835
+ console.log('client public key:', cnx.clientKeys!.public.toString(16));
2623
2836
  console.log('hashAlgo:', srpAlgo);
2624
2837
  }
2625
2838
 
2626
2839
  const _t1 = Date.now();
2627
2840
  var proof = srp.clientProof(
2628
- cnx.options.user.toUpperCase(),
2629
- cnx.options.password,
2630
- cnx.serverKeys.salt,
2631
- cnx.clientKeys.public,
2632
- cnx.serverKeys.public,
2633
- cnx.clientKeys.private,
2841
+ cnx.options.user!.toUpperCase(),
2842
+ cnx.options.password!,
2843
+ cnx.serverKeys!.salt,
2844
+ cnx.clientKeys!.public,
2845
+ cnx.serverKeys!.public,
2846
+ cnx.clientKeys!.private,
2634
2847
  srpAlgo
2635
2848
  );
2636
2849
 
@@ -2667,7 +2880,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2667
2880
  cnx._pendingAccept.protocolVersion,
2668
2881
  cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
2669
2882
  }
2670
- var legacyAuthData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
2883
+ var legacyAuthData = crypt.crypt(cnx.options.password!, Const.LEGACY_AUTH_SALT).substring(2);
2671
2884
  cnx.sendOpContAuth(legacyAuthData, Const.DEFAULT_ENCODING, pluginName);
2672
2885
  return; // wait for op_accept
2673
2886
  }
@@ -2680,7 +2893,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2680
2893
 
2681
2894
  if (pluginName === Const.AUTH_PLUGIN_LEGACY) { // Fallback to LegacyAuth
2682
2895
  cnx.accept.pluginName = pluginName;
2683
- cnx.accept.authData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
2896
+ cnx.accept.authData = crypt.crypt(cnx.options.password!, Const.LEGACY_AUTH_SALT).substring(2);
2684
2897
 
2685
2898
  cnx.sendOpContAuth(
2686
2899
  cnx.accept.authData,
@@ -2773,7 +2986,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2773
2986
  }
2774
2987
  return cb(new Error('Unexpected:' + r));
2775
2988
  }
2776
- } catch (err) {
2989
+ } catch (err: any) {
2777
2990
  if (process.env.FIREBIRD_DEBUG) {
2778
2991
  console.warn('[fb-debug] decodeResponse exception: %s (RangeError=%s) pos=%d buflen=%d',
2779
2992
  err.message, err instanceof RangeError, data.pos, data.buffer.length);
@@ -2789,8 +3002,8 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2789
3002
  * Read one XDR status vector (as in op_response / op_batch_cs error
2790
3003
  * vectors): a stream of isc_arg_* items terminated by isc_arg_end.
2791
3004
  */
2792
- function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2793
- var result: { status: any[]; sqlcode?: number } = { status: [] };
3005
+ function readStatusVector(data: XdrReader): { status: any[]; warnings?: any[]; sqlcode?: number } {
3006
+ var result: { status: any[]; warnings?: any[]; sqlcode?: number } = { status: [] };
2794
3007
  var item: any = {};
2795
3008
 
2796
3009
  while (true) {
@@ -2820,13 +3033,24 @@ function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2820
3033
  result.sqlcode = n;
2821
3034
  }
2822
3035
  break;
3036
+ case Const.isc_arg_warning:
3037
+ // A warning attached to a SUCCESS vector (e.g. "parallel
3038
+ // workers value capped"). Keep it out of `status` so the
3039
+ // operation is not mistaken for a failure; later string/
3040
+ // number items attach to the warning entry.
3041
+ var wnum = data.readInt();
3042
+ item = { gdscode: wnum };
3043
+ if (wnum) {
3044
+ (result.warnings = result.warnings || []).push(item);
3045
+ }
3046
+ break;
2823
3047
  default:
2824
3048
  throw new Error('Unexpected status vector item: ' + op);
2825
3049
  }
2826
3050
  }
2827
3051
  }
2828
3052
 
2829
- function parseOpResponse(data: any, response: any, cb?: (err?: any, response?: any) => void) {
3053
+ function parseOpResponse(data: XdrReader, response: WireResponse, cb?: (err?: any, response?: any) => void) {
2830
3054
  var handle = data.readInt();
2831
3055
 
2832
3056
  if (!response.handle) {
@@ -2889,18 +3113,31 @@ function parseOpResponse(data: any, response: any, cb?: (err?: any, response?: a
2889
3113
  response.sqlcode = num;
2890
3114
  }
2891
3115
 
3116
+ break;
3117
+ case Const.isc_arg_warning:
3118
+ // A warning attached to a SUCCESS response (e.g. Firebird's
3119
+ // "parallel workers value capped" on attach). Keep it out of
3120
+ // `status` so the response is not mistaken for an error;
3121
+ // later string/number items attach to the warning entry.
3122
+ num = data.readInt();
3123
+ item = { gdscode: num };
3124
+ if (num) {
3125
+ (response.warnings = response.warnings || []).push(item);
3126
+ }
2892
3127
  break;
2893
3128
  default:
3129
+ // Stop parsing: continuing the loop after an unknown item
3130
+ // re-read the same bytes forever (the caller resets the
3131
+ // reader position when the error is delivered).
2894
3132
  if (cb) {
2895
- cb(new Error('Unexpected: ' + op))
2896
- } else {
2897
- throw new Error('Unexpected: ' + op);
3133
+ return cb(new Error('Unexpected: ' + op));
2898
3134
  }
3135
+ throw new Error('Unexpected: ' + op);
2899
3136
  }
2900
3137
  }
2901
3138
  }
2902
3139
 
2903
- function describe(buff: Buffer, statement: any) {
3140
+ function describe(buff: Buffer, statement: Statement) {
2904
3141
  var br = new BlrReader(buff);
2905
3142
  var parameters: any = null;
2906
3143
  var type: any, param: any;
@@ -2908,7 +3145,7 @@ function describe(buff: Buffer, statement: any) {
2908
3145
  while (br.pos < br.buffer.length) {
2909
3146
  switch (br.readByteCode()) {
2910
3147
  case Const.isc_info_sql_stmt_type:
2911
- statement.type = br.readInt();
3148
+ statement.type = br.readInt()!;
2912
3149
  break;
2913
3150
  case Const.isc_info_sql_get_plan:
2914
3151
  statement.plan = br.readString(Const.DEFAULT_ENCODING);
@@ -2932,7 +3169,9 @@ function describe(buff: Buffer, statement: any) {
2932
3169
  case Const.isc_info_sql_describe_end:
2933
3170
  break;
2934
3171
  case Const.isc_info_sql_sqlda_seq:
2935
- var num = br.readInt();
3172
+ // describe output always encodes the sequence as a
3173
+ // 1/2/4-byte int, so readInt cannot return undefined
3174
+ var num = br.readInt()!;
2936
3175
  break;
2937
3176
  case Const.isc_info_sql_type:
2938
3177
  type = br.readInt();
@@ -2963,7 +3202,9 @@ function describe(buff: Buffer, statement: any) {
2963
3202
  default:
2964
3203
  throw new Error('Unexpected');
2965
3204
  }
2966
- parameters[num-1] = param;
3205
+ // isc_info_sql_sqlda_seq always precedes the type
3206
+ // item in the describe stream, so num is set here
3207
+ parameters[num!-1] = param;
2967
3208
  param.type = type;
2968
3209
  param.nullable = Boolean(param.type & 1);
2969
3210
  param.type &= ~1;
@@ -3243,10 +3484,10 @@ function CalcBlr(blr: BlrWriter, xsqlda: any[]) {
3243
3484
  blr.addByte(Const.blr_eoc);
3244
3485
  }
3245
3486
 
3246
- function fetch_blob_async_transaction(statement: any, id: any, column: any, row: any) {
3247
- const infoValue = { row, column, value: '' };
3487
+ function fetch_blob_async_transaction(statement: Statement, id: Quad, column: string | number, row: number, meta?: Xsql.SQLVarBase, table?: string) {
3488
+ const infoValue = { row, column, value: '', meta, table };
3248
3489
 
3249
- return (transactionArg) => {
3490
+ return (transactionArg: any) => {
3250
3491
  const cacheKey = `${id.high}:${id.low}`;
3251
3492
  if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
3252
3493
  const data = statement.connection._inlineBlobs.get(cacheKey);
@@ -3256,10 +3497,10 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3256
3497
 
3257
3498
  const singleTransaction = transactionArg === undefined;
3258
3499
 
3259
- let promiseTransaction;
3500
+ let promiseTransaction: Promise<Transaction>;
3260
3501
  if (singleTransaction) {
3261
3502
  promiseTransaction = new Promise((resolve, reject) => {
3262
- statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
3503
+ statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err: any, transaction: Transaction) => {
3263
3504
  if (err) {
3264
3505
  return reject(err);
3265
3506
  }
@@ -3273,7 +3514,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3273
3514
  return promiseTransaction.then((transaction) => {
3274
3515
  return new Promise((resolve, reject) => {
3275
3516
  statement.connection._pending.push('openBlob');
3276
- statement.connection.openBlob(id, transaction, (err, blob) => {
3517
+ statement.connection.openBlob(id, transaction, (err: any, blob: any) => {
3277
3518
 
3278
3519
  if (err) {
3279
3520
  reject(err);
@@ -3281,7 +3522,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3281
3522
  }
3282
3523
 
3283
3524
  const read = () => {
3284
- statement.connection.getSegment(blob, (err, ret) => {
3525
+ statement.connection.getSegment(blob, (err: any, ret: any) => {
3285
3526
 
3286
3527
  if (err) {
3287
3528
  if (singleTransaction) {
@@ -3305,7 +3546,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3305
3546
 
3306
3547
  statement.connection.closeBlob(blob);
3307
3548
  if (singleTransaction) {
3308
- transaction.commit((err) => {
3549
+ transaction.commit((err: any) => {
3309
3550
  if (err) {
3310
3551
  reject(err);
3311
3552
  } else {
@@ -3325,14 +3566,14 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3325
3566
  };
3326
3567
  }
3327
3568
 
3328
- function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3329
- const cbTransaction = (transaction, close, callback) => {
3569
+ function fetch_blob_async(statement: Statement, id: Quad, name: string | number, row: number) {
3570
+ const cbTransaction = (transaction: Transaction, close: any, callback: any) => {
3330
3571
  statement.connection._pending.push('openBlob');
3331
- statement.connection.openBlob(id, transaction, (err, blob) => {
3572
+ statement.connection.openBlob(id, transaction, (err: any, blob: any) => {
3332
3573
  let e: any = new Events.EventEmitter();
3333
3574
 
3334
- e.pipe = (stream) => {
3335
- e.on('data', (chunk) => {
3575
+ e.pipe = (stream: any) => {
3576
+ e.on('data', (chunk: any) => {
3336
3577
  stream.write(chunk);
3337
3578
  });
3338
3579
  e.on('end', () => {
@@ -3345,7 +3586,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3345
3586
  }
3346
3587
 
3347
3588
  const read = () => {
3348
- statement.connection.getSegment(blob, (err, ret) => {
3589
+ statement.connection.getSegment(blob, (err: any, ret: any) => {
3349
3590
 
3350
3591
  if (err) {
3351
3592
  transaction.rollback(() => {
@@ -3368,7 +3609,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3368
3609
 
3369
3610
  statement.connection.closeBlob(blob);
3370
3611
  if (close) {
3371
- transaction.commit((err) => {
3612
+ transaction.commit((err: any) => {
3372
3613
  if (err) {
3373
3614
  e.emit('error', err);
3374
3615
  } else {
@@ -3388,7 +3629,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3388
3629
  });
3389
3630
  };
3390
3631
 
3391
- return (transaction, callback) => {
3632
+ return (transaction: Transaction, callback: any) => {
3392
3633
  // callback(error, nameField, eventEmitter, row)
3393
3634
  const singleTransaction = callback === undefined;
3394
3635
  const actualCallback = singleTransaction ? transaction : callback;
@@ -3397,8 +3638,8 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3397
3638
  if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
3398
3639
  const data = statement.connection._inlineBlobs.get(cacheKey);
3399
3640
  let e: any = new Events.EventEmitter();
3400
- e.pipe = (stream) => {
3401
- e.on('data', (chunk) => {
3641
+ e.pipe = (stream: any) => {
3642
+ e.on('data', (chunk: any) => {
3402
3643
  stream.write(chunk);
3403
3644
  });
3404
3645
  e.on('end', () => {
@@ -3419,7 +3660,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3419
3660
 
3420
3661
  if (singleTransaction) {
3421
3662
  callback = transaction;
3422
- statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
3663
+ statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err: any, transaction: Transaction) => {
3423
3664
  if (err) {
3424
3665
  callback(err);
3425
3666
  return;
@@ -3438,7 +3679,7 @@ function doSynchronousLoop(data: any[], processData: (row: any, index: number, n
3438
3679
  return;
3439
3680
  }
3440
3681
 
3441
- const loop = (index) => {
3682
+ const loop = (index: any) => {
3442
3683
  processData(data[index], index, (err) => {
3443
3684
  if (err) {
3444
3685
  done(err);