node-firebird 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +211 -6
  2. package/lib/index.d.ts +5 -0
  3. package/lib/index.js +32 -1
  4. package/lib/pool.js +1 -1
  5. package/lib/srp.d.ts +3 -3
  6. package/lib/types.d.ts +165 -5
  7. package/lib/uri.js +2 -2
  8. package/lib/wire/connection.d.ts +92 -59
  9. package/lib/wire/connection.js +279 -58
  10. package/lib/wire/const.d.ts +9 -1
  11. package/lib/wire/const.js +23 -9
  12. package/lib/wire/database.d.ts +51 -26
  13. package/lib/wire/database.js +53 -20
  14. package/lib/wire/eventConnection.js +5 -3
  15. package/lib/wire/query-stream.d.ts +18 -0
  16. package/lib/wire/query-stream.js +73 -0
  17. package/lib/wire/serialize.d.ts +20 -2
  18. package/lib/wire/serialize.js +7 -0
  19. package/lib/wire/service.d.ts +42 -0
  20. package/lib/wire/service.js +145 -0
  21. package/lib/wire/socket.d.ts +3 -1
  22. package/lib/wire/socket.js +5 -2
  23. package/lib/wire/statement.d.ts +31 -19
  24. package/lib/wire/statement.js +1 -5
  25. package/lib/wire/transaction.d.ts +30 -18
  26. package/lib/wire/transaction.js +23 -4
  27. package/lib/wire/wire-types.d.ts +116 -0
  28. package/lib/wire/wire-types.js +10 -0
  29. package/lib/wire/xsqlvar.d.ts +57 -18
  30. package/lib/wire/xsqlvar.js +59 -0
  31. package/package.json +1 -1
  32. package/src/index.ts +36 -4
  33. package/src/messages.ts +1 -1
  34. package/src/pool.ts +1 -1
  35. package/src/srp.ts +6 -6
  36. package/src/types.ts +162 -5
  37. package/src/unix-crypt.ts +9 -9
  38. package/src/uri.ts +2 -2
  39. package/src/wire/connection.ts +475 -234
  40. package/src/wire/const.ts +23 -9
  41. package/src/wire/database.ts +101 -54
  42. package/src/wire/eventConnection.ts +8 -5
  43. package/src/wire/query-stream.ts +80 -0
  44. package/src/wire/serialize.ts +31 -0
  45. package/src/wire/service.ts +188 -6
  46. package/src/wire/socket.ts +17 -8
  47. package/src/wire/statement.ts +37 -29
  48. package/src/wire/transaction.ts +57 -32
  49. package/src/wire/wire-types.ts +127 -0
  50. package/src/wire/xsqlvar.ts +85 -7
package/src/wire/const.ts CHANGED
@@ -269,7 +269,8 @@ const SUPPORTED_PROTOCOL = [
269
269
  [protocol.PROTOCOL_VERSION16, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 7],
270
270
  [protocol.PROTOCOL_VERSION17, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 8],
271
271
  [protocol.PROTOCOL_VERSION18, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 9],
272
- [protocol.PROTOCOL_VERSION19, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 10]
272
+ [protocol.PROTOCOL_VERSION19, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 10],
273
+ [protocol.PROTOCOL_VERSION20, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 11]
273
274
  ];
274
275
 
275
276
  const authPlugin = {
@@ -472,16 +473,27 @@ const dpb = {
472
473
  isc_dpb_reset_icu : 89,
473
474
  isc_dpb_map_attach : 90,
474
475
  isc_dpb_session_time_zone : 91,
475
- isc_dpb_parallel_workers : 92,
476
- isc_dpb_max_inline_blob_size : 93,
477
- // Firebird 6.0 SQL Schema parameters
476
+ // Firebird 4.0
477
+ isc_dpb_set_db_replica : 92,
478
+ isc_dpb_set_bind : 93,
479
+ isc_dpb_decfloat_round : 94,
480
+ isc_dpb_decfloat_traps : 95,
481
+ isc_dpb_clear_map : 96,
482
+ // Firebird 5.0
483
+ isc_dpb_upgrade_db : 97,
484
+ isc_dpb_parallel_workers : 100,
485
+ isc_dpb_worker_attach : 101,
486
+ // Firebird 6.0
487
+ // isc_dpb_owner sets the owner of a newly created database to a user
488
+ // other than the connecting one (create only; requires superuser).
489
+ isc_dpb_owner : 102,
490
+ isc_dpb_max_blob_cache_size : 103,
491
+ isc_dpb_max_inline_blob_size : 104, // Firebird 5.0.3+
478
492
  // isc_dpb_search_path sets the comma-separated ordered list of schema
479
493
  // names to search for unqualified object names (similar to PostgreSQL
480
- // search_path). Available on Protocol 20+ (Firebird 6.0).
481
- isc_dpb_search_path : 94,
482
- // isc_dpb_default_schema overrides the session's current schema
483
- // (CURRENT_SCHEMA) at connection time. Available on Protocol 20+.
484
- isc_dpb_default_schema : 95,
494
+ // search_path). CURRENT_SCHEMA is the first existing schema of the list.
495
+ // Available on Protocol 20+ (Firebird 6.0).
496
+ isc_dpb_search_path : 105,
485
497
  };
486
498
 
487
499
  const cnct = {
@@ -614,6 +626,7 @@ const DESCRIBE = [
614
626
  sqlInfo.isc_info_sql_length,
615
627
  sqlInfo.isc_info_sql_field,
616
628
  sqlInfo.isc_info_sql_relation,
629
+ sqlInfo.isc_info_sql_relation_alias, // FB 2.0+: query alias of the source relation (nestTables)
617
630
  //isc_info_sql_owner,
618
631
  sqlInfo.isc_info_sql_alias,
619
632
  sqlInfo.isc_info_sql_describe_end,
@@ -642,6 +655,7 @@ const DESCRIBE_WITH_SCHEMA = [
642
655
  sqlInfo.isc_info_sql_field,
643
656
  sqlInfo.isc_info_sql_relation,
644
657
  sqlInfo.isc_info_sql_relation_schema, // FB 6.0: schema of source relation
658
+ sqlInfo.isc_info_sql_relation_alias, // query alias of the source relation (nestTables)
645
659
  //isc_info_sql_owner,
646
660
  sqlInfo.isc_info_sql_alias,
647
661
  sqlInfo.isc_info_sql_describe_end,
@@ -1,9 +1,24 @@
1
1
  import Events from 'events';
2
- import { doError, fromCallback } from '../callback';
2
+ import { doError, fromCallback, type Callback, type SimpleCallback } from '../callback';
3
3
  import { escape } from '../utils';
4
4
  import Const from './const';
5
+ import { computeColumnKeys, nestCell, resolveNestTables } from './xsqlvar';
5
6
  import EventConnection from './eventConnection';
6
7
  import FbEventManager from './fbEventManager';
8
+ import makeQueryStream from './query-stream';
9
+ import type Connection from './connection';
10
+ import type Transaction from './transaction';
11
+ import type Statement from './statement';
12
+ import type { BatchCb, StatementCb, InternalQueryOptions } from './wire-types';
13
+ import type { BatchOptions, BatchResult, Isolation, QueryParams, QueryStreamOptions, TransactionCallback, TransactionOptions } from '../types';
14
+
15
+ /** Callback for startTransaction: the internal optional-args shape, or the
16
+ * public TransactionCallback (non-optional transaction) from types.ts. */
17
+ type TransactionCb = Callback<Transaction> | TransactionCallback;
18
+
19
+ /** startTransaction options: resolved options object, a bare isolation
20
+ * array, or omitted entirely (callback in the options position). */
21
+ type TransactionArg = TransactionOptions | Isolation | TransactionCb | undefined;
7
22
 
8
23
  /***************************************
9
24
  *
@@ -40,7 +55,7 @@ import FbEventManager from './fbEventManager';
40
55
  *
41
56
  ***************************************/
42
57
 
43
- function readblob(blob: any, callback: (err: any, data?: any) => void): void {
58
+ function readblob(blob: any, callback: Callback): void {
44
59
  if (blob === undefined || blob === null) {
45
60
  callback(null, blob);
46
61
  return;
@@ -80,35 +95,47 @@ function readblob(blob: any, callback: (err: any, data?: any) => void): void {
80
95
  });
81
96
  }
82
97
 
83
- function fetchBlobSyncRow(row: any, meta: any[], callback: (err: any, row?: any) => void): void {
84
- if (!row || !meta || !meta.length) {
98
+ function fetchBlobSyncRow(row: any, meta: any[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined, callback: Callback): void {
99
+ if (!row || !meta || !meta.length || !meta.some((m) => m && m.type === Const.SQL_BLOB)) {
85
100
  callback(null, row);
86
101
  return;
87
102
  }
88
103
 
89
- const rowKeys = Object.keys(row);
90
- const blobColumns: string[] = [];
104
+ // locate blob cells by the same key computation the fetch decoder used,
105
+ // rather than assuming Object.keys(row) is index-aligned with meta —
106
+ // duplicate JOIN column names (and nested rows) break that alignment.
107
+ // Array rows (sequentially's legacy boolean form) are keyed by index.
108
+ const isArrayRow = Array.isArray(row);
109
+ const keys = isArrayRow ? null : computeColumnKeys(meta, nestTables, lowercaseKeys);
110
+ const blobCells: { target: any; key: string | number }[] = [];
91
111
 
92
112
  for (let i = 0; i < meta.length; i++) {
93
- if (meta[i] && meta[i].type === Const.SQL_BLOB && rowKeys[i] !== undefined) {
94
- blobColumns.push(rowKeys[i]);
113
+ if (!meta[i] || meta[i].type !== Const.SQL_BLOB) {
114
+ continue;
115
+ }
116
+ const target = keys ? nestCell(row, keys[i].table) : row;
117
+ const key = keys ? keys[i].key : i;
118
+ // duplicate aliases collapse onto one cell — read it only once
119
+ if (typeof target[key] === 'function' &&
120
+ !blobCells.some((cell) => cell.target === target && cell.key === key)) {
121
+ blobCells.push({ target, key });
95
122
  }
96
123
  }
97
124
 
98
- if (!blobColumns.length) {
125
+ if (!blobCells.length) {
99
126
  callback(null, row);
100
127
  return;
101
128
  }
102
129
 
103
- let pending = blobColumns.length;
130
+ let pending = blobCells.length;
104
131
  let blobErr: any;
105
132
 
106
- blobColumns.forEach(function(columnName) {
107
- readblob(row[columnName], function(err: any, data: any) {
133
+ blobCells.forEach(function(cell) {
134
+ readblob(cell.target[cell.key], function(err: any, data: any) {
108
135
  if (err && !blobErr) {
109
136
  blobErr = err;
110
137
  }
111
- row[columnName] = data;
138
+ cell.target[cell.key] = data;
112
139
  pending--;
113
140
  if (pending === 0) {
114
141
  callback(blobErr, row);
@@ -118,10 +145,10 @@ function fetchBlobSyncRow(row: any, meta: any[], callback: (err: any, row?: any)
118
145
  }
119
146
 
120
147
  class Database extends Events.EventEmitter {
121
- connection: any;
148
+ connection: Connection;
122
149
  eventid: number;
123
150
 
124
- constructor(connection: any) {
151
+ constructor(connection: Connection) {
125
152
  super();
126
153
  this.connection = connection;
127
154
  connection.db = this;
@@ -132,7 +159,7 @@ class Database extends Events.EventEmitter {
132
159
  return escape(value, this.connection.accept.protocolVersion);
133
160
  }
134
161
 
135
- detach(callback?: (err?: any, obj?: any) => void, force?: boolean): this {
162
+ detach(callback?: Callback, force?: boolean): this {
136
163
  var self = this;
137
164
 
138
165
  if (!force && self.connection._pending.length > 0) {
@@ -150,7 +177,7 @@ class Database extends Events.EventEmitter {
150
177
  if (callback)
151
178
  callback(err, obj);
152
179
 
153
- }, force);
180
+ });
154
181
  } else {
155
182
  self.emit('detach', false);
156
183
  if (callback)
@@ -160,32 +187,35 @@ class Database extends Events.EventEmitter {
160
187
  return self;
161
188
  }
162
189
 
163
- transaction(options: any, callback?: (err: any, transaction?: any) => void): this {
190
+ transaction(options: TransactionArg, callback?: TransactionCb): this {
164
191
  return this.startTransaction(options, callback);
165
192
  }
166
193
 
167
- startTransaction(options: any, callback?: (err: any, transaction?: any) => void): this {
194
+ startTransaction(options: TransactionArg, callback?: TransactionCb): this {
168
195
  this.connection.startTransaction(options, callback);
169
196
  return this;
170
197
  }
171
198
 
172
- newStatement(query: string, callback: (err: any, statement?: any) => void): this {
173
- this.startTransaction(function(err: any, transaction: any) {
199
+ newStatement(query: string, callback: StatementCb): this {
200
+ // the public strict callback shape and the internal optional-args
201
+ // shape only differ in optionality; treat it as the internal one
202
+ const cb = callback as Callback<Statement>;
203
+ this.startTransaction(function(err: any, transaction?: Transaction) {
174
204
 
175
- if (err) {
176
- callback(err);
205
+ if (err || !transaction) {
206
+ cb(err);
177
207
  return;
178
208
  }
179
209
 
180
- transaction.newStatement(query, function(err: any, statement: any) {
210
+ transaction.newStatement(query, function(err: any, statement?: Statement) {
181
211
 
182
212
  if (err) {
183
- callback(err);
213
+ cb(err);
184
214
  return;
185
215
  }
186
216
 
187
217
  transaction.commit(function(err: any) {
188
- callback(err, statement);
218
+ cb(err, statement);
189
219
  });
190
220
  });
191
221
  });
@@ -193,7 +223,7 @@ class Database extends Events.EventEmitter {
193
223
  return this;
194
224
  }
195
225
 
196
- execute(query: string, params?: any, callback?: any, options?: any): this {
226
+ execute(query: string, params?: QueryParams | Callback, callback?: any, options?: InternalQueryOptions): this {
197
227
  if (params instanceof Function) {
198
228
  options = callback;
199
229
  callback = params;
@@ -202,9 +232,9 @@ class Database extends Events.EventEmitter {
202
232
 
203
233
  var self = this;
204
234
 
205
- self.connection.startTransaction(function(err: any, transaction: any) {
235
+ self.connection.startTransaction(function(err: any, transaction?: Transaction) {
206
236
 
207
- if (err) {
237
+ if (err || !transaction) {
208
238
  doError(err, callback);
209
239
  return;
210
240
  }
@@ -238,17 +268,17 @@ class Database extends Events.EventEmitter {
238
268
  * err.batchCompletion. Use transaction.executeBatch for partial-success
239
269
  * handling.
240
270
  */
241
- executeBatch(query: string, rows: any[][], callback?: any, options?: any): this {
271
+ executeBatch(query: string, rows: QueryParams[], callback?: BatchCb, options?: BatchOptions): this {
242
272
  var self = this;
243
273
 
244
- self.connection.startTransaction(function(err: any, transaction: any) {
245
- if (err) {
274
+ self.connection.startTransaction(function(err: any, transaction?: Transaction) {
275
+ if (err || !transaction) {
246
276
  doError(err, callback);
247
277
  return;
248
278
  }
249
279
 
250
- transaction.executeBatch(query, rows, function(err: any, result: any) {
251
- if (err) {
280
+ transaction.executeBatch(query, rows, function(err: any, result?: BatchResult) {
281
+ if (err || !result) {
252
282
  transaction.rollback(function() {
253
283
  doError(err, callback);
254
284
  });
@@ -277,7 +307,7 @@ class Database extends Events.EventEmitter {
277
307
  return self;
278
308
  }
279
309
 
280
- sequentially(query: string, params?: any, on?: any, callback?: any, options: any = {}): this {
310
+ sequentially(query: string, params?: any, on?: any, callback?: any, options: InternalQueryOptions | boolean = {}): this {
281
311
  if (params instanceof Function) {
282
312
  options = callback;
283
313
  callback = on;
@@ -295,7 +325,7 @@ class Database extends Events.EventEmitter {
295
325
  }
296
326
 
297
327
  var self = this;
298
- var _on = function(row: any, i: number, meta: any, next: (err?: any) => void) {
328
+ var _on = function(row: any, i: number, meta: any[], next: (err?: any) => void) {
299
329
  var done = false;
300
330
  var finish = function(err?: any) {
301
331
  if (done) {
@@ -305,7 +335,9 @@ class Database extends Events.EventEmitter {
305
335
  next(err);
306
336
  };
307
337
 
308
- fetchBlobSyncRow(row, meta, function(blobErr: any) {
338
+ // options is read at call time, after the normalization below
339
+ const nest = resolveNestTables(options as any, self.connection.options);
340
+ fetchBlobSyncRow(row, meta, nest, self.connection._lowercase_keys, function(blobErr: any) {
309
341
  if (blobErr) {
310
342
  finish(blobErr);
311
343
  return;
@@ -348,7 +380,20 @@ class Database extends Events.EventEmitter {
348
380
  return self;
349
381
  }
350
382
 
351
- query(query: string, params?: any, callback?: any, options: any = {}): this {
383
+ /**
384
+ * Run `query` and return an object-mode Readable emitting one row per
385
+ * chunk (what pg-query-stream / mysql2 .stream() return), with real
386
+ * backpressure: fetching pauses while the stream buffer is full. Runs
387
+ * in its own transaction, like db.query. Destroying the stream early
388
+ * (e.g. a pipeline() teardown) aborts the fetch and releases the
389
+ * statement. Rows go through the regular decode path, so typeCast,
390
+ * blobAsText and jsonAsObject all apply.
391
+ */
392
+ queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions) {
393
+ return makeQueryStream(this, query, params, options);
394
+ }
395
+
396
+ query(query: string, params?: QueryParams | Callback, callback?: any, options: InternalQueryOptions = {}): this {
352
397
  if (params instanceof Function) {
353
398
  options = callback || {};
354
399
  callback = params;
@@ -366,7 +411,7 @@ class Database extends Events.EventEmitter {
366
411
  return self;
367
412
  }
368
413
 
369
- drop(callback?: (err?: any) => void): void {
414
+ drop(callback?: SimpleCallback): void {
370
415
  return this.connection.dropDatabase(callback);
371
416
  }
372
417
 
@@ -377,7 +422,7 @@ class Database extends Events.EventEmitter {
377
422
  * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
378
423
  * per-attachment, not per-statement.
379
424
  */
380
- cancel(kind?: number | ((err?: any) => void), callback?: (err?: any) => void): this {
425
+ cancel(kind?: number | SimpleCallback, callback?: SimpleCallback): this {
381
426
  if (typeof kind === 'function') {
382
427
  callback = kind;
383
428
  kind = undefined;
@@ -386,7 +431,7 @@ class Database extends Events.EventEmitter {
386
431
  return this;
387
432
  }
388
433
 
389
- attachEvent(callback: (err: any, evt?: any) => void): this {
434
+ attachEvent(callback: Callback<FbEventManager>): this {
390
435
  var self = this;
391
436
  const eventid = self.eventid++;
392
437
  if (process.env.FIREBIRD_DEBUG) {
@@ -450,7 +495,7 @@ class Database extends Events.EventEmitter {
450
495
  * @param {function} [callback] - Asynchronous completion callback.
451
496
  * @returns {Database}
452
497
  */
453
- createTablespace(name: string, filePath: string, callback?: any): this {
498
+ createTablespace(name: string, filePath: string, callback?: Callback): this {
454
499
  const sql = `CREATE TABLESPACE ${name} FILE '${filePath}'`;
455
500
  return this.execute(sql, [], callback);
456
501
  }
@@ -464,7 +509,7 @@ class Database extends Events.EventEmitter {
464
509
  * @param {function} [callback] - Asynchronous completion callback.
465
510
  * @returns {Database}
466
511
  */
467
- alterTablespace(name: string, filePath: string, callback?: any): this {
512
+ alterTablespace(name: string, filePath: string, callback?: Callback): this {
468
513
  const sql = `ALTER TABLESPACE ${name} SET FILE TO '${filePath}'`;
469
514
  return this.execute(sql, [], callback);
470
515
  }
@@ -477,7 +522,7 @@ class Database extends Events.EventEmitter {
477
522
  * @param {function} [callback] - Asynchronous completion callback.
478
523
  * @returns {Database}
479
524
  */
480
- dropTablespace(name: string, callback?: any): this {
525
+ dropTablespace(name: string, callback?: Callback): this {
481
526
  const sql = `DROP TABLESPACE ${name}`;
482
527
  return this.execute(sql, [], callback);
483
528
  }
@@ -492,7 +537,7 @@ class Database extends Events.EventEmitter {
492
537
  * @param {function} [callback] - Asynchronous completion callback.
493
538
  * @returns {Database}
494
539
  */
495
- createSchema(schemaName: string, tablespaceName?: string | ((err?: any) => void), callback?: any): this {
540
+ createSchema(schemaName: string, tablespaceName?: string | Callback, callback?: Callback): this {
496
541
  if (typeof tablespaceName === 'function') {
497
542
  callback = tablespaceName;
498
543
  tablespaceName = undefined;
@@ -511,22 +556,24 @@ class Database extends Events.EventEmitter {
511
556
  * callback API — the promises resolve with the rows alone.
512
557
  */
513
558
 
514
- queryAsync(query: string, params?: any, options?: any): Promise<any[]> {
559
+ queryAsync(query: string, params?: QueryParams, options?: InternalQueryOptions): Promise<any[]> {
515
560
  var self = this;
516
561
  return fromCallback(function(cb) { self.query(query, params, cb, options); });
517
562
  }
518
563
 
519
- executeAsync(query: string, params?: any, options?: any): Promise<any[]> {
564
+ executeAsync(query: string, params?: QueryParams, options?: InternalQueryOptions): Promise<any[]> {
520
565
  var self = this;
521
566
  return fromCallback(function(cb) { self.execute(query, params, cb, options); });
522
567
  }
523
568
 
524
- executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any> {
569
+ executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult> {
525
570
  var self = this;
526
571
  return fromCallback(function(cb) { self.executeBatch(query, rows, cb, options); });
527
572
  }
528
573
 
529
- sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void> {
574
+ /** `on` may hold the options when the params argument is the row callback
575
+ * (public overload: sequentiallyAsync(query, rowCallback, options)). */
576
+ sequentiallyAsync(query: string, params?: any, on?: any, options?: InternalQueryOptions | boolean): Promise<void> {
530
577
  if (params instanceof Function) {
531
578
  options = on;
532
579
  on = params;
@@ -536,16 +583,16 @@ class Database extends Events.EventEmitter {
536
583
  return fromCallback(function(cb) { self.sequentially(query, params, on, cb, options); });
537
584
  }
538
585
 
539
- transactionAsync(options?: any): Promise<any> {
586
+ transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction> {
540
587
  var self = this;
541
588
  return fromCallback(function(cb) { self.startTransaction(options, cb); });
542
589
  }
543
590
 
544
- startTransactionAsync(options?: any): Promise<any> {
591
+ startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction> {
545
592
  return this.transactionAsync(options);
546
593
  }
547
594
 
548
- newStatementAsync(query: string): Promise<any> {
595
+ newStatementAsync(query: string): Promise<Statement> {
549
596
  var self = this;
550
597
  return fromCallback(function(cb) { self.newStatement(query, cb); });
551
598
  }
@@ -560,7 +607,7 @@ class Database extends Events.EventEmitter {
560
607
  return fromCallback(function(cb) { self.drop(cb); });
561
608
  }
562
609
 
563
- attachEventAsync(): Promise<any> {
610
+ attachEventAsync(): Promise<FbEventManager> {
564
611
  var self = this;
565
612
  return fromCallback(function(cb) { self.attachEvent(cb); });
566
613
  }
@@ -575,7 +622,7 @@ class Database extends Events.EventEmitter {
575
622
  * resolves, rolls back when it rejects (the original error is rethrown,
576
623
  * even if the rollback itself fails).
577
624
  */
578
- async withTransaction<T>(work: (transaction: any) => Promise<T> | T, options?: any): Promise<T> {
625
+ async withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T> {
579
626
  const transaction = await this.transactionAsync(options);
580
627
  try {
581
628
  const result = await work(transaction);
@@ -22,8 +22,8 @@ class EventConnection {
22
22
  this._isOpened = false;
23
23
  this._socket = net.createConnection(port, host);
24
24
  this._bind_events(host, port, callback);
25
- this.error;
26
- this.eventcallback;
25
+ this.error = null;
26
+ this.eventcallback = null;
27
27
  }
28
28
 
29
29
  _bind_events(host: string, port: number, callback?: () => void): void {
@@ -60,9 +60,10 @@ class EventConnection {
60
60
  xdr.buffer = buf;
61
61
  }
62
62
 
63
+ var op_pos = xdr.pos;
64
+
63
65
  try {
64
66
 
65
- var op_pos = xdr.pos;
66
67
  var tmp_event: Record<string, number>;
67
68
  while (xdr.pos < xdr.buffer.length) {
68
69
  do {
@@ -72,10 +73,12 @@ class EventConnection {
72
73
  switch (r) {
73
74
  case Const.op_event:
74
75
  xdr.readInt(); // db handle
75
- buf = xdr.readArray();
76
+ // op_event always carries a payload; readArray only
77
+ // returns undefined for zero-length arrays
78
+ buf = xdr.readArray()!;
76
79
  // first byte is always set to 1
77
80
  tmp_event = {};
78
- var lst_event = [];
81
+ var lst_event: { name: string; count: number }[] = [];
79
82
  var eventname = '';
80
83
  var eventcount = 0;
81
84
  var pos = 1;
@@ -0,0 +1,80 @@
1
+ /***************************************
2
+ *
3
+ * queryStream — object-mode Readable over sequentially()
4
+ *
5
+ ***************************************/
6
+
7
+ import { Readable } from 'stream';
8
+
9
+ /**
10
+ * Build an object-mode Readable that emits one row per chunk, implemented
11
+ * on top of `target.sequentially()`'s next()-based backpressure: fetching
12
+ * pauses whenever the stream's internal buffer is full and resumes when
13
+ * the consumer drains it. Shared by Database.queryStream and
14
+ * Transaction.queryStream.
15
+ *
16
+ * Destroying the stream early (including a pipeline() teardown) aborts the
17
+ * row loop, which releases the statement server-side.
18
+ */
19
+ function makeQueryStream(target: any, query: string, params?: any, options?: any): Readable {
20
+ options = options || {};
21
+
22
+ const streamOptions: any = { objectMode: true };
23
+ if (options.highWaterMark !== undefined) {
24
+ streamOptions.highWaterMark = options.highWaterMark;
25
+ }
26
+
27
+ // forwarded to sequentially(); these keys would break its plumbing
28
+ const queryOptions = { ...options };
29
+ delete queryOptions.on;
30
+ delete queryOptions.asStream;
31
+ delete queryOptions.highWaterMark;
32
+
33
+ // sentinel used to stop the row loop on early destroy; recognized by
34
+ // identity in the completion callback and never surfaced to the user
35
+ const abortError = new Error('queryStream destroyed');
36
+
37
+ let pendingNext: ((err?: any) => void) | null = null;
38
+ const resume = (err?: any) => {
39
+ if (pendingNext) {
40
+ const next = pendingNext;
41
+ pendingNext = null;
42
+ next(err);
43
+ }
44
+ };
45
+
46
+ const stream = new Readable({
47
+ ...streamOptions,
48
+ read() {
49
+ resume();
50
+ },
51
+ destroy(err: any, cb: (err?: any) => void) {
52
+ // if the row loop is paused waiting for us, abort it so the
53
+ // statement is released instead of leaking mid-fetch
54
+ resume(abortError);
55
+ cb(err);
56
+ },
57
+ });
58
+
59
+ target.sequentially(query, params, function (row: any, _index: number, next: (err?: any) => void) {
60
+ if (stream.destroyed) {
61
+ next(abortError);
62
+ return;
63
+ }
64
+ if (stream.push(row)) {
65
+ next();
66
+ } else {
67
+ pendingNext = next;
68
+ }
69
+ }, function (err: any) {
70
+ if (err && err !== abortError) {
71
+ stream.destroy(err);
72
+ } else if (!stream.destroyed) {
73
+ stream.push(null);
74
+ }
75
+ }, queryOptions);
76
+
77
+ return stream;
78
+ }
79
+
80
+ export = makeQueryStream;
@@ -421,6 +421,30 @@ export class XdrReader {
421
421
  buffer: Buffer;
422
422
  pos: number;
423
423
 
424
+ // Row-decode scratch state used by decodeResponse (connection.ts) while
425
+ // decoding op_fetch_response / op_sql_response. Only meaningful within a
426
+ // single decode call: incomplete packets are re-decoded from scratch on
427
+ // a fresh XdrReader, and decodeResponse clears these at branch entry so
428
+ // nothing leaks between packets sharing a data event (issue #341).
429
+ /** opcode carried over for a resumed decode (vestigial, see connection.ts) */
430
+ r?: number | null;
431
+ /** partial fetch-op flag (vestigial) */
432
+ fop?: boolean;
433
+ /** fetch status of the current row batch (100 = end of cursor) */
434
+ fstatus?: number;
435
+ /** rows remaining in the current packet */
436
+ fcount?: number;
437
+ /** column index the row decode stopped at */
438
+ fcolumn?: number;
439
+ /** row currently being decoded (object or array) */
440
+ frow?: any;
441
+ /** rows decoded so far in this call */
442
+ frows?: any[];
443
+ /** cached object-row keys (column aliases, qualified when nestTables is set) */
444
+ fcols?: string[];
445
+ /** cached per-column table keys when nestTables === true */
446
+ ftables?: string[];
447
+
424
448
  constructor(buffer: Buffer) {
425
449
  this.buffer = buffer;
426
450
  this.pos = 0;
@@ -506,6 +530,13 @@ export class XdrReader {
506
530
  var len = this.readInt();
507
531
  if (!len)
508
532
  return;
533
+ // Firebird 2.5 sign-extends XDR opaque lengths above 32767: a
534
+ // 32768-byte array arrives as length 0xFFFF8000. A negative length
535
+ // is never valid, so recover the real length from the low 16 bits
536
+ // instead of corrupting the read position (issue #312 — hang when
537
+ // preparing a statement with very many parameters on FB 2.5).
538
+ if (len < 0)
539
+ len &= 0xFFFF;
509
540
  var r = this.buffer.slice(this.pos, this.pos + len);
510
541
  this.pos += align(len);
511
542
  return r;