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
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 = {
@@ -1,9 +1,23 @@
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
5
  import EventConnection from './eventConnection';
6
6
  import FbEventManager from './fbEventManager';
7
+ import makeQueryStream from './query-stream';
8
+ import type Connection from './connection';
9
+ import type Transaction from './transaction';
10
+ import type Statement from './statement';
11
+ import type { BatchCb, StatementCb, InternalQueryOptions } from './wire-types';
12
+ import type { BatchOptions, BatchResult, Isolation, QueryParams, QueryStreamOptions, TransactionCallback, TransactionOptions } from '../types';
13
+
14
+ /** Callback for startTransaction: the internal optional-args shape, or the
15
+ * public TransactionCallback (non-optional transaction) from types.ts. */
16
+ type TransactionCb = Callback<Transaction> | TransactionCallback;
17
+
18
+ /** startTransaction options: resolved options object, a bare isolation
19
+ * array, or omitted entirely (callback in the options position). */
20
+ type TransactionArg = TransactionOptions | Isolation | TransactionCb | undefined;
7
21
 
8
22
  /***************************************
9
23
  *
@@ -40,7 +54,7 @@ import FbEventManager from './fbEventManager';
40
54
  *
41
55
  ***************************************/
42
56
 
43
- function readblob(blob: any, callback: (err: any, data?: any) => void): void {
57
+ function readblob(blob: any, callback: Callback): void {
44
58
  if (blob === undefined || blob === null) {
45
59
  callback(null, blob);
46
60
  return;
@@ -80,7 +94,7 @@ function readblob(blob: any, callback: (err: any, data?: any) => void): void {
80
94
  });
81
95
  }
82
96
 
83
- function fetchBlobSyncRow(row: any, meta: any[], callback: (err: any, row?: any) => void): void {
97
+ function fetchBlobSyncRow(row: any, meta: any[], callback: Callback): void {
84
98
  if (!row || !meta || !meta.length) {
85
99
  callback(null, row);
86
100
  return;
@@ -118,10 +132,10 @@ function fetchBlobSyncRow(row: any, meta: any[], callback: (err: any, row?: any)
118
132
  }
119
133
 
120
134
  class Database extends Events.EventEmitter {
121
- connection: any;
135
+ connection: Connection;
122
136
  eventid: number;
123
137
 
124
- constructor(connection: any) {
138
+ constructor(connection: Connection) {
125
139
  super();
126
140
  this.connection = connection;
127
141
  connection.db = this;
@@ -132,7 +146,7 @@ class Database extends Events.EventEmitter {
132
146
  return escape(value, this.connection.accept.protocolVersion);
133
147
  }
134
148
 
135
- detach(callback?: (err?: any, obj?: any) => void, force?: boolean): this {
149
+ detach(callback?: Callback, force?: boolean): this {
136
150
  var self = this;
137
151
 
138
152
  if (!force && self.connection._pending.length > 0) {
@@ -150,7 +164,7 @@ class Database extends Events.EventEmitter {
150
164
  if (callback)
151
165
  callback(err, obj);
152
166
 
153
- }, force);
167
+ });
154
168
  } else {
155
169
  self.emit('detach', false);
156
170
  if (callback)
@@ -160,32 +174,35 @@ class Database extends Events.EventEmitter {
160
174
  return self;
161
175
  }
162
176
 
163
- transaction(options: any, callback?: (err: any, transaction?: any) => void): this {
177
+ transaction(options: TransactionArg, callback?: TransactionCb): this {
164
178
  return this.startTransaction(options, callback);
165
179
  }
166
180
 
167
- startTransaction(options: any, callback?: (err: any, transaction?: any) => void): this {
181
+ startTransaction(options: TransactionArg, callback?: TransactionCb): this {
168
182
  this.connection.startTransaction(options, callback);
169
183
  return this;
170
184
  }
171
185
 
172
- newStatement(query: string, callback: (err: any, statement?: any) => void): this {
173
- this.startTransaction(function(err: any, transaction: any) {
186
+ newStatement(query: string, callback: StatementCb): this {
187
+ // the public strict callback shape and the internal optional-args
188
+ // shape only differ in optionality; treat it as the internal one
189
+ const cb = callback as Callback<Statement>;
190
+ this.startTransaction(function(err: any, transaction?: Transaction) {
174
191
 
175
- if (err) {
176
- callback(err);
192
+ if (err || !transaction) {
193
+ cb(err);
177
194
  return;
178
195
  }
179
196
 
180
- transaction.newStatement(query, function(err: any, statement: any) {
197
+ transaction.newStatement(query, function(err: any, statement?: Statement) {
181
198
 
182
199
  if (err) {
183
- callback(err);
200
+ cb(err);
184
201
  return;
185
202
  }
186
203
 
187
204
  transaction.commit(function(err: any) {
188
- callback(err, statement);
205
+ cb(err, statement);
189
206
  });
190
207
  });
191
208
  });
@@ -193,7 +210,7 @@ class Database extends Events.EventEmitter {
193
210
  return this;
194
211
  }
195
212
 
196
- execute(query: string, params?: any, callback?: any, options?: any): this {
213
+ execute(query: string, params?: QueryParams | Callback, callback?: any, options?: InternalQueryOptions): this {
197
214
  if (params instanceof Function) {
198
215
  options = callback;
199
216
  callback = params;
@@ -202,9 +219,9 @@ class Database extends Events.EventEmitter {
202
219
 
203
220
  var self = this;
204
221
 
205
- self.connection.startTransaction(function(err: any, transaction: any) {
222
+ self.connection.startTransaction(function(err: any, transaction?: Transaction) {
206
223
 
207
- if (err) {
224
+ if (err || !transaction) {
208
225
  doError(err, callback);
209
226
  return;
210
227
  }
@@ -238,17 +255,17 @@ class Database extends Events.EventEmitter {
238
255
  * err.batchCompletion. Use transaction.executeBatch for partial-success
239
256
  * handling.
240
257
  */
241
- executeBatch(query: string, rows: any[][], callback?: any, options?: any): this {
258
+ executeBatch(query: string, rows: QueryParams[], callback?: BatchCb, options?: BatchOptions): this {
242
259
  var self = this;
243
260
 
244
- self.connection.startTransaction(function(err: any, transaction: any) {
245
- if (err) {
261
+ self.connection.startTransaction(function(err: any, transaction?: Transaction) {
262
+ if (err || !transaction) {
246
263
  doError(err, callback);
247
264
  return;
248
265
  }
249
266
 
250
- transaction.executeBatch(query, rows, function(err: any, result: any) {
251
- if (err) {
267
+ transaction.executeBatch(query, rows, function(err: any, result?: BatchResult) {
268
+ if (err || !result) {
252
269
  transaction.rollback(function() {
253
270
  doError(err, callback);
254
271
  });
@@ -277,7 +294,7 @@ class Database extends Events.EventEmitter {
277
294
  return self;
278
295
  }
279
296
 
280
- sequentially(query: string, params?: any, on?: any, callback?: any, options: any = {}): this {
297
+ sequentially(query: string, params?: any, on?: any, callback?: any, options: InternalQueryOptions | boolean = {}): this {
281
298
  if (params instanceof Function) {
282
299
  options = callback;
283
300
  callback = on;
@@ -295,7 +312,7 @@ class Database extends Events.EventEmitter {
295
312
  }
296
313
 
297
314
  var self = this;
298
- var _on = function(row: any, i: number, meta: any, next: (err?: any) => void) {
315
+ var _on = function(row: any, i: number, meta: any[], next: (err?: any) => void) {
299
316
  var done = false;
300
317
  var finish = function(err?: any) {
301
318
  if (done) {
@@ -348,7 +365,20 @@ class Database extends Events.EventEmitter {
348
365
  return self;
349
366
  }
350
367
 
351
- query(query: string, params?: any, callback?: any, options: any = {}): this {
368
+ /**
369
+ * Run `query` and return an object-mode Readable emitting one row per
370
+ * chunk (what pg-query-stream / mysql2 .stream() return), with real
371
+ * backpressure: fetching pauses while the stream buffer is full. Runs
372
+ * in its own transaction, like db.query. Destroying the stream early
373
+ * (e.g. a pipeline() teardown) aborts the fetch and releases the
374
+ * statement. Rows go through the regular decode path, so typeCast,
375
+ * blobAsText and jsonAsObject all apply.
376
+ */
377
+ queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions) {
378
+ return makeQueryStream(this, query, params, options);
379
+ }
380
+
381
+ query(query: string, params?: QueryParams | Callback, callback?: any, options: InternalQueryOptions = {}): this {
352
382
  if (params instanceof Function) {
353
383
  options = callback || {};
354
384
  callback = params;
@@ -366,7 +396,7 @@ class Database extends Events.EventEmitter {
366
396
  return self;
367
397
  }
368
398
 
369
- drop(callback?: (err?: any) => void): void {
399
+ drop(callback?: SimpleCallback): void {
370
400
  return this.connection.dropDatabase(callback);
371
401
  }
372
402
 
@@ -377,7 +407,7 @@ class Database extends Events.EventEmitter {
377
407
  * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
378
408
  * per-attachment, not per-statement.
379
409
  */
380
- cancel(kind?: number | ((err?: any) => void), callback?: (err?: any) => void): this {
410
+ cancel(kind?: number | SimpleCallback, callback?: SimpleCallback): this {
381
411
  if (typeof kind === 'function') {
382
412
  callback = kind;
383
413
  kind = undefined;
@@ -386,7 +416,7 @@ class Database extends Events.EventEmitter {
386
416
  return this;
387
417
  }
388
418
 
389
- attachEvent(callback: (err: any, evt?: any) => void): this {
419
+ attachEvent(callback: Callback<FbEventManager>): this {
390
420
  var self = this;
391
421
  const eventid = self.eventid++;
392
422
  if (process.env.FIREBIRD_DEBUG) {
@@ -450,7 +480,7 @@ class Database extends Events.EventEmitter {
450
480
  * @param {function} [callback] - Asynchronous completion callback.
451
481
  * @returns {Database}
452
482
  */
453
- createTablespace(name: string, filePath: string, callback?: any): this {
483
+ createTablespace(name: string, filePath: string, callback?: Callback): this {
454
484
  const sql = `CREATE TABLESPACE ${name} FILE '${filePath}'`;
455
485
  return this.execute(sql, [], callback);
456
486
  }
@@ -464,7 +494,7 @@ class Database extends Events.EventEmitter {
464
494
  * @param {function} [callback] - Asynchronous completion callback.
465
495
  * @returns {Database}
466
496
  */
467
- alterTablespace(name: string, filePath: string, callback?: any): this {
497
+ alterTablespace(name: string, filePath: string, callback?: Callback): this {
468
498
  const sql = `ALTER TABLESPACE ${name} SET FILE TO '${filePath}'`;
469
499
  return this.execute(sql, [], callback);
470
500
  }
@@ -477,7 +507,7 @@ class Database extends Events.EventEmitter {
477
507
  * @param {function} [callback] - Asynchronous completion callback.
478
508
  * @returns {Database}
479
509
  */
480
- dropTablespace(name: string, callback?: any): this {
510
+ dropTablespace(name: string, callback?: Callback): this {
481
511
  const sql = `DROP TABLESPACE ${name}`;
482
512
  return this.execute(sql, [], callback);
483
513
  }
@@ -492,7 +522,7 @@ class Database extends Events.EventEmitter {
492
522
  * @param {function} [callback] - Asynchronous completion callback.
493
523
  * @returns {Database}
494
524
  */
495
- createSchema(schemaName: string, tablespaceName?: string | ((err?: any) => void), callback?: any): this {
525
+ createSchema(schemaName: string, tablespaceName?: string | Callback, callback?: Callback): this {
496
526
  if (typeof tablespaceName === 'function') {
497
527
  callback = tablespaceName;
498
528
  tablespaceName = undefined;
@@ -511,22 +541,24 @@ class Database extends Events.EventEmitter {
511
541
  * callback API — the promises resolve with the rows alone.
512
542
  */
513
543
 
514
- queryAsync(query: string, params?: any, options?: any): Promise<any[]> {
544
+ queryAsync(query: string, params?: QueryParams, options?: InternalQueryOptions): Promise<any[]> {
515
545
  var self = this;
516
546
  return fromCallback(function(cb) { self.query(query, params, cb, options); });
517
547
  }
518
548
 
519
- executeAsync(query: string, params?: any, options?: any): Promise<any[]> {
549
+ executeAsync(query: string, params?: QueryParams, options?: InternalQueryOptions): Promise<any[]> {
520
550
  var self = this;
521
551
  return fromCallback(function(cb) { self.execute(query, params, cb, options); });
522
552
  }
523
553
 
524
- executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any> {
554
+ executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult> {
525
555
  var self = this;
526
556
  return fromCallback(function(cb) { self.executeBatch(query, rows, cb, options); });
527
557
  }
528
558
 
529
- sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void> {
559
+ /** `on` may hold the options when the params argument is the row callback
560
+ * (public overload: sequentiallyAsync(query, rowCallback, options)). */
561
+ sequentiallyAsync(query: string, params?: any, on?: any, options?: InternalQueryOptions | boolean): Promise<void> {
530
562
  if (params instanceof Function) {
531
563
  options = on;
532
564
  on = params;
@@ -536,16 +568,16 @@ class Database extends Events.EventEmitter {
536
568
  return fromCallback(function(cb) { self.sequentially(query, params, on, cb, options); });
537
569
  }
538
570
 
539
- transactionAsync(options?: any): Promise<any> {
571
+ transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction> {
540
572
  var self = this;
541
573
  return fromCallback(function(cb) { self.startTransaction(options, cb); });
542
574
  }
543
575
 
544
- startTransactionAsync(options?: any): Promise<any> {
576
+ startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction> {
545
577
  return this.transactionAsync(options);
546
578
  }
547
579
 
548
- newStatementAsync(query: string): Promise<any> {
580
+ newStatementAsync(query: string): Promise<Statement> {
549
581
  var self = this;
550
582
  return fromCallback(function(cb) { self.newStatement(query, cb); });
551
583
  }
@@ -560,7 +592,7 @@ class Database extends Events.EventEmitter {
560
592
  return fromCallback(function(cb) { self.drop(cb); });
561
593
  }
562
594
 
563
- attachEventAsync(): Promise<any> {
595
+ attachEventAsync(): Promise<FbEventManager> {
564
596
  var self = this;
565
597
  return fromCallback(function(cb) { self.attachEvent(cb); });
566
598
  }
@@ -575,7 +607,7 @@ class Database extends Events.EventEmitter {
575
607
  * resolves, rolls back when it rejects (the original error is rethrown,
576
608
  * even if the rollback itself fails).
577
609
  */
578
- async withTransaction<T>(work: (transaction: any) => Promise<T> | T, options?: any): Promise<T> {
610
+ async withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T> {
579
611
  const transaction = await this.transactionAsync(options);
580
612
  try {
581
613
  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,28 @@ 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) */
444
+ fcols?: string[];
445
+
424
446
  constructor(buffer: Buffer) {
425
447
  this.buffer = buffer;
426
448
  this.pos = 0;
@@ -506,6 +528,13 @@ export class XdrReader {
506
528
  var len = this.readInt();
507
529
  if (!len)
508
530
  return;
531
+ // Firebird 2.5 sign-extends XDR opaque lengths above 32767: a
532
+ // 32768-byte array arrives as length 0xFFFF8000. A negative length
533
+ // is never valid, so recover the real length from the low 16 bits
534
+ // instead of corrupting the read position (issue #312 — hang when
535
+ // preparing a statement with very many parameters on FB 2.5).
536
+ if (len < 0)
537
+ len &= 0xFFFF;
509
538
  var r = this.buffer.slice(this.pos, this.pos + len);
510
539
  this.pos += align(len);
511
540
  return r;