node-firebird 2.8.1 → 2.10.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 (53) hide show
  1. package/README.md +268 -7
  2. package/lib/index.d.ts +17 -9
  3. package/lib/index.js +42 -3
  4. package/lib/named-params.d.ts +42 -0
  5. package/lib/named-params.js +133 -0
  6. package/lib/pool.js +1 -1
  7. package/lib/srp.d.ts +3 -3
  8. package/lib/types.d.ts +185 -25
  9. package/lib/uri.d.ts +57 -0
  10. package/lib/uri.js +193 -0
  11. package/lib/wire/connection.d.ts +92 -59
  12. package/lib/wire/connection.js +286 -55
  13. package/lib/wire/const.d.ts +9 -1
  14. package/lib/wire/const.js +21 -9
  15. package/lib/wire/database.d.ts +51 -26
  16. package/lib/wire/database.js +26 -8
  17. package/lib/wire/eventConnection.js +5 -3
  18. package/lib/wire/query-stream.d.ts +18 -0
  19. package/lib/wire/query-stream.js +73 -0
  20. package/lib/wire/serialize.d.ts +18 -2
  21. package/lib/wire/serialize.js +7 -0
  22. package/lib/wire/service.d.ts +42 -0
  23. package/lib/wire/service.js +145 -0
  24. package/lib/wire/socket.d.ts +3 -1
  25. package/lib/wire/socket.js +5 -2
  26. package/lib/wire/statement.d.ts +40 -20
  27. package/lib/wire/statement.js +26 -6
  28. package/lib/wire/transaction.d.ts +32 -18
  29. package/lib/wire/transaction.js +50 -8
  30. package/lib/wire/wire-types.d.ts +116 -0
  31. package/lib/wire/wire-types.js +10 -0
  32. package/lib/wire/xsqlvar.d.ts +18 -18
  33. package/package.json +1 -1
  34. package/src/index.ts +54 -15
  35. package/src/messages.ts +1 -1
  36. package/src/named-params.ts +145 -0
  37. package/src/pool.ts +1 -1
  38. package/src/srp.ts +6 -6
  39. package/src/types.ts +183 -25
  40. package/src/unix-crypt.ts +9 -9
  41. package/src/uri.ts +204 -0
  42. package/src/wire/connection.ts +481 -234
  43. package/src/wire/const.ts +21 -9
  44. package/src/wire/database.ts +75 -43
  45. package/src/wire/eventConnection.ts +8 -5
  46. package/src/wire/query-stream.ts +80 -0
  47. package/src/wire/serialize.ts +29 -0
  48. package/src/wire/service.ts +188 -6
  49. package/src/wire/socket.ts +17 -8
  50. package/src/wire/statement.ts +68 -31
  51. package/src/wire/transaction.ts +85 -33
  52. package/src/wire/wire-types.ts +127 -0
  53. package/src/wire/xsqlvar.ts +9 -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,39 +200,81 @@ 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;
90
209
  this._isDetach = false;
91
210
  this._isUsed = false;
92
211
  this._pooled = options.isPool||false;
93
- if (options && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
94
- if (options && options.blobReadChunkSize > 65535) options.blobReadChunkSize = 65535;
212
+ // Credentials may be absent (e.g. a traditional host:database
213
+ // connection string) apply the driver defaults once here, so every
214
+ // auth path (op_connect CNCT block, SRP proof, legacy cont_auth) sees
215
+ // the same values.
216
+ if (options && !options.user) options.user = Const.DEFAULT_USER;
217
+ if (options && !options.password) options.password = Const.DEFAULT_PASSWORD;
218
+ if (options && options.blobChunkSize && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
219
+ if (options && options.blobReadChunkSize && options.blobReadChunkSize > 65535) options.blobReadChunkSize = 65535;
95
220
  this.options = options;
96
221
  this._bind_events(host, port, callback);
97
222
  this.error;
98
223
  this._retry_connection_id;
99
224
  this._retry_connection_interval = options.retryConnectionInterval || 1000;
100
- this._max_cached_query = options.maxCachedQuery || -1;
101
- this._cache_query = options.cacheQuery?{}:null;
225
+ this._statementCacheSize = statementCacheLimit(options);
226
+ this._statementCache = this._statementCacheSize > 0 ? new Map() : null;
102
227
  this._messageFile = options.messageFile || path.join(__dirname, 'firebird.msg');
103
228
  }
104
229
 
105
230
 
106
- _setcachedquery(query, statement) {
107
- if (this._cache_query){
108
- if (this._max_cached_query === -1 || this._max_cached_query > Object.keys(this._cache_query).length){
109
- this._cache_query[query] = statement;
110
- }
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;
111
241
  }
112
-
113
-
242
+ const statement = cache.get(query);
243
+ if (!statement) {
244
+ return null;
245
+ }
246
+ cache.delete(query);
247
+ return statement;
114
248
  }
115
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
+ }
116
269
 
117
- getCachedQuery(query) {
118
- 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);
119
278
  }
120
279
 
121
280
 
@@ -125,7 +284,7 @@ class Connection {
125
284
  // be transparently resumed on a reconnect: a fresh attach() hands out new
126
285
  // transaction/statement handles, so nothing the server sends back could
127
286
  // ever match a callback queued against the old connection.
128
- _rejectPending(err) {
287
+ _rejectPending(err: any) {
129
288
  var queue = this._queue;
130
289
  this._queue = [];
131
290
  this._pending = [];
@@ -136,7 +295,7 @@ class Connection {
136
295
  }
137
296
 
138
297
 
139
- _bind_events(host, port, callback) {
298
+ _bind_events(host: string, port: number, callback: SimpleCallback | undefined) {
140
299
 
141
300
  var self = this;
142
301
 
@@ -162,17 +321,19 @@ class Connection {
162
321
 
163
322
  self._retry_connection_id = setTimeout(function() {
164
323
  self._socket.removeAllListeners();
165
- 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!;
166
327
 
167
- var ctx = new Connection(host, port, function(err) {
168
- ctx.connect(self.options, function(err) {
328
+ var ctx = new Connection(host, port, function(err: any) {
329
+ ctx.connect(self.options, function(err: any) {
169
330
 
170
331
  if (err) {
171
332
  self.db.emit('error', err);
172
333
  return;
173
334
  }
174
335
 
175
- ctx.attach(self.options, function(err) {
336
+ ctx.attach(self.options, function(err: any) {
176
337
 
177
338
  if (err) {
178
339
  self.db.emit('error', err);
@@ -191,7 +352,7 @@ class Connection {
191
352
 
192
353
  });
193
354
 
194
- self._socket.on('error', function(e) {
355
+ self._socket.on('error', function(e: any) {
195
356
 
196
357
  self.error = e;
197
358
 
@@ -210,8 +371,8 @@ class Connection {
210
371
  callback();
211
372
  });
212
373
 
213
- self._socket.on('data', function (data) {
214
- var xdr;
374
+ self._socket.on('data', function (data: any) {
375
+ var xdr: any;
215
376
  var hadSavedBuffer = Boolean(self._xdr);
216
377
 
217
378
  if (!self._xdr) {
@@ -331,7 +492,7 @@ class Connection {
331
492
 
332
493
 
333
494
 
334
- sendOpContAuth(authData, authDataEnc, pluginName) {
495
+ sendOpContAuth(authData: string, authDataEnc: BufferEncoding, pluginName: string) {
335
496
  var msg = this._msg;
336
497
  msg.pos = 0;
337
498
 
@@ -346,7 +507,7 @@ class Connection {
346
507
  }
347
508
 
348
509
 
349
- sendOpCrypt(encryptPlugin) {
510
+ sendOpCrypt(encryptPlugin: string) {
350
511
  var msg = this._msg;
351
512
  msg.pos = 0;
352
513
 
@@ -358,7 +519,7 @@ class Connection {
358
519
  }
359
520
 
360
521
 
361
- sendOpCryptKeyCallback(pluginData) {
522
+ sendOpCryptKeyCallback(pluginData: BlrWriter) {
362
523
  var msg = this._msg;
363
524
  msg.pos = 0;
364
525
 
@@ -375,7 +536,7 @@ class Connection {
375
536
  * makes that operation fail with isc_cancelled (GDSCode.CANCELLED); the
376
537
  * op_cancel packet itself has no response, so nothing is queued here.
377
538
  */
378
- cancelOperation(kind, callback) {
539
+ cancelOperation(kind?: number | SimpleCallback, callback?: SimpleCallback) {
379
540
  if (typeof kind === 'function') {
380
541
  callback = kind;
381
542
  kind = undefined;
@@ -402,7 +563,7 @@ class Connection {
402
563
 
403
564
 
404
565
  /** Write a prebuilt packet and queue its response callback. */
405
- _queueEventBuffer(buffer, callback) {
566
+ _queueEventBuffer(buffer: Buffer, callback: QueueCallback | undefined) {
406
567
  if (this._isClosed) {
407
568
  if (callback)
408
569
  callback(new Error('Connection is closed.'));
@@ -414,7 +575,7 @@ class Connection {
414
575
  }
415
576
 
416
577
 
417
- _queueEvent(callback, defer = false) {
578
+ _queueEvent(callback: QueueCallback | undefined, defer = false) {
418
579
  var self = this;
419
580
 
420
581
  if (self._isClosed) {
@@ -426,15 +587,24 @@ class Connection {
426
587
  const canDefer = defer && this.accept.protocolVersion >= Const.PROTOCOL_VERSION11;
427
588
 
428
589
  self._socket.write(self._msg.getData(), canDefer);
429
- if (canDefer && callback) {
430
- callback();
590
+ if (canDefer) {
591
+ // A deferred packet sits in the socket buffer until the next
592
+ // non-deferred write flushes it, but the server still answers it
593
+ // with its own op_response (delivered along with that next
594
+ // exchange). Queue a placeholder to consume that response —
595
+ // otherwise the queue pairs it with the NEXT request and every
596
+ // later response is off by one. The op itself is fire-and-forget,
597
+ // so complete the caller right away.
598
+ self._queue.push(undefined);
599
+ if (callback)
600
+ callback();
431
601
  } else {
432
602
  self._queue.push(callback);
433
603
  }
434
604
  }
435
605
 
436
606
 
437
- connect(options, callback) {
607
+ connect(options: InternalOptions, callback: Callback<AcceptPacket> | undefined) {
438
608
  var pluginName = options.pluginName || Const.AUTH_PLUGIN_LIST[0];
439
609
  var msg = this._msg;
440
610
  var blr = this._blr;
@@ -445,7 +615,7 @@ class Connection {
445
615
  msg.pos = 0;
446
616
  blr.pos = 0;
447
617
 
448
- blr.addString(Const.CNCT_login, options.user, Const.DEFAULT_ENCODING);
618
+ blr.addString(Const.CNCT_login, options.user!, Const.DEFAULT_ENCODING);
449
619
  blr.addString(Const.CNCT_plugin_name, pluginName, Const.DEFAULT_ENCODING);
450
620
  blr.addString(Const.CNCT_plugin_list, Const.AUTH_PLUGIN_LIST.join(','), Const.DEFAULT_ENCODING);
451
621
 
@@ -459,7 +629,7 @@ class Connection {
459
629
  specificData = this.clientKeys.public.toString(16);
460
630
  blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
461
631
  } else if (pluginName === Const.AUTH_PLUGIN_LEGACY) {
462
- specificData = crypt.crypt(options.password, Const.LEGACY_AUTH_SALT).substring(2);
632
+ specificData = crypt.crypt(options.password!, Const.LEGACY_AUTH_SALT).substring(2);
463
633
  blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
464
634
  } else {
465
635
  doError(new Error('Invalide auth plugin \'' + pluginName + '\''), callback);
@@ -475,10 +645,17 @@ class Connection {
475
645
  msg.addInt(Const.CONNECT_VERSION3);
476
646
  msg.addInt(Const.ARCHITECTURE_GENERIC);
477
647
  msg.addString(options.database || options.filename, Const.DEFAULT_ENCODING);
478
- 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;
479
653
  var protocolsToSend = Const.SUPPORTED_PROTOCOL;
480
654
  if (protocolsToSend.length > maxProtocols) {
481
- 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);
482
659
  }
483
660
 
484
661
  msg.addInt(protocolsToSend.length); // Count of Protocol version understood count.
@@ -497,7 +674,7 @@ class Connection {
497
674
  }
498
675
 
499
676
  var self = this;
500
- function cb(err, ret) {
677
+ function cb(err: any, ret: any) {
501
678
  if (err) {
502
679
  doError(err, callback);
503
680
  return;
@@ -519,11 +696,11 @@ class Connection {
519
696
 
520
697
  var selectedPlugin = 'Arc4';
521
698
  if (ret.keys) {
522
- 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(); });
523
700
  var preferred = ['chacha64', 'chacha', 'arc4'];
524
701
  for (var i = 0; i < preferred.length; i++) {
525
702
  if (serverPlugins.indexOf(preferred[i]) !== -1) {
526
- var mapping = {
703
+ var mapping: Record<string, string> = {
527
704
  chacha64: 'ChaCha64',
528
705
  chacha: 'ChaCha',
529
706
  arc4: 'Arc4'
@@ -544,7 +721,7 @@ class Connection {
544
721
  }
545
722
 
546
723
  self._pending.push('crypt');
547
- self._queue.push(function(cryptErr, response) {
724
+ self._queue.push(function(cryptErr: any, response: any) {
548
725
  if (cryptErr) {
549
726
  doError(cryptErr, callback);
550
727
  return;
@@ -578,7 +755,7 @@ class Connection {
578
755
  }
579
756
 
580
757
 
581
- attach(options: any, callback?: any, db?: any) {
758
+ attach(options: InternalOptions, callback?: Callback<Database>, db?: Database) {
582
759
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
583
760
 
584
761
  var database = options.database || options.filename;
@@ -642,31 +819,19 @@ class Connection {
642
819
  }
643
820
 
644
821
  // Firebird 6.0 SQL Schema parameters (Protocol 20+).
645
- // These DPB tags configure the session's current schema and the
646
- // schema search path for unqualified object name resolution.
647
822
  if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
648
- if (options.defaultSchema) {
649
- // Sets CURRENT_SCHEMA for the session. Equivalent to issuing
650
- // SET SCHEMA <name> immediately after connecting.
651
- blr.addString(Const.isc_dpb_default_schema, options.defaultSchema, Const.DEFAULT_ENCODING);
652
- }
653
- if (options.searchPath) {
654
- // Comma-separated ordered schema name list, like PostgreSQL's
655
- // search_path. Unqualified object references are resolved by
656
- // scanning schemas in this order.
657
- const sp = Array.isArray(options.searchPath)
658
- ? options.searchPath.join(',')
659
- : String(options.searchPath);
823
+ const sp = buildSchemaSearchPath(options);
824
+ if (sp) {
660
825
  blr.addString(Const.isc_dpb_search_path, sp, Const.DEFAULT_ENCODING);
661
826
  }
662
827
  }
663
-
828
+
664
829
  msg.addInt(Const.op_attach);
665
830
  msg.addInt(0); // Database Object ID
666
831
  msg.addString(database, Const.DEFAULT_ENCODING);
667
832
  msg.addBlr(this._blr);
668
833
 
669
- function cb(err, ret) {
834
+ function cb(err: any, ret: any) {
670
835
  if (err) {
671
836
  doError(err, callback);
672
837
  return;
@@ -693,7 +858,7 @@ class Connection {
693
858
  }
694
859
 
695
860
 
696
- detach(callback) {
861
+ detach(callback: Callback | undefined) {
697
862
 
698
863
  var self = this;
699
864
 
@@ -709,7 +874,7 @@ class Connection {
709
874
  msg.addInt(Const.op_detach);
710
875
  msg.addInt(0); // Database Object ID
711
876
 
712
- self._queueEvent(function(err, ret) {
877
+ self._queueEvent(function(err: any, ret: any) {
713
878
  clearTimeout(self._retry_connection_id);
714
879
  delete(self.dbhandle);
715
880
  if (callback)
@@ -718,7 +883,7 @@ class Connection {
718
883
  }
719
884
 
720
885
 
721
- createDatabase(options, callback) {
886
+ createDatabase(options: InternalOptions, callback: Callback<Database> | undefined) {
722
887
  // Mirror attach(): honour the lowercase_keys option so that db.query()
723
888
  // called on a freshly-created database returns the expected column case.
724
889
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
@@ -781,16 +946,18 @@ class Connection {
781
946
 
782
947
  // Firebird 6.0 SQL Schema parameters (Protocol 20+).
783
948
  if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
784
- if (options.defaultSchema) {
785
- blr.addString(Const.isc_dpb_default_schema, options.defaultSchema, Const.DEFAULT_ENCODING);
786
- }
787
- if (options.searchPath) {
788
- const sp = Array.isArray(options.searchPath)
789
- ? options.searchPath.join(',')
790
- : String(options.searchPath);
949
+ const sp = buildSchemaSearchPath(options);
950
+ if (sp) {
791
951
  blr.addString(Const.isc_dpb_search_path, sp, Const.DEFAULT_ENCODING);
792
952
  }
793
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
+ }
794
961
 
795
962
  blr.addNumeric(Const.isc_dpb_sql_dialect, 3);
796
963
  blr.addNumeric(Const.isc_dpb_force_write, 1);
@@ -806,7 +973,7 @@ class Connection {
806
973
 
807
974
  var self = this;
808
975
 
809
- function cb(err, ret) {
976
+ function cb(err: any, ret: any) {
810
977
 
811
978
  if (ret)
812
979
  self.dbhandle = ret.handle;
@@ -823,15 +990,15 @@ class Connection {
823
990
  }
824
991
 
825
992
 
826
- dropDatabase(callback) {
993
+ dropDatabase(callback: SimpleCallback | undefined) {
827
994
  var msg = this._msg;
828
995
  msg.pos = 0;
829
996
 
830
997
  msg.addInt(Const.op_drop_database);
831
- msg.addInt(this.dbhandle);
998
+ msg.addInt(this.dbhandle!);
832
999
 
833
1000
  var self = this;
834
- this._queueEvent(function(err) {
1001
+ this._queueEvent(function(err: any) {
835
1002
  self.detach(function() {
836
1003
  self.disconnect();
837
1004
 
@@ -842,7 +1009,7 @@ class Connection {
842
1009
  }
843
1010
 
844
1011
 
845
- throwClosed(callback) {
1012
+ throwClosed(callback: ((err: Error, ...args: any[]) => void) | undefined) {
846
1013
  var err = new Error('Connection is closed.');
847
1014
  this.db.emit('error', err);
848
1015
  if (callback)
@@ -851,7 +1018,9 @@ class Connection {
851
1018
  }
852
1019
 
853
1020
 
854
- 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) {
855
1024
 
856
1025
  if (typeof(options) === 'function') {
857
1026
  var tmp = options;
@@ -924,7 +1093,7 @@ class Connection {
924
1093
  }*/
925
1094
 
926
1095
  msg.addInt(Const.op_transaction);
927
- msg.addInt(this.dbhandle);
1096
+ msg.addInt(this.dbhandle!);
928
1097
  msg.addBlr(blr);
929
1098
  callback.response = new Transaction(this);
930
1099
 
@@ -933,7 +1102,7 @@ class Connection {
933
1102
  }
934
1103
 
935
1104
 
936
- commit(transaction, callback) {
1105
+ commit(transaction: Transaction, callback: QueueCallback | undefined) {
937
1106
 
938
1107
  if (this._isClosed)
939
1108
  return this.throwClosed(callback);
@@ -950,7 +1119,7 @@ class Connection {
950
1119
  }
951
1120
 
952
1121
 
953
- rollback(transaction, callback) {
1122
+ rollback(transaction: Transaction, callback: QueueCallback | undefined) {
954
1123
 
955
1124
  if (this._isClosed)
956
1125
  return this.throwClosed(callback);
@@ -967,7 +1136,7 @@ class Connection {
967
1136
  }
968
1137
 
969
1138
 
970
- commitRetaining(transaction, callback) {
1139
+ commitRetaining(transaction: Transaction, callback: QueueCallback | undefined) {
971
1140
 
972
1141
  if (this._isClosed)
973
1142
  return this.throwClosed(callback);
@@ -983,7 +1152,7 @@ class Connection {
983
1152
  }
984
1153
 
985
1154
 
986
- rollbackRetaining(transaction, callback) {
1155
+ rollbackRetaining(transaction: Transaction, callback: QueueCallback | undefined) {
987
1156
 
988
1157
  if (this._isClosed)
989
1158
  return this.throwClosed(callback);
@@ -999,7 +1168,7 @@ class Connection {
999
1168
  }
1000
1169
 
1001
1170
 
1002
- allocateStatement(callback) {
1171
+ allocateStatement(callback: QueueCallback) {
1003
1172
 
1004
1173
  if (this._isClosed)
1005
1174
  return this.throwClosed(callback);
@@ -1010,13 +1179,13 @@ class Connection {
1010
1179
  var msg = this._msg;
1011
1180
  msg.pos = 0;
1012
1181
  msg.addInt(Const.op_allocate_statement);
1013
- msg.addInt(this.dbhandle);
1182
+ msg.addInt(this.dbhandle!);
1014
1183
  callback.response = new Statement(this);
1015
1184
  this._queueEvent(callback);
1016
1185
  }
1017
1186
 
1018
1187
 
1019
- dropStatement(statement, callback) {
1188
+ dropStatement(statement: Statement, callback: QueueCallback | undefined) {
1020
1189
 
1021
1190
  if (this._isClosed)
1022
1191
  return this.throwClosed(callback);
@@ -1034,7 +1203,7 @@ class Connection {
1034
1203
  }
1035
1204
 
1036
1205
 
1037
- closeStatement(statement, callback) {
1206
+ closeStatement(statement: Statement, callback: QueueCallback | undefined) {
1038
1207
 
1039
1208
  if (this._isClosed)
1040
1209
  return this.throwClosed(callback);
@@ -1052,16 +1221,15 @@ class Connection {
1052
1221
  }
1053
1222
 
1054
1223
 
1055
- allocateAndPrepareStatement(transaction, query, plan, callback) {
1224
+ allocateAndPrepareStatement(transaction: Transaction, query: string, plan: boolean, callback: Callback<Statement>) {
1056
1225
  var self = this;
1057
- var mainCallback: any = function(err: any, ret: any) {
1226
+ var mainCallback: QueueCallback = function(err: any, ret: any) {
1058
1227
  if (!err) {
1059
1228
  mainCallback.response.handle = ret.handle;
1060
1229
  describe(ret.buffer, mainCallback.response);
1061
1230
  mainCallback.response.query = query;
1062
1231
  self.db.emit('query', query);
1063
1232
  ret = mainCallback.response;
1064
- self._setcachedquery(query, ret);
1065
1233
  }
1066
1234
 
1067
1235
  if (callback)
@@ -1078,7 +1246,7 @@ class Connection {
1078
1246
  blr.pos = 0;
1079
1247
 
1080
1248
  msg.addInt(Const.op_allocate_statement);
1081
- msg.addInt(this.dbhandle);
1249
+ msg.addInt(this.dbhandle!);
1082
1250
  mainCallback.lazy_count = 1;
1083
1251
 
1084
1252
  const describeBytes = this.accept.protocolVersion >= Const.PROTOCOL_VERSION20 ? Const.DESCRIBE_WITH_SCHEMA : Const.DESCRIBE;
@@ -1096,6 +1264,12 @@ class Connection {
1096
1264
  msg.addString(query, Const.DEFAULT_ENCODING);
1097
1265
  msg.addBlr(blr);
1098
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
+ }
1099
1273
  mainCallback.lazy_count += 1;
1100
1274
 
1101
1275
  mainCallback.response = new Statement(this);
@@ -1103,13 +1277,13 @@ class Connection {
1103
1277
  }
1104
1278
 
1105
1279
 
1106
- prepare(transaction, query, plan, callback) {
1280
+ prepare(transaction: Transaction, query: string, plan: boolean, callback: Callback<Statement>) {
1107
1281
  var self = this;
1108
1282
 
1109
1283
  if (this.accept.protocolMinimumType === Const.ptype_lazy_send) { // V11 Statement or higher
1110
1284
  self.allocateAndPrepareStatement(transaction, query, plan, callback);
1111
1285
  } else { // V10 Statement
1112
- self.allocateStatement(function (err, statement) {
1286
+ self.allocateStatement(function (err: any, statement: Statement) {
1113
1287
  if (err) {
1114
1288
  doError(err, callback);
1115
1289
  return;
@@ -1122,7 +1296,8 @@ class Connection {
1122
1296
 
1123
1297
 
1124
1298
 
1125
- 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>) {
1126
1301
 
1127
1302
  if (this._isClosed)
1128
1303
  return this.throwClosed(callback);
@@ -1151,16 +1326,18 @@ class Connection {
1151
1326
  msg.addString(query, Const.DEFAULT_ENCODING);
1152
1327
  msg.addBlr(blr);
1153
1328
  msg.addInt(65535); // buffer_length
1329
+ if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION20) {
1330
+ msg.addInt(0); // p_sqlst_flags (see allocateAndPrepareStatement)
1331
+ }
1154
1332
 
1155
1333
  var self = this;
1156
- this._queueEvent(function(err, ret) {
1334
+ this._queueEvent(function(err: any, ret: any) {
1157
1335
 
1158
1336
  if (!err) {
1159
1337
  describe(ret.buffer, statement);
1160
1338
  statement.query = query;
1161
1339
  self.db.emit('query', query);
1162
1340
  ret = statement;
1163
- self._setcachedquery(query, ret);
1164
1341
  }
1165
1342
 
1166
1343
  if (callback)
@@ -1183,7 +1360,7 @@ class Connection {
1183
1360
  * { recordCount, updateCounts, errors: [{recordNumber, error}],
1184
1361
  * errorRecordNumbers, success }.
1185
1362
  */
1186
- executeBatch(transaction, statement, rows, callback, options) {
1363
+ executeBatch(transaction: Transaction, statement: Statement, rows: QueryParams[], callback: BatchCb | undefined, options?: BatchOptions) {
1187
1364
  options = options || {};
1188
1365
 
1189
1366
  if (this._isClosed)
@@ -1229,7 +1406,7 @@ class Connection {
1229
1406
 
1230
1407
  var self = this;
1231
1408
  var encoders = built.encoders;
1232
- var chunkSize = options.chunkSize > 0 ? options.chunkSize : 500;
1409
+ var chunkSize = options.chunkSize && options.chunkSize > 0 ? options.chunkSize : 500;
1233
1410
  var chunkCount = Math.ceil(rows.length / chunkSize);
1234
1411
 
1235
1412
  var failure: any = null;
@@ -1249,7 +1426,7 @@ class Connection {
1249
1426
  }
1250
1427
 
1251
1428
  var detailed = completion ? completion.detailedErrors : [];
1252
- var errorRecordNumbers = detailed.map(function(e) { return e.recordNumber; })
1429
+ var errorRecordNumbers = detailed.map(function(e: any) { return e.recordNumber; })
1253
1430
  .concat(completion ? completion.errorRecordNumbers : []);
1254
1431
 
1255
1432
  if (callback) callback(undefined, {
@@ -1297,7 +1474,8 @@ class Connection {
1297
1474
  msg.addInt(end - start);
1298
1475
 
1299
1476
  for (var i = start; i < end; i++) {
1300
- var row = rows[i];
1477
+ // validated as an array of input.length values above
1478
+ var row = rows[i] as any[];
1301
1479
 
1302
1480
  var nullBits = new BitSet();
1303
1481
  for (var j = 0; j < input.length; j++) {
@@ -1356,7 +1534,7 @@ class Connection {
1356
1534
  for (var p = 0; p < packets.length; p++) {
1357
1535
  this._pending.push('executeBatch');
1358
1536
  if (p === execIndex) {
1359
- this._queueEventBuffer(packets[p], function(err, ret) {
1537
+ this._queueEventBuffer(packets[p], function(err: any, ret: any) {
1360
1538
  if (!err && ret && ret.batchCompletion) {
1361
1539
  completion = ret.batchCompletion;
1362
1540
  settle();
@@ -1367,13 +1545,14 @@ class Connection {
1367
1545
  } else if (p === packets.length - 1) {
1368
1546
  this._queueEventBuffer(packets[p], function() {});
1369
1547
  } else {
1370
- this._queueEventBuffer(packets[p], function(err) { settle(err); });
1548
+ this._queueEventBuffer(packets[p], function(err: any) { settle(err); });
1371
1549
  }
1372
1550
  }
1373
1551
  }
1374
1552
 
1375
1553
 
1376
- 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) {
1377
1556
 
1378
1557
  if (this._isClosed)
1379
1558
  return this.throwClosed(callback);
@@ -1397,14 +1576,14 @@ class Connection {
1397
1576
  op = Const.op_execute2;
1398
1577
  }
1399
1578
 
1400
- function PrepareParams(params, input, callback) {
1579
+ function PrepareParams(params: any[], input: Xsql.SQLVarBase[], callback: (prms: any[]) => void) {
1401
1580
 
1402
1581
  var value, meta;
1403
1582
  var ret = new Array(params.length);
1404
1583
 
1405
- function putBlobData(index, value, callback) {
1584
+ function putBlobData(index: any, value: any, callback: any) {
1406
1585
 
1407
- self.createBlob2(transaction, function(err, blob) {
1586
+ self.createBlob2(transaction, function(err: any, blob: any) {
1408
1587
 
1409
1588
  var b;
1410
1589
  var isStream = value.readable;
@@ -1432,7 +1611,7 @@ class Connection {
1432
1611
  var isReading = false;
1433
1612
  var isEnd = false;
1434
1613
 
1435
- value.on('data', function(chunk) {
1614
+ value.on('data', function(chunk: any) {
1436
1615
  // Optimization: If chunk is smaller than transfer size, send directly
1437
1616
  if (chunk.length <= chunkSize) {
1438
1617
  self.batchSegments(blob, chunk, function () {
@@ -1473,7 +1652,7 @@ class Connection {
1473
1652
  });
1474
1653
  }
1475
1654
 
1476
- function step(i) {
1655
+ function step(i: any) {
1477
1656
  if (i === params.length) {
1478
1657
  callback(ret);
1479
1658
  return;
@@ -1590,12 +1769,12 @@ class Connection {
1590
1769
 
1591
1770
  if (!(params instanceof Array)) {
1592
1771
  if (params !== undefined && typeof params === 'object' && params !== null) {
1593
- var mappedParams = [];
1772
+ var mappedParams: any[] = [];
1594
1773
  for (var i = 0; i < input.length; i++) {
1595
1774
  mappedParams.push(undefined);
1596
1775
  }
1597
1776
  var matchedCount = 0;
1598
- var nameMap = {};
1777
+ var nameMap: Record<string, number> = {};
1599
1778
  for (var i = 0; i < input.length; i++) {
1600
1779
  var name = input[i].alias || input[i].field;
1601
1780
  if (name) {
@@ -1626,11 +1805,11 @@ class Connection {
1626
1805
 
1627
1806
  if (params.length !== input.length) {
1628
1807
  self._pending.pop();
1629
- 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));
1630
1809
  return;
1631
1810
  }
1632
1811
 
1633
- PrepareParams(params, input, function(prms) {
1812
+ PrepareParams(params, input, function(prms: any) {
1634
1813
  self.sendExecute(op, statement, transaction, callback, prms);
1635
1814
  });
1636
1815
 
@@ -1641,7 +1820,7 @@ class Connection {
1641
1820
  }
1642
1821
 
1643
1822
 
1644
- sendExecute(op: number, statement: any, transaction: any, callback: any, parameters?: any[]) {
1823
+ sendExecute(op: number, statement: Statement, transaction: Transaction, callback: QueueCallback | undefined, parameters?: any[]) {
1645
1824
  var msg = this._msg;
1646
1825
  var blr = this._blr;
1647
1826
  msg.pos = 0;
@@ -1721,14 +1900,15 @@ class Connection {
1721
1900
  msg.addInt(statement.options?.maxInlineBlobSize !== undefined ? statement.options.maxInlineBlobSize : (this.options?.maxInlineBlobSize || 0)); // p_sqldata_inline_blob_size
1722
1901
  }
1723
1902
 
1724
- callback.statement = statement;
1903
+ callback!.statement = statement;
1725
1904
  this._queueEvent(callback);
1726
1905
  }
1727
1906
 
1728
1907
 
1729
1908
 
1730
1909
 
1731
- 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) {
1732
1912
 
1733
1913
  var msg = this._msg;
1734
1914
  var blr = this._blr;
@@ -1748,12 +1928,12 @@ class Connection {
1748
1928
  msg.addInt(0); // message number
1749
1929
  msg.addInt(count || Const.DEFAULT_FETCHSIZE); // fetch count
1750
1930
 
1751
- callback.statement = statement;
1931
+ callback!.statement = statement;
1752
1932
  this._queueEvent(callback);
1753
1933
  }
1754
1934
 
1755
1935
 
1756
- fetchScroll(statement, transaction, direction, offset, count, callback) {
1936
+ fetchScroll(statement: Statement, transaction: Transaction, direction: string | number, offset: any, count: any, callback?: QueueCallback) {
1757
1937
  if (typeof count === 'function') {
1758
1938
  callback = count;
1759
1939
  count = undefined;
@@ -1799,18 +1979,18 @@ class Connection {
1799
1979
  msg.addInt(dirInt); // fetch operation
1800
1980
  msg.addInt(offsetVal); // fetch position (offset)
1801
1981
 
1802
- callback.statement = statement;
1982
+ callback!.statement = statement;
1803
1983
  this._queueEvent(callback);
1804
1984
  }
1805
1985
 
1806
1986
 
1807
- fetchAll(statement, transaction, callback) {
1987
+ fetchAll(statement: Statement, transaction: Transaction, callback: Callback<any[]>) {
1808
1988
  const self = this;
1809
1989
  const custom = statement.options || {};
1810
1990
  const asStream = custom.asStream && custom.on;
1811
- const data = asStream ? null : [];
1991
+ const data: any[] | null = asStream ? null : [];
1812
1992
  let streamIndex = 0;
1813
- const loop = (err, ret) => {
1993
+ const loop = (err: any, ret: any) => {
1814
1994
  if (err) {
1815
1995
  callback(err);
1816
1996
  return;
@@ -1822,24 +2002,26 @@ class Connection {
1822
2002
  // which causes a server-side deadlock when many rows contain
1823
2003
  // BLOBs and blobAsText is true. See issue #387.
1824
2004
  const arrBlobFns = ret.arrBlob || [];
1825
- const readBlobsSequentially = (index, results) => {
2005
+ const readBlobsSequentially = (index: any, results: any) => {
1826
2006
  if (index >= arrBlobFns.length) {
1827
2007
  return Promise.resolve(results);
1828
2008
  }
1829
- return arrBlobFns[index](transaction).then((v) => {
2009
+ return arrBlobFns[index](transaction).then((v: any) => {
1830
2010
  results.push(v);
1831
2011
  return readBlobsSequentially(index + 1, results);
1832
2012
  });
1833
2013
  };
1834
2014
 
1835
- readBlobsSequentially(0, []).then((arrBlob) => {
2015
+ readBlobsSequentially(0, []).then((arrBlob: any) => {
1836
2016
  for (let i = 0; i < arrBlob.length; i++) {
1837
2017
  const blob = arrBlob[i];
1838
- ret.data[blob.row][blob.column] = parseValueIfJson(blob.value, statement.connection.options);
2018
+ ret.data[blob.row][blob.column] = applyTypeCast(
2019
+ statement.connection.options, blob.meta || {},
2020
+ parseValueIfJson(blob.value, statement.connection.options));
1839
2021
  }
1840
2022
 
1841
2023
  doSynchronousLoop(ret.data, (row, _i, next) => {
1842
- const pos = asStream ? streamIndex++ : (data.push(row) - 1);
2024
+ const pos = asStream ? streamIndex++ : (data!.push(row) - 1);
1843
2025
  if (asStream) {
1844
2026
  executeStreamRow(custom, row, pos, statement.output, next);
1845
2027
  } else {
@@ -1873,7 +2055,7 @@ class Connection {
1873
2055
 
1874
2056
 
1875
2057
 
1876
- openBlob(blob, transaction, callback) {
2058
+ openBlob(blob: Quad, transaction: Transaction, callback: QueueCallback) {
1877
2059
  var msg = this._msg;
1878
2060
  msg.pos = 0;
1879
2061
  msg.addInt(Const.op_open_blob);
@@ -1883,7 +2065,7 @@ class Connection {
1883
2065
  }
1884
2066
 
1885
2067
 
1886
- closeBlob(blob, callback, defer = true) {
2068
+ closeBlob(blob: any, callback?: QueueCallback, defer = true) {
1887
2069
  var msg = this._msg;
1888
2070
  msg.pos = 0;
1889
2071
  msg.addInt(Const.op_close_blob);
@@ -1892,7 +2074,7 @@ class Connection {
1892
2074
  }
1893
2075
 
1894
2076
 
1895
- getSegment(blob, callback) {
2077
+ getSegment(blob: any, callback: QueueCallback) {
1896
2078
  var msg = this._msg;
1897
2079
  msg.pos = 0;
1898
2080
  msg.addInt(Const.op_get_segment);
@@ -1903,7 +2085,7 @@ class Connection {
1903
2085
  }
1904
2086
 
1905
2087
 
1906
- createBlob2(transaction, callback) {
2088
+ createBlob2(transaction: Transaction, callback: QueueCallback) {
1907
2089
  var msg = this._msg;
1908
2090
  msg.pos = 0;
1909
2091
  msg.addInt(Const.op_create_blob2);
@@ -1915,7 +2097,7 @@ class Connection {
1915
2097
  }
1916
2098
 
1917
2099
 
1918
- batchSegments(blob, buffer, callback) {
2100
+ batchSegments(blob: any, buffer: Buffer, callback: QueueCallback) {
1919
2101
  var msg = this._msg;
1920
2102
  var blr = this._blr;
1921
2103
  msg.pos = 0;
@@ -1929,7 +2111,7 @@ class Connection {
1929
2111
  }
1930
2112
 
1931
2113
 
1932
- svcattach(options: any, callback?: any, svc?: any) {
2114
+ svcattach(options: InternalOptions, callback?: Callback<ServiceManager>, svc?: ServiceManager) {
1933
2115
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
1934
2116
  var database = options.database || options.filename;
1935
2117
  var user = options.user || Const.DEFAULT_USER;
@@ -1964,7 +2146,7 @@ class Connection {
1964
2146
 
1965
2147
  var self = this;
1966
2148
 
1967
- function cb(err, ret) {
2149
+ function cb(err: any, ret: any) {
1968
2150
 
1969
2151
  if (err) {
1970
2152
  doError(err, callback);
@@ -1990,19 +2172,19 @@ class Connection {
1990
2172
  }
1991
2173
 
1992
2174
 
1993
- svcstart(spbaction, callback) {
2175
+ svcstart(spbaction: BlrWriter, callback: QueueCallback | undefined) {
1994
2176
  var msg = this._msg;
1995
2177
  var blr = this._blr;
1996
2178
  msg.pos = 0;
1997
2179
  msg.addInt(Const.op_service_start);
1998
- msg.addInt(this.svchandle);
2180
+ msg.addInt(this.svchandle!);
1999
2181
  msg.addInt(0)
2000
2182
  msg.addBlr(spbaction);
2001
2183
  this._queueEvent(callback);
2002
2184
  }
2003
2185
 
2004
2186
 
2005
- svcquery(spbquery, resultbuffersize, timeout,callback) {
2187
+ svcquery(spbquery: number[], resultbuffersize: number, timeout: number | undefined, callback: QueueCallback | undefined) {
2006
2188
  if (resultbuffersize > Const.MAX_BUFFER_SIZE) {
2007
2189
  doError(new Error('Buffer is too big'), callback);
2008
2190
  return;
@@ -2015,7 +2197,7 @@ class Connection {
2015
2197
  blr.addByte(Const.isc_spb_current_version);
2016
2198
  //blr.addByteInt32(Const.isc_info_svc_timeout, timeout);
2017
2199
  msg.addInt(Const.op_service_info);
2018
- msg.addInt(this.svchandle);
2200
+ msg.addInt(this.svchandle!);
2019
2201
  msg.addInt(0);
2020
2202
  msg.addBlr(blr);
2021
2203
  blr.pos = 0
@@ -2026,7 +2208,7 @@ class Connection {
2026
2208
  }
2027
2209
 
2028
2210
 
2029
- svcdetach(callback) {
2211
+ svcdetach(callback: Callback | undefined) {
2030
2212
  var self = this;
2031
2213
 
2032
2214
  if (self._isClosed) {
@@ -2041,9 +2223,9 @@ class Connection {
2041
2223
 
2042
2224
  msg.pos = 0;
2043
2225
  msg.addInt(Const.op_service_detach);
2044
- msg.addInt(this.svchandle); // Database Object ID
2226
+ msg.addInt(this.svchandle!); // Database Object ID
2045
2227
 
2046
- self._queueEvent(function (err, ret) {
2228
+ self._queueEvent(function (err: any, ret: any) {
2047
2229
  delete (self.svchandle);
2048
2230
  if (callback)
2049
2231
  callback(err, ret);
@@ -2052,7 +2234,7 @@ class Connection {
2052
2234
 
2053
2235
 
2054
2236
 
2055
- auxConnection(eventid, callback) {
2237
+ auxConnection(eventid: number | Callback, callback?: Callback) {
2056
2238
  if (typeof eventid === 'function') {
2057
2239
  // Preserve the older auxConnection(callback) call shape; plain
2058
2240
  // auxiliary connections historically used event id 0.
@@ -2066,13 +2248,13 @@ class Connection {
2066
2248
  msg.pos = 0;
2067
2249
  msg.addInt(Const.op_connect_request);
2068
2250
  msg.addInt(1); // async
2069
- msg.addInt(self.dbhandle);
2251
+ msg.addInt(self.dbhandle!);
2070
2252
  msg.addInt(eventid);
2071
2253
  if (process.env.FIREBIRD_DEBUG) {
2072
2254
  console.log('[fb-debug] auxConnection: sending op_connect_request(53) dbhandle=%d eventid=%d queue_before=%d xdr_saved=%s',
2073
2255
  self.dbhandle, eventid, self._queue.length, Boolean(self._xdr));
2074
2256
  }
2075
- function cb(err, ret) {
2257
+ function cb(err: any, ret: any) {
2076
2258
 
2077
2259
  if (err) {
2078
2260
  if (process.env.FIREBIRD_DEBUG) {
@@ -2093,13 +2275,13 @@ class Connection {
2093
2275
  socket_info.family, socket_info.port, socket_info.host, self._queue.length);
2094
2276
  }
2095
2277
 
2096
- callback(undefined, socket_info);
2278
+ callback!(undefined, socket_info);
2097
2279
  }
2098
2280
  this._queueEvent(cb);
2099
2281
  }
2100
2282
 
2101
2283
 
2102
- queEvents(events, eventid, callback) {
2284
+ queEvents(events: Record<string, number>, eventid: number, callback: Callback) {
2103
2285
  var self = this;
2104
2286
  if (this._isClosed)
2105
2287
  return this.throwClosed(callback);
@@ -2108,7 +2290,7 @@ class Connection {
2108
2290
  blr.pos = 0;
2109
2291
  msg.pos = 0;
2110
2292
  msg.addInt(Const.op_que_events);
2111
- msg.addInt(this.dbhandle);
2293
+ msg.addInt(this.dbhandle!);
2112
2294
  // prepare EPB
2113
2295
  blr.addByte(1) // epb_version
2114
2296
  for (var event in events) {
@@ -2122,7 +2304,7 @@ class Connection {
2122
2304
  msg.addInt(0); // args
2123
2305
  msg.addInt(eventid);
2124
2306
 
2125
- function cb(err, ret) {
2307
+ function cb(err: any, ret: any) {
2126
2308
  if (err) {
2127
2309
  doError(err, callback);
2128
2310
  return;
@@ -2135,17 +2317,17 @@ class Connection {
2135
2317
  }
2136
2318
 
2137
2319
 
2138
- closeEvents(eventid, callback) {
2320
+ closeEvents(eventid: number, callback: Callback) {
2139
2321
  var self = this;
2140
2322
  if (this._isClosed)
2141
2323
  return this.throwClosed(callback);
2142
2324
  var msg = self._msg;
2143
2325
  msg.pos = 0;
2144
2326
  msg.addInt(Const.op_cancel_events);
2145
- msg.addInt(self.dbhandle);
2327
+ msg.addInt(self.dbhandle!);
2146
2328
  msg.addInt(eventid);
2147
2329
 
2148
- function cb(err, ret) {
2330
+ function cb(err: any, ret: any) {
2149
2331
  if (err) {
2150
2332
  doError(err, callback);
2151
2333
  return;
@@ -2164,7 +2346,7 @@ const opcodeNames = Object.fromEntries(
2164
2346
  Object.entries(Const).filter(([k]) => k.startsWith('op_')).map(([k, v]) => [v, k])
2165
2347
  );
2166
2348
 
2167
- function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any, cb: (err?: any, obj?: any) => void) {
2349
+ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cnx: Connection, lowercase_keys: boolean | undefined, cb: (err?: any, obj?: any) => void) {
2168
2350
  try {
2169
2351
  do {
2170
2352
  var r = data.r || data.readInt();
@@ -2185,7 +2367,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2185
2367
  cnx._inlineBlobs = new Map();
2186
2368
  }
2187
2369
  const cacheKey = `${blob_id.high}:${blob_id.low}`;
2188
- cnx._inlineBlobs.set(cacheKey, blob_data);
2370
+ cnx._inlineBlobs.set(cacheKey, blob_data!);
2189
2371
  r = Const.op_dummy; // Continue loop to read next opcode
2190
2372
  }
2191
2373
  } while (r === Const.op_dummy);
@@ -2195,7 +2377,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2195
2377
  r, opcodeNames[r] || 'unknown', data.pos, data.buffer.length);
2196
2378
  }
2197
2379
 
2198
- var item, op, response;
2380
+ var item, op, response: any;
2199
2381
 
2200
2382
  switch (r) {
2201
2383
  case Const.op_response:
@@ -2206,7 +2388,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2206
2388
  response = {};
2207
2389
  }
2208
2390
 
2209
- let loop = function (err) {
2391
+ let loop = function (err: any) {
2210
2392
  if (err) {
2211
2393
  return cb(err);
2212
2394
  } else {
@@ -2267,13 +2449,27 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2267
2449
  }
2268
2450
  case Const.op_fetch_response:
2269
2451
  case Const.op_sql_response:
2270
- var statement = callback.statement;
2452
+ // fetch/sql_response entries always carry their statement
2453
+ var statement = callback!.statement!;
2271
2454
  var output = statement.output;
2272
2455
  var custom = statement.options || {};
2273
2456
  var isOpFetch = r === Const.op_fetch_response;
2274
2457
  var _xdrpos;
2275
2458
  statement.nbrowsfetched = statement.nbrowsfetched || 0;
2276
2459
 
2460
+ // The f* decode state is only meaningful within a single
2461
+ // decode call: incomplete packets are re-decoded from scratch
2462
+ // on a fresh XdrReader (see the 'data' handler). State left by
2463
+ // an earlier packet in the same data event (e.g. fstatus=100 /
2464
+ // fcount=0 from a completed fetch) would make this decode
2465
+ // consume just the opcode and desync every later response.
2466
+ delete data.fstatus;
2467
+ delete data.fcount;
2468
+ delete data.fcolumn;
2469
+ delete data.frow;
2470
+ delete data.frows;
2471
+ delete data.fcols;
2472
+
2277
2473
  if (isOpFetch && data.fop) { // could be set when a packet is not complete
2278
2474
  data.readBuffer(68); // ??
2279
2475
  op = data.readInt(); // ??
@@ -2295,55 +2491,67 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2295
2491
 
2296
2492
  if (custom.asObject && !data.fcols) {
2297
2493
  if (lowercase_keys) {
2298
- data.fcols = output.map((column) => column.alias.toLowerCase());
2494
+ data.fcols = output.map((column: any) => column.alias.toLowerCase());
2299
2495
  } else {
2300
- data.fcols = output.map((column) => column.alias);
2496
+ data.fcols = output.map((column: any) => column.alias);
2301
2497
  }
2302
2498
  }
2303
2499
 
2304
- const arrBlob = [];
2500
+ const arrBlob: any[] = [];
2305
2501
  const lowerV13 = statement.connection.accept.protocolVersion < Const.PROTOCOL_VERSION13;
2306
2502
 
2503
+ // op_sql_response (op_execute2) is always followed by an
2504
+ // op_response carrying the execute status vector. The row loop
2505
+ // below consumes it after the last row, but with zero rows
2506
+ // (e.g. INSERT ... RETURNING failing on a constraint) it stays
2507
+ // in the buffer, shifting every later response to the wrong
2508
+ // callback and poisoning the connection (issue #341).
2509
+ var sqlResponseTrailerPending = !isOpFetch && !data.fcount;
2510
+
2307
2511
  while (data.fcount && (data.fstatus !== 100)) {
2308
2512
  let nullBitSet;
2309
2513
  if (!lowerV13) {
2310
2514
  const nullBitsLen = Math.floor((output.length + 7) / 8);
2311
- nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false));
2515
+ nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false)!);
2312
2516
  data.readBuffer((4 - nullBitsLen) & 3, false); // Skip padding
2313
2517
  }
2314
2518
 
2315
2519
  for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
2316
2520
  item = output[data.fcolumn];
2317
2521
 
2318
- if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
2319
- if (custom.asObject) {
2320
- data.frow[data.fcols[data.fcolumn]] = null;
2321
- } else {
2322
- data.frow[data.fcolumn] = null;
2323
- }
2522
+ if (!lowerV13 && nullBitSet!.get(data.fcolumn)) {
2523
+ const nullKey = custom.asObject ? data.fcols![data.fcolumn!] : data.fcolumn;
2524
+ data.frow[nullKey] = applyTypeCast(cnx.options, item, null);
2324
2525
 
2325
2526
  continue;
2326
2527
  }
2327
2528
 
2328
2529
  try {
2329
2530
  _xdrpos = data.pos;
2330
- const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
2531
+ const key = custom.asObject ? data.fcols![data.fcolumn!] : data.fcolumn;
2331
2532
  const row = data.frows.length;
2332
2533
  let value = item.decode(data, lowerV13, cnx.options);
2534
+ // text blobs resolved by blobAsText run through the
2535
+ // typeCast hook once the text arrives (see fetchAll),
2536
+ // not here where the value is still a pending fetch
2537
+ let pendingTextBlob = false;
2333
2538
 
2334
2539
  if (item.type === Const.SQL_BLOB && value !== null) {
2335
2540
  if (item.subType === Const.isc_blob_text && cnx.options.blobAsText) {
2336
- value = fetch_blob_async_transaction(statement, value, key, row);
2541
+ value = fetch_blob_async_transaction(statement, value, key, row, item);
2337
2542
  arrBlob.push(value);
2543
+ pendingTextBlob = true;
2338
2544
  } else {
2339
2545
  value = fetch_blob_async(statement, value, key, row);
2340
2546
  }
2341
2547
  }
2342
2548
 
2343
- data.frow[key] = parseValueIfJson(value, cnx.options);
2549
+ data.frow[key] = pendingTextBlob
2550
+ ? value
2551
+ : applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
2344
2552
  } catch (e) {
2345
2553
  // uncomplete packet read
2346
- data.pos = _xdrpos;
2554
+ data.pos = _xdrpos!;
2347
2555
  data.r = r;
2348
2556
  return cb(new Error('Packet is not complete'));
2349
2557
  }
@@ -2388,6 +2596,17 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2388
2596
  statement.nbrowsfetched++;
2389
2597
  }
2390
2598
 
2599
+ if (sqlResponseTrailerPending) {
2600
+ op = data.readInt();
2601
+ if (op === Const.op_response) {
2602
+ response = {};
2603
+ parseOpResponse(data, response);
2604
+ if (response.status) {
2605
+ return cb(null, response);
2606
+ }
2607
+ }
2608
+ }
2609
+
2391
2610
  // ToDo: emit "result" with blob subtype string decoded
2392
2611
  statement.connection.db.emit('result', data.frows, arrBlob);
2393
2612
  return cb(null, {data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob});
@@ -2412,7 +2631,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2412
2631
  }
2413
2632
 
2414
2633
  if (r === Const.op_cond_accept || r === Const.op_accept_data) {
2415
- var d = new BlrReader(data.readArray());
2634
+ var d = new BlrReader(data.readArray()!);
2416
2635
  accept.pluginName = data.readString(Const.DEFAULT_ENCODING);
2417
2636
  var is_authenticated = data.readInt();
2418
2637
  var keys = data.readString(Const.DEFAULT_ENCODING); // keys
@@ -2432,7 +2651,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2432
2651
  }
2433
2652
 
2434
2653
  if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
2435
- var crypto = {
2654
+ var crypto: Record<string, string> = {
2436
2655
  Srp: 'sha1',
2437
2656
  Srp256: 'sha256',
2438
2657
  Srp384: 'sha384',
@@ -2443,7 +2662,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2443
2662
  if (!d.buffer) {
2444
2663
  cnx._pendingAccept = accept;
2445
2664
  cnx.sendOpContAuth(
2446
- cnx.clientKeys.public.toString(16),
2665
+ cnx.clientKeys!.public.toString(16),
2447
2666
  Const.DEFAULT_ENCODING,
2448
2667
  accept.pluginName
2449
2668
  );
@@ -2474,20 +2693,20 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2474
2693
 
2475
2694
  if (process.env.FIREBIRD_DEBUG) {
2476
2695
  console.log('--- DEBUG SRP Handshake ---');
2477
- console.log('salt:', cnx.serverKeys.salt);
2478
- console.log('server public key:', cnx.serverKeys.public.toString(16));
2479
- console.log('client public key:', cnx.clientKeys.public.toString(16));
2696
+ console.log('salt:', cnx.serverKeys!.salt);
2697
+ console.log('server public key:', cnx.serverKeys!.public.toString(16));
2698
+ console.log('client public key:', cnx.clientKeys!.public.toString(16));
2480
2699
  console.log('hashAlgo:', accept.srpAlgo);
2481
2700
  }
2482
2701
 
2483
2702
  const _t1 = Date.now();
2484
2703
  var proof = srp.clientProof(
2485
- cnx.options.user.toUpperCase(),
2486
- cnx.options.password,
2487
- cnx.serverKeys.salt,
2488
- cnx.clientKeys.public,
2489
- cnx.serverKeys.public,
2490
- cnx.clientKeys.private,
2704
+ cnx.options.user!.toUpperCase(),
2705
+ cnx.options.password!,
2706
+ cnx.serverKeys!.salt,
2707
+ cnx.clientKeys!.public,
2708
+ cnx.serverKeys!.public,
2709
+ cnx.clientKeys!.private,
2491
2710
  accept.srpAlgo
2492
2711
  );
2493
2712
 
@@ -2504,7 +2723,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2504
2723
  accept.authData = proof.authData.toString(16);
2505
2724
  accept.sessionKey = proof.clientSessionKey;
2506
2725
  } else if (accept.pluginName === Const.AUTH_PLUGIN_LEGACY) {
2507
- accept.authData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
2726
+ accept.authData = crypt.crypt(cnx.options.password!, Const.LEGACY_AUTH_SALT).substring(2);
2508
2727
  } else {
2509
2728
  return cb(new Error('Unknow auth plugin : ' + accept.pluginName));
2510
2729
  }
@@ -2542,7 +2761,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2542
2761
 
2543
2762
  return cb(undefined, accept);
2544
2763
  case Const.op_cont_auth:
2545
- var d = new BlrReader(data.readArray());
2764
+ var d = new BlrReader(data.readArray()!);
2546
2765
  var pluginName = data.readString(Const.DEFAULT_ENCODING);
2547
2766
  data.readString(Const.DEFAULT_ENCODING); // plist
2548
2767
  data.readString(Const.DEFAULT_ENCODING); // pkey
@@ -2569,7 +2788,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2569
2788
  // the proof with the new plugin's hash algorithm - rather than
2570
2789
  // as the server's M2 proof, otherwise the client silently waits
2571
2790
  // forever for an op_accept the server will never send (#254).
2572
- if (!cnx.serverKeys || cnx.serverKeys.pluginName !== pluginName) {
2791
+ if (!cnx.serverKeys || cnx.serverKeys!.pluginName !== pluginName) {
2573
2792
  // Check buffer contains salt
2574
2793
  var saltLen = d.buffer.readUInt16LE(0);
2575
2794
  if (saltLen > 32 * 2) {
@@ -2592,7 +2811,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2592
2811
  pluginName: pluginName
2593
2812
  };
2594
2813
 
2595
- var crypto = {
2814
+ var crypto: Record<string, string> = {
2596
2815
  Srp: 'sha1',
2597
2816
  Srp256: 'sha256',
2598
2817
  Srp384: 'sha384',
@@ -2602,20 +2821,20 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2602
2821
 
2603
2822
  if (process.env.FIREBIRD_DEBUG) {
2604
2823
  console.log('--- DEBUG SRP Handshake ---');
2605
- console.log('salt:', cnx.serverKeys.salt);
2606
- console.log('server public key:', cnx.serverKeys.public.toString(16));
2607
- console.log('client public key:', cnx.clientKeys.public.toString(16));
2824
+ console.log('salt:', cnx.serverKeys!.salt);
2825
+ console.log('server public key:', cnx.serverKeys!.public.toString(16));
2826
+ console.log('client public key:', cnx.clientKeys!.public.toString(16));
2608
2827
  console.log('hashAlgo:', srpAlgo);
2609
2828
  }
2610
2829
 
2611
2830
  const _t1 = Date.now();
2612
2831
  var proof = srp.clientProof(
2613
- cnx.options.user.toUpperCase(),
2614
- cnx.options.password,
2615
- cnx.serverKeys.salt,
2616
- cnx.clientKeys.public,
2617
- cnx.serverKeys.public,
2618
- cnx.clientKeys.private,
2832
+ cnx.options.user!.toUpperCase(),
2833
+ cnx.options.password!,
2834
+ cnx.serverKeys!.salt,
2835
+ cnx.clientKeys!.public,
2836
+ cnx.serverKeys!.public,
2837
+ cnx.clientKeys!.private,
2619
2838
  srpAlgo
2620
2839
  );
2621
2840
 
@@ -2652,7 +2871,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2652
2871
  cnx._pendingAccept.protocolVersion,
2653
2872
  cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
2654
2873
  }
2655
- var legacyAuthData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
2874
+ var legacyAuthData = crypt.crypt(cnx.options.password!, Const.LEGACY_AUTH_SALT).substring(2);
2656
2875
  cnx.sendOpContAuth(legacyAuthData, Const.DEFAULT_ENCODING, pluginName);
2657
2876
  return; // wait for op_accept
2658
2877
  }
@@ -2665,7 +2884,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2665
2884
 
2666
2885
  if (pluginName === Const.AUTH_PLUGIN_LEGACY) { // Fallback to LegacyAuth
2667
2886
  cnx.accept.pluginName = pluginName;
2668
- cnx.accept.authData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
2887
+ cnx.accept.authData = crypt.crypt(cnx.options.password!, Const.LEGACY_AUTH_SALT).substring(2);
2669
2888
 
2670
2889
  cnx.sendOpContAuth(
2671
2890
  cnx.accept.authData,
@@ -2758,7 +2977,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2758
2977
  }
2759
2978
  return cb(new Error('Unexpected:' + r));
2760
2979
  }
2761
- } catch (err) {
2980
+ } catch (err: any) {
2762
2981
  if (process.env.FIREBIRD_DEBUG) {
2763
2982
  console.warn('[fb-debug] decodeResponse exception: %s (RangeError=%s) pos=%d buflen=%d',
2764
2983
  err.message, err instanceof RangeError, data.pos, data.buffer.length);
@@ -2774,8 +2993,8 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2774
2993
  * Read one XDR status vector (as in op_response / op_batch_cs error
2775
2994
  * vectors): a stream of isc_arg_* items terminated by isc_arg_end.
2776
2995
  */
2777
- function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2778
- var result: { status: any[]; sqlcode?: number } = { status: [] };
2996
+ function readStatusVector(data: XdrReader): { status: any[]; warnings?: any[]; sqlcode?: number } {
2997
+ var result: { status: any[]; warnings?: any[]; sqlcode?: number } = { status: [] };
2779
2998
  var item: any = {};
2780
2999
 
2781
3000
  while (true) {
@@ -2805,13 +3024,24 @@ function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2805
3024
  result.sqlcode = n;
2806
3025
  }
2807
3026
  break;
3027
+ case Const.isc_arg_warning:
3028
+ // A warning attached to a SUCCESS vector (e.g. "parallel
3029
+ // workers value capped"). Keep it out of `status` so the
3030
+ // operation is not mistaken for a failure; later string/
3031
+ // number items attach to the warning entry.
3032
+ var wnum = data.readInt();
3033
+ item = { gdscode: wnum };
3034
+ if (wnum) {
3035
+ (result.warnings = result.warnings || []).push(item);
3036
+ }
3037
+ break;
2808
3038
  default:
2809
3039
  throw new Error('Unexpected status vector item: ' + op);
2810
3040
  }
2811
3041
  }
2812
3042
  }
2813
3043
 
2814
- function parseOpResponse(data: any, response: any, cb?: (err?: any, response?: any) => void) {
3044
+ function parseOpResponse(data: XdrReader, response: WireResponse, cb?: (err?: any, response?: any) => void) {
2815
3045
  var handle = data.readInt();
2816
3046
 
2817
3047
  if (!response.handle) {
@@ -2874,18 +3104,31 @@ function parseOpResponse(data: any, response: any, cb?: (err?: any, response?: a
2874
3104
  response.sqlcode = num;
2875
3105
  }
2876
3106
 
3107
+ break;
3108
+ case Const.isc_arg_warning:
3109
+ // A warning attached to a SUCCESS response (e.g. Firebird's
3110
+ // "parallel workers value capped" on attach). Keep it out of
3111
+ // `status` so the response is not mistaken for an error;
3112
+ // later string/number items attach to the warning entry.
3113
+ num = data.readInt();
3114
+ item = { gdscode: num };
3115
+ if (num) {
3116
+ (response.warnings = response.warnings || []).push(item);
3117
+ }
2877
3118
  break;
2878
3119
  default:
3120
+ // Stop parsing: continuing the loop after an unknown item
3121
+ // re-read the same bytes forever (the caller resets the
3122
+ // reader position when the error is delivered).
2879
3123
  if (cb) {
2880
- cb(new Error('Unexpected: ' + op))
2881
- } else {
2882
- throw new Error('Unexpected: ' + op);
3124
+ return cb(new Error('Unexpected: ' + op));
2883
3125
  }
3126
+ throw new Error('Unexpected: ' + op);
2884
3127
  }
2885
3128
  }
2886
3129
  }
2887
3130
 
2888
- function describe(buff: Buffer, statement: any) {
3131
+ function describe(buff: Buffer, statement: Statement) {
2889
3132
  var br = new BlrReader(buff);
2890
3133
  var parameters: any = null;
2891
3134
  var type: any, param: any;
@@ -2893,7 +3136,7 @@ function describe(buff: Buffer, statement: any) {
2893
3136
  while (br.pos < br.buffer.length) {
2894
3137
  switch (br.readByteCode()) {
2895
3138
  case Const.isc_info_sql_stmt_type:
2896
- statement.type = br.readInt();
3139
+ statement.type = br.readInt()!;
2897
3140
  break;
2898
3141
  case Const.isc_info_sql_get_plan:
2899
3142
  statement.plan = br.readString(Const.DEFAULT_ENCODING);
@@ -2917,7 +3160,9 @@ function describe(buff: Buffer, statement: any) {
2917
3160
  case Const.isc_info_sql_describe_end:
2918
3161
  break;
2919
3162
  case Const.isc_info_sql_sqlda_seq:
2920
- var num = br.readInt();
3163
+ // describe output always encodes the sequence as a
3164
+ // 1/2/4-byte int, so readInt cannot return undefined
3165
+ var num = br.readInt()!;
2921
3166
  break;
2922
3167
  case Const.isc_info_sql_type:
2923
3168
  type = br.readInt();
@@ -2948,7 +3193,9 @@ function describe(buff: Buffer, statement: any) {
2948
3193
  default:
2949
3194
  throw new Error('Unexpected');
2950
3195
  }
2951
- parameters[num-1] = param;
3196
+ // isc_info_sql_sqlda_seq always precedes the type
3197
+ // item in the describe stream, so num is set here
3198
+ parameters[num!-1] = param;
2952
3199
  param.type = type;
2953
3200
  param.nullable = Boolean(param.type & 1);
2954
3201
  param.type &= ~1;
@@ -3228,10 +3475,10 @@ function CalcBlr(blr: BlrWriter, xsqlda: any[]) {
3228
3475
  blr.addByte(Const.blr_eoc);
3229
3476
  }
3230
3477
 
3231
- function fetch_blob_async_transaction(statement: any, id: any, column: any, row: any) {
3232
- const infoValue = { row, column, value: '' };
3478
+ function fetch_blob_async_transaction(statement: Statement, id: Quad, column: string | number, row: number, meta?: Xsql.SQLVarBase) {
3479
+ const infoValue = { row, column, value: '', meta };
3233
3480
 
3234
- return (transactionArg) => {
3481
+ return (transactionArg: any) => {
3235
3482
  const cacheKey = `${id.high}:${id.low}`;
3236
3483
  if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
3237
3484
  const data = statement.connection._inlineBlobs.get(cacheKey);
@@ -3241,10 +3488,10 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3241
3488
 
3242
3489
  const singleTransaction = transactionArg === undefined;
3243
3490
 
3244
- let promiseTransaction;
3491
+ let promiseTransaction: Promise<Transaction>;
3245
3492
  if (singleTransaction) {
3246
3493
  promiseTransaction = new Promise((resolve, reject) => {
3247
- statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
3494
+ statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err: any, transaction: Transaction) => {
3248
3495
  if (err) {
3249
3496
  return reject(err);
3250
3497
  }
@@ -3258,7 +3505,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3258
3505
  return promiseTransaction.then((transaction) => {
3259
3506
  return new Promise((resolve, reject) => {
3260
3507
  statement.connection._pending.push('openBlob');
3261
- statement.connection.openBlob(id, transaction, (err, blob) => {
3508
+ statement.connection.openBlob(id, transaction, (err: any, blob: any) => {
3262
3509
 
3263
3510
  if (err) {
3264
3511
  reject(err);
@@ -3266,7 +3513,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3266
3513
  }
3267
3514
 
3268
3515
  const read = () => {
3269
- statement.connection.getSegment(blob, (err, ret) => {
3516
+ statement.connection.getSegment(blob, (err: any, ret: any) => {
3270
3517
 
3271
3518
  if (err) {
3272
3519
  if (singleTransaction) {
@@ -3290,7 +3537,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3290
3537
 
3291
3538
  statement.connection.closeBlob(blob);
3292
3539
  if (singleTransaction) {
3293
- transaction.commit((err) => {
3540
+ transaction.commit((err: any) => {
3294
3541
  if (err) {
3295
3542
  reject(err);
3296
3543
  } else {
@@ -3310,14 +3557,14 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3310
3557
  };
3311
3558
  }
3312
3559
 
3313
- function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3314
- const cbTransaction = (transaction, close, callback) => {
3560
+ function fetch_blob_async(statement: Statement, id: Quad, name: string | number, row: number) {
3561
+ const cbTransaction = (transaction: Transaction, close: any, callback: any) => {
3315
3562
  statement.connection._pending.push('openBlob');
3316
- statement.connection.openBlob(id, transaction, (err, blob) => {
3563
+ statement.connection.openBlob(id, transaction, (err: any, blob: any) => {
3317
3564
  let e: any = new Events.EventEmitter();
3318
3565
 
3319
- e.pipe = (stream) => {
3320
- e.on('data', (chunk) => {
3566
+ e.pipe = (stream: any) => {
3567
+ e.on('data', (chunk: any) => {
3321
3568
  stream.write(chunk);
3322
3569
  });
3323
3570
  e.on('end', () => {
@@ -3330,7 +3577,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3330
3577
  }
3331
3578
 
3332
3579
  const read = () => {
3333
- statement.connection.getSegment(blob, (err, ret) => {
3580
+ statement.connection.getSegment(blob, (err: any, ret: any) => {
3334
3581
 
3335
3582
  if (err) {
3336
3583
  transaction.rollback(() => {
@@ -3353,7 +3600,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3353
3600
 
3354
3601
  statement.connection.closeBlob(blob);
3355
3602
  if (close) {
3356
- transaction.commit((err) => {
3603
+ transaction.commit((err: any) => {
3357
3604
  if (err) {
3358
3605
  e.emit('error', err);
3359
3606
  } else {
@@ -3373,7 +3620,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3373
3620
  });
3374
3621
  };
3375
3622
 
3376
- return (transaction, callback) => {
3623
+ return (transaction: Transaction, callback: any) => {
3377
3624
  // callback(error, nameField, eventEmitter, row)
3378
3625
  const singleTransaction = callback === undefined;
3379
3626
  const actualCallback = singleTransaction ? transaction : callback;
@@ -3382,8 +3629,8 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3382
3629
  if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
3383
3630
  const data = statement.connection._inlineBlobs.get(cacheKey);
3384
3631
  let e: any = new Events.EventEmitter();
3385
- e.pipe = (stream) => {
3386
- e.on('data', (chunk) => {
3632
+ e.pipe = (stream: any) => {
3633
+ e.on('data', (chunk: any) => {
3387
3634
  stream.write(chunk);
3388
3635
  });
3389
3636
  e.on('end', () => {
@@ -3404,7 +3651,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3404
3651
 
3405
3652
  if (singleTransaction) {
3406
3653
  callback = transaction;
3407
- statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
3654
+ statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err: any, transaction: Transaction) => {
3408
3655
  if (err) {
3409
3656
  callback(err);
3410
3657
  return;
@@ -3423,7 +3670,7 @@ function doSynchronousLoop(data: any[], processData: (row: any, index: number, n
3423
3670
  return;
3424
3671
  }
3425
3672
 
3426
- const loop = (index) => {
3673
+ const loop = (index: any) => {
3427
3674
  processData(data[index], index, (err) => {
3428
3675
  if (err) {
3429
3676
  done(err);