node-firebird 2.9.0 → 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 (49) hide show
  1. package/README.md +166 -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 +143 -5
  7. package/lib/uri.js +2 -2
  8. package/lib/wire/connection.d.ts +92 -59
  9. package/lib/wire/connection.js +267 -53
  10. package/lib/wire/const.d.ts +9 -1
  11. package/lib/wire/const.js +21 -9
  12. package/lib/wire/database.d.ts +51 -26
  13. package/lib/wire/database.js +26 -8
  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 +18 -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 +18 -18
  30. package/package.json +1 -1
  31. package/src/index.ts +36 -4
  32. package/src/messages.ts +1 -1
  33. package/src/pool.ts +1 -1
  34. package/src/srp.ts +6 -6
  35. package/src/types.ts +140 -5
  36. package/src/unix-crypt.ts +9 -9
  37. package/src/uri.ts +2 -2
  38. package/src/wire/connection.ts +464 -232
  39. package/src/wire/const.ts +21 -9
  40. package/src/wire/database.ts +75 -43
  41. package/src/wire/eventConnection.ts +8 -5
  42. package/src/wire/query-stream.ts +80 -0
  43. package/src/wire/serialize.ts +29 -0
  44. package/src/wire/service.ts +188 -6
  45. package/src/wire/socket.ts +17 -8
  46. package/src/wire/statement.ts +37 -29
  47. package/src/wire/transaction.ts +57 -32
  48. package/src/wire/wire-types.ts +127 -0
  49. 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,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,26 @@ 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
+ ret.data[blob.row][blob.column] = applyTypeCast(
2019
+ statement.connection.options, blob.meta || {},
2020
+ parseValueIfJson(blob.value, statement.connection.options));
1854
2021
  }
1855
2022
 
1856
2023
  doSynchronousLoop(ret.data, (row, _i, next) => {
1857
- const pos = asStream ? streamIndex++ : (data.push(row) - 1);
2024
+ const pos = asStream ? streamIndex++ : (data!.push(row) - 1);
1858
2025
  if (asStream) {
1859
2026
  executeStreamRow(custom, row, pos, statement.output, next);
1860
2027
  } else {
@@ -1888,7 +2055,7 @@ class Connection {
1888
2055
 
1889
2056
 
1890
2057
 
1891
- openBlob(blob, transaction, callback) {
2058
+ openBlob(blob: Quad, transaction: Transaction, callback: QueueCallback) {
1892
2059
  var msg = this._msg;
1893
2060
  msg.pos = 0;
1894
2061
  msg.addInt(Const.op_open_blob);
@@ -1898,7 +2065,7 @@ class Connection {
1898
2065
  }
1899
2066
 
1900
2067
 
1901
- closeBlob(blob, callback, defer = true) {
2068
+ closeBlob(blob: any, callback?: QueueCallback, defer = true) {
1902
2069
  var msg = this._msg;
1903
2070
  msg.pos = 0;
1904
2071
  msg.addInt(Const.op_close_blob);
@@ -1907,7 +2074,7 @@ class Connection {
1907
2074
  }
1908
2075
 
1909
2076
 
1910
- getSegment(blob, callback) {
2077
+ getSegment(blob: any, callback: QueueCallback) {
1911
2078
  var msg = this._msg;
1912
2079
  msg.pos = 0;
1913
2080
  msg.addInt(Const.op_get_segment);
@@ -1918,7 +2085,7 @@ class Connection {
1918
2085
  }
1919
2086
 
1920
2087
 
1921
- createBlob2(transaction, callback) {
2088
+ createBlob2(transaction: Transaction, callback: QueueCallback) {
1922
2089
  var msg = this._msg;
1923
2090
  msg.pos = 0;
1924
2091
  msg.addInt(Const.op_create_blob2);
@@ -1930,7 +2097,7 @@ class Connection {
1930
2097
  }
1931
2098
 
1932
2099
 
1933
- batchSegments(blob, buffer, callback) {
2100
+ batchSegments(blob: any, buffer: Buffer, callback: QueueCallback) {
1934
2101
  var msg = this._msg;
1935
2102
  var blr = this._blr;
1936
2103
  msg.pos = 0;
@@ -1944,7 +2111,7 @@ class Connection {
1944
2111
  }
1945
2112
 
1946
2113
 
1947
- svcattach(options: any, callback?: any, svc?: any) {
2114
+ svcattach(options: InternalOptions, callback?: Callback<ServiceManager>, svc?: ServiceManager) {
1948
2115
  this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
1949
2116
  var database = options.database || options.filename;
1950
2117
  var user = options.user || Const.DEFAULT_USER;
@@ -1979,7 +2146,7 @@ class Connection {
1979
2146
 
1980
2147
  var self = this;
1981
2148
 
1982
- function cb(err, ret) {
2149
+ function cb(err: any, ret: any) {
1983
2150
 
1984
2151
  if (err) {
1985
2152
  doError(err, callback);
@@ -2005,19 +2172,19 @@ class Connection {
2005
2172
  }
2006
2173
 
2007
2174
 
2008
- svcstart(spbaction, callback) {
2175
+ svcstart(spbaction: BlrWriter, callback: QueueCallback | undefined) {
2009
2176
  var msg = this._msg;
2010
2177
  var blr = this._blr;
2011
2178
  msg.pos = 0;
2012
2179
  msg.addInt(Const.op_service_start);
2013
- msg.addInt(this.svchandle);
2180
+ msg.addInt(this.svchandle!);
2014
2181
  msg.addInt(0)
2015
2182
  msg.addBlr(spbaction);
2016
2183
  this._queueEvent(callback);
2017
2184
  }
2018
2185
 
2019
2186
 
2020
- svcquery(spbquery, resultbuffersize, timeout,callback) {
2187
+ svcquery(spbquery: number[], resultbuffersize: number, timeout: number | undefined, callback: QueueCallback | undefined) {
2021
2188
  if (resultbuffersize > Const.MAX_BUFFER_SIZE) {
2022
2189
  doError(new Error('Buffer is too big'), callback);
2023
2190
  return;
@@ -2030,7 +2197,7 @@ class Connection {
2030
2197
  blr.addByte(Const.isc_spb_current_version);
2031
2198
  //blr.addByteInt32(Const.isc_info_svc_timeout, timeout);
2032
2199
  msg.addInt(Const.op_service_info);
2033
- msg.addInt(this.svchandle);
2200
+ msg.addInt(this.svchandle!);
2034
2201
  msg.addInt(0);
2035
2202
  msg.addBlr(blr);
2036
2203
  blr.pos = 0
@@ -2041,7 +2208,7 @@ class Connection {
2041
2208
  }
2042
2209
 
2043
2210
 
2044
- svcdetach(callback) {
2211
+ svcdetach(callback: Callback | undefined) {
2045
2212
  var self = this;
2046
2213
 
2047
2214
  if (self._isClosed) {
@@ -2056,9 +2223,9 @@ class Connection {
2056
2223
 
2057
2224
  msg.pos = 0;
2058
2225
  msg.addInt(Const.op_service_detach);
2059
- msg.addInt(this.svchandle); // Database Object ID
2226
+ msg.addInt(this.svchandle!); // Database Object ID
2060
2227
 
2061
- self._queueEvent(function (err, ret) {
2228
+ self._queueEvent(function (err: any, ret: any) {
2062
2229
  delete (self.svchandle);
2063
2230
  if (callback)
2064
2231
  callback(err, ret);
@@ -2067,7 +2234,7 @@ class Connection {
2067
2234
 
2068
2235
 
2069
2236
 
2070
- auxConnection(eventid, callback) {
2237
+ auxConnection(eventid: number | Callback, callback?: Callback) {
2071
2238
  if (typeof eventid === 'function') {
2072
2239
  // Preserve the older auxConnection(callback) call shape; plain
2073
2240
  // auxiliary connections historically used event id 0.
@@ -2081,13 +2248,13 @@ class Connection {
2081
2248
  msg.pos = 0;
2082
2249
  msg.addInt(Const.op_connect_request);
2083
2250
  msg.addInt(1); // async
2084
- msg.addInt(self.dbhandle);
2251
+ msg.addInt(self.dbhandle!);
2085
2252
  msg.addInt(eventid);
2086
2253
  if (process.env.FIREBIRD_DEBUG) {
2087
2254
  console.log('[fb-debug] auxConnection: sending op_connect_request(53) dbhandle=%d eventid=%d queue_before=%d xdr_saved=%s',
2088
2255
  self.dbhandle, eventid, self._queue.length, Boolean(self._xdr));
2089
2256
  }
2090
- function cb(err, ret) {
2257
+ function cb(err: any, ret: any) {
2091
2258
 
2092
2259
  if (err) {
2093
2260
  if (process.env.FIREBIRD_DEBUG) {
@@ -2108,13 +2275,13 @@ class Connection {
2108
2275
  socket_info.family, socket_info.port, socket_info.host, self._queue.length);
2109
2276
  }
2110
2277
 
2111
- callback(undefined, socket_info);
2278
+ callback!(undefined, socket_info);
2112
2279
  }
2113
2280
  this._queueEvent(cb);
2114
2281
  }
2115
2282
 
2116
2283
 
2117
- queEvents(events, eventid, callback) {
2284
+ queEvents(events: Record<string, number>, eventid: number, callback: Callback) {
2118
2285
  var self = this;
2119
2286
  if (this._isClosed)
2120
2287
  return this.throwClosed(callback);
@@ -2123,7 +2290,7 @@ class Connection {
2123
2290
  blr.pos = 0;
2124
2291
  msg.pos = 0;
2125
2292
  msg.addInt(Const.op_que_events);
2126
- msg.addInt(this.dbhandle);
2293
+ msg.addInt(this.dbhandle!);
2127
2294
  // prepare EPB
2128
2295
  blr.addByte(1) // epb_version
2129
2296
  for (var event in events) {
@@ -2137,7 +2304,7 @@ class Connection {
2137
2304
  msg.addInt(0); // args
2138
2305
  msg.addInt(eventid);
2139
2306
 
2140
- function cb(err, ret) {
2307
+ function cb(err: any, ret: any) {
2141
2308
  if (err) {
2142
2309
  doError(err, callback);
2143
2310
  return;
@@ -2150,17 +2317,17 @@ class Connection {
2150
2317
  }
2151
2318
 
2152
2319
 
2153
- closeEvents(eventid, callback) {
2320
+ closeEvents(eventid: number, callback: Callback) {
2154
2321
  var self = this;
2155
2322
  if (this._isClosed)
2156
2323
  return this.throwClosed(callback);
2157
2324
  var msg = self._msg;
2158
2325
  msg.pos = 0;
2159
2326
  msg.addInt(Const.op_cancel_events);
2160
- msg.addInt(self.dbhandle);
2327
+ msg.addInt(self.dbhandle!);
2161
2328
  msg.addInt(eventid);
2162
2329
 
2163
- function cb(err, ret) {
2330
+ function cb(err: any, ret: any) {
2164
2331
  if (err) {
2165
2332
  doError(err, callback);
2166
2333
  return;
@@ -2179,7 +2346,7 @@ const opcodeNames = Object.fromEntries(
2179
2346
  Object.entries(Const).filter(([k]) => k.startsWith('op_')).map(([k, v]) => [v, k])
2180
2347
  );
2181
2348
 
2182
- 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) {
2183
2350
  try {
2184
2351
  do {
2185
2352
  var r = data.r || data.readInt();
@@ -2200,7 +2367,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2200
2367
  cnx._inlineBlobs = new Map();
2201
2368
  }
2202
2369
  const cacheKey = `${blob_id.high}:${blob_id.low}`;
2203
- cnx._inlineBlobs.set(cacheKey, blob_data);
2370
+ cnx._inlineBlobs.set(cacheKey, blob_data!);
2204
2371
  r = Const.op_dummy; // Continue loop to read next opcode
2205
2372
  }
2206
2373
  } while (r === Const.op_dummy);
@@ -2210,7 +2377,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2210
2377
  r, opcodeNames[r] || 'unknown', data.pos, data.buffer.length);
2211
2378
  }
2212
2379
 
2213
- var item, op, response;
2380
+ var item, op, response: any;
2214
2381
 
2215
2382
  switch (r) {
2216
2383
  case Const.op_response:
@@ -2221,7 +2388,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2221
2388
  response = {};
2222
2389
  }
2223
2390
 
2224
- let loop = function (err) {
2391
+ let loop = function (err: any) {
2225
2392
  if (err) {
2226
2393
  return cb(err);
2227
2394
  } else {
@@ -2282,13 +2449,27 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2282
2449
  }
2283
2450
  case Const.op_fetch_response:
2284
2451
  case Const.op_sql_response:
2285
- var statement = callback.statement;
2452
+ // fetch/sql_response entries always carry their statement
2453
+ var statement = callback!.statement!;
2286
2454
  var output = statement.output;
2287
2455
  var custom = statement.options || {};
2288
2456
  var isOpFetch = r === Const.op_fetch_response;
2289
2457
  var _xdrpos;
2290
2458
  statement.nbrowsfetched = statement.nbrowsfetched || 0;
2291
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
+
2292
2473
  if (isOpFetch && data.fop) { // could be set when a packet is not complete
2293
2474
  data.readBuffer(68); // ??
2294
2475
  op = data.readInt(); // ??
@@ -2310,55 +2491,67 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2310
2491
 
2311
2492
  if (custom.asObject && !data.fcols) {
2312
2493
  if (lowercase_keys) {
2313
- data.fcols = output.map((column) => column.alias.toLowerCase());
2494
+ data.fcols = output.map((column: any) => column.alias.toLowerCase());
2314
2495
  } else {
2315
- data.fcols = output.map((column) => column.alias);
2496
+ data.fcols = output.map((column: any) => column.alias);
2316
2497
  }
2317
2498
  }
2318
2499
 
2319
- const arrBlob = [];
2500
+ const arrBlob: any[] = [];
2320
2501
  const lowerV13 = statement.connection.accept.protocolVersion < Const.PROTOCOL_VERSION13;
2321
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
+
2322
2511
  while (data.fcount && (data.fstatus !== 100)) {
2323
2512
  let nullBitSet;
2324
2513
  if (!lowerV13) {
2325
2514
  const nullBitsLen = Math.floor((output.length + 7) / 8);
2326
- nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false));
2515
+ nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false)!);
2327
2516
  data.readBuffer((4 - nullBitsLen) & 3, false); // Skip padding
2328
2517
  }
2329
2518
 
2330
2519
  for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
2331
2520
  item = output[data.fcolumn];
2332
2521
 
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
- }
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);
2339
2525
 
2340
2526
  continue;
2341
2527
  }
2342
2528
 
2343
2529
  try {
2344
2530
  _xdrpos = data.pos;
2345
- const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
2531
+ const key = custom.asObject ? data.fcols![data.fcolumn!] : data.fcolumn;
2346
2532
  const row = data.frows.length;
2347
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;
2348
2538
 
2349
2539
  if (item.type === Const.SQL_BLOB && value !== null) {
2350
2540
  if (item.subType === Const.isc_blob_text && cnx.options.blobAsText) {
2351
- value = fetch_blob_async_transaction(statement, value, key, row);
2541
+ value = fetch_blob_async_transaction(statement, value, key, row, item);
2352
2542
  arrBlob.push(value);
2543
+ pendingTextBlob = true;
2353
2544
  } else {
2354
2545
  value = fetch_blob_async(statement, value, key, row);
2355
2546
  }
2356
2547
  }
2357
2548
 
2358
- data.frow[key] = parseValueIfJson(value, cnx.options);
2549
+ data.frow[key] = pendingTextBlob
2550
+ ? value
2551
+ : applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
2359
2552
  } catch (e) {
2360
2553
  // uncomplete packet read
2361
- data.pos = _xdrpos;
2554
+ data.pos = _xdrpos!;
2362
2555
  data.r = r;
2363
2556
  return cb(new Error('Packet is not complete'));
2364
2557
  }
@@ -2403,6 +2596,17 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2403
2596
  statement.nbrowsfetched++;
2404
2597
  }
2405
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
+
2406
2610
  // ToDo: emit "result" with blob subtype string decoded
2407
2611
  statement.connection.db.emit('result', data.frows, arrBlob);
2408
2612
  return cb(null, {data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob});
@@ -2427,7 +2631,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2427
2631
  }
2428
2632
 
2429
2633
  if (r === Const.op_cond_accept || r === Const.op_accept_data) {
2430
- var d = new BlrReader(data.readArray());
2634
+ var d = new BlrReader(data.readArray()!);
2431
2635
  accept.pluginName = data.readString(Const.DEFAULT_ENCODING);
2432
2636
  var is_authenticated = data.readInt();
2433
2637
  var keys = data.readString(Const.DEFAULT_ENCODING); // keys
@@ -2447,7 +2651,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2447
2651
  }
2448
2652
 
2449
2653
  if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
2450
- var crypto = {
2654
+ var crypto: Record<string, string> = {
2451
2655
  Srp: 'sha1',
2452
2656
  Srp256: 'sha256',
2453
2657
  Srp384: 'sha384',
@@ -2458,7 +2662,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2458
2662
  if (!d.buffer) {
2459
2663
  cnx._pendingAccept = accept;
2460
2664
  cnx.sendOpContAuth(
2461
- cnx.clientKeys.public.toString(16),
2665
+ cnx.clientKeys!.public.toString(16),
2462
2666
  Const.DEFAULT_ENCODING,
2463
2667
  accept.pluginName
2464
2668
  );
@@ -2489,20 +2693,20 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2489
2693
 
2490
2694
  if (process.env.FIREBIRD_DEBUG) {
2491
2695
  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));
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));
2495
2699
  console.log('hashAlgo:', accept.srpAlgo);
2496
2700
  }
2497
2701
 
2498
2702
  const _t1 = Date.now();
2499
2703
  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,
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,
2506
2710
  accept.srpAlgo
2507
2711
  );
2508
2712
 
@@ -2519,7 +2723,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2519
2723
  accept.authData = proof.authData.toString(16);
2520
2724
  accept.sessionKey = proof.clientSessionKey;
2521
2725
  } else if (accept.pluginName === Const.AUTH_PLUGIN_LEGACY) {
2522
- 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);
2523
2727
  } else {
2524
2728
  return cb(new Error('Unknow auth plugin : ' + accept.pluginName));
2525
2729
  }
@@ -2557,7 +2761,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2557
2761
 
2558
2762
  return cb(undefined, accept);
2559
2763
  case Const.op_cont_auth:
2560
- var d = new BlrReader(data.readArray());
2764
+ var d = new BlrReader(data.readArray()!);
2561
2765
  var pluginName = data.readString(Const.DEFAULT_ENCODING);
2562
2766
  data.readString(Const.DEFAULT_ENCODING); // plist
2563
2767
  data.readString(Const.DEFAULT_ENCODING); // pkey
@@ -2584,7 +2788,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2584
2788
  // the proof with the new plugin's hash algorithm - rather than
2585
2789
  // as the server's M2 proof, otherwise the client silently waits
2586
2790
  // forever for an op_accept the server will never send (#254).
2587
- if (!cnx.serverKeys || cnx.serverKeys.pluginName !== pluginName) {
2791
+ if (!cnx.serverKeys || cnx.serverKeys!.pluginName !== pluginName) {
2588
2792
  // Check buffer contains salt
2589
2793
  var saltLen = d.buffer.readUInt16LE(0);
2590
2794
  if (saltLen > 32 * 2) {
@@ -2607,7 +2811,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2607
2811
  pluginName: pluginName
2608
2812
  };
2609
2813
 
2610
- var crypto = {
2814
+ var crypto: Record<string, string> = {
2611
2815
  Srp: 'sha1',
2612
2816
  Srp256: 'sha256',
2613
2817
  Srp384: 'sha384',
@@ -2617,20 +2821,20 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2617
2821
 
2618
2822
  if (process.env.FIREBIRD_DEBUG) {
2619
2823
  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));
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));
2623
2827
  console.log('hashAlgo:', srpAlgo);
2624
2828
  }
2625
2829
 
2626
2830
  const _t1 = Date.now();
2627
2831
  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,
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,
2634
2838
  srpAlgo
2635
2839
  );
2636
2840
 
@@ -2667,7 +2871,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2667
2871
  cnx._pendingAccept.protocolVersion,
2668
2872
  cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
2669
2873
  }
2670
- 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);
2671
2875
  cnx.sendOpContAuth(legacyAuthData, Const.DEFAULT_ENCODING, pluginName);
2672
2876
  return; // wait for op_accept
2673
2877
  }
@@ -2680,7 +2884,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2680
2884
 
2681
2885
  if (pluginName === Const.AUTH_PLUGIN_LEGACY) { // Fallback to LegacyAuth
2682
2886
  cnx.accept.pluginName = pluginName;
2683
- 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);
2684
2888
 
2685
2889
  cnx.sendOpContAuth(
2686
2890
  cnx.accept.authData,
@@ -2773,7 +2977,7 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2773
2977
  }
2774
2978
  return cb(new Error('Unexpected:' + r));
2775
2979
  }
2776
- } catch (err) {
2980
+ } catch (err: any) {
2777
2981
  if (process.env.FIREBIRD_DEBUG) {
2778
2982
  console.warn('[fb-debug] decodeResponse exception: %s (RangeError=%s) pos=%d buflen=%d',
2779
2983
  err.message, err instanceof RangeError, data.pos, data.buffer.length);
@@ -2789,8 +2993,8 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2789
2993
  * Read one XDR status vector (as in op_response / op_batch_cs error
2790
2994
  * vectors): a stream of isc_arg_* items terminated by isc_arg_end.
2791
2995
  */
2792
- function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2793
- 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: [] };
2794
2998
  var item: any = {};
2795
2999
 
2796
3000
  while (true) {
@@ -2820,13 +3024,24 @@ function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2820
3024
  result.sqlcode = n;
2821
3025
  }
2822
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;
2823
3038
  default:
2824
3039
  throw new Error('Unexpected status vector item: ' + op);
2825
3040
  }
2826
3041
  }
2827
3042
  }
2828
3043
 
2829
- 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) {
2830
3045
  var handle = data.readInt();
2831
3046
 
2832
3047
  if (!response.handle) {
@@ -2889,18 +3104,31 @@ function parseOpResponse(data: any, response: any, cb?: (err?: any, response?: a
2889
3104
  response.sqlcode = num;
2890
3105
  }
2891
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
+ }
2892
3118
  break;
2893
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).
2894
3123
  if (cb) {
2895
- cb(new Error('Unexpected: ' + op))
2896
- } else {
2897
- throw new Error('Unexpected: ' + op);
3124
+ return cb(new Error('Unexpected: ' + op));
2898
3125
  }
3126
+ throw new Error('Unexpected: ' + op);
2899
3127
  }
2900
3128
  }
2901
3129
  }
2902
3130
 
2903
- function describe(buff: Buffer, statement: any) {
3131
+ function describe(buff: Buffer, statement: Statement) {
2904
3132
  var br = new BlrReader(buff);
2905
3133
  var parameters: any = null;
2906
3134
  var type: any, param: any;
@@ -2908,7 +3136,7 @@ function describe(buff: Buffer, statement: any) {
2908
3136
  while (br.pos < br.buffer.length) {
2909
3137
  switch (br.readByteCode()) {
2910
3138
  case Const.isc_info_sql_stmt_type:
2911
- statement.type = br.readInt();
3139
+ statement.type = br.readInt()!;
2912
3140
  break;
2913
3141
  case Const.isc_info_sql_get_plan:
2914
3142
  statement.plan = br.readString(Const.DEFAULT_ENCODING);
@@ -2932,7 +3160,9 @@ function describe(buff: Buffer, statement: any) {
2932
3160
  case Const.isc_info_sql_describe_end:
2933
3161
  break;
2934
3162
  case Const.isc_info_sql_sqlda_seq:
2935
- 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()!;
2936
3166
  break;
2937
3167
  case Const.isc_info_sql_type:
2938
3168
  type = br.readInt();
@@ -2963,7 +3193,9 @@ function describe(buff: Buffer, statement: any) {
2963
3193
  default:
2964
3194
  throw new Error('Unexpected');
2965
3195
  }
2966
- 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;
2967
3199
  param.type = type;
2968
3200
  param.nullable = Boolean(param.type & 1);
2969
3201
  param.type &= ~1;
@@ -3243,10 +3475,10 @@ function CalcBlr(blr: BlrWriter, xsqlda: any[]) {
3243
3475
  blr.addByte(Const.blr_eoc);
3244
3476
  }
3245
3477
 
3246
- function fetch_blob_async_transaction(statement: any, id: any, column: any, row: any) {
3247
- 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 };
3248
3480
 
3249
- return (transactionArg) => {
3481
+ return (transactionArg: any) => {
3250
3482
  const cacheKey = `${id.high}:${id.low}`;
3251
3483
  if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
3252
3484
  const data = statement.connection._inlineBlobs.get(cacheKey);
@@ -3256,10 +3488,10 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3256
3488
 
3257
3489
  const singleTransaction = transactionArg === undefined;
3258
3490
 
3259
- let promiseTransaction;
3491
+ let promiseTransaction: Promise<Transaction>;
3260
3492
  if (singleTransaction) {
3261
3493
  promiseTransaction = new Promise((resolve, reject) => {
3262
- statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
3494
+ statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err: any, transaction: Transaction) => {
3263
3495
  if (err) {
3264
3496
  return reject(err);
3265
3497
  }
@@ -3273,7 +3505,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3273
3505
  return promiseTransaction.then((transaction) => {
3274
3506
  return new Promise((resolve, reject) => {
3275
3507
  statement.connection._pending.push('openBlob');
3276
- statement.connection.openBlob(id, transaction, (err, blob) => {
3508
+ statement.connection.openBlob(id, transaction, (err: any, blob: any) => {
3277
3509
 
3278
3510
  if (err) {
3279
3511
  reject(err);
@@ -3281,7 +3513,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3281
3513
  }
3282
3514
 
3283
3515
  const read = () => {
3284
- statement.connection.getSegment(blob, (err, ret) => {
3516
+ statement.connection.getSegment(blob, (err: any, ret: any) => {
3285
3517
 
3286
3518
  if (err) {
3287
3519
  if (singleTransaction) {
@@ -3305,7 +3537,7 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3305
3537
 
3306
3538
  statement.connection.closeBlob(blob);
3307
3539
  if (singleTransaction) {
3308
- transaction.commit((err) => {
3540
+ transaction.commit((err: any) => {
3309
3541
  if (err) {
3310
3542
  reject(err);
3311
3543
  } else {
@@ -3325,14 +3557,14 @@ function fetch_blob_async_transaction(statement: any, id: any, column: any, row:
3325
3557
  };
3326
3558
  }
3327
3559
 
3328
- function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3329
- 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) => {
3330
3562
  statement.connection._pending.push('openBlob');
3331
- statement.connection.openBlob(id, transaction, (err, blob) => {
3563
+ statement.connection.openBlob(id, transaction, (err: any, blob: any) => {
3332
3564
  let e: any = new Events.EventEmitter();
3333
3565
 
3334
- e.pipe = (stream) => {
3335
- e.on('data', (chunk) => {
3566
+ e.pipe = (stream: any) => {
3567
+ e.on('data', (chunk: any) => {
3336
3568
  stream.write(chunk);
3337
3569
  });
3338
3570
  e.on('end', () => {
@@ -3345,7 +3577,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3345
3577
  }
3346
3578
 
3347
3579
  const read = () => {
3348
- statement.connection.getSegment(blob, (err, ret) => {
3580
+ statement.connection.getSegment(blob, (err: any, ret: any) => {
3349
3581
 
3350
3582
  if (err) {
3351
3583
  transaction.rollback(() => {
@@ -3368,7 +3600,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3368
3600
 
3369
3601
  statement.connection.closeBlob(blob);
3370
3602
  if (close) {
3371
- transaction.commit((err) => {
3603
+ transaction.commit((err: any) => {
3372
3604
  if (err) {
3373
3605
  e.emit('error', err);
3374
3606
  } else {
@@ -3388,7 +3620,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3388
3620
  });
3389
3621
  };
3390
3622
 
3391
- return (transaction, callback) => {
3623
+ return (transaction: Transaction, callback: any) => {
3392
3624
  // callback(error, nameField, eventEmitter, row)
3393
3625
  const singleTransaction = callback === undefined;
3394
3626
  const actualCallback = singleTransaction ? transaction : callback;
@@ -3397,8 +3629,8 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3397
3629
  if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
3398
3630
  const data = statement.connection._inlineBlobs.get(cacheKey);
3399
3631
  let e: any = new Events.EventEmitter();
3400
- e.pipe = (stream) => {
3401
- e.on('data', (chunk) => {
3632
+ e.pipe = (stream: any) => {
3633
+ e.on('data', (chunk: any) => {
3402
3634
  stream.write(chunk);
3403
3635
  });
3404
3636
  e.on('end', () => {
@@ -3419,7 +3651,7 @@ function fetch_blob_async(statement: any, id: any, name: any, row: any) {
3419
3651
 
3420
3652
  if (singleTransaction) {
3421
3653
  callback = transaction;
3422
- statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
3654
+ statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err: any, transaction: Transaction) => {
3423
3655
  if (err) {
3424
3656
  callback(err);
3425
3657
  return;
@@ -3438,7 +3670,7 @@ function doSynchronousLoop(data: any[], processData: (row: any, index: number, n
3438
3670
  return;
3439
3671
  }
3440
3672
 
3441
- const loop = (index) => {
3673
+ const loop = (index: any) => {
3442
3674
  processData(data[index], index, (err) => {
3443
3675
  if (err) {
3444
3676
  done(err);