firebird-wasm 0.9.0 → 0.10.1

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 (75) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/dist/.tsbuildinfo.build +1 -1
  3. package/dist/browser/engine-transport.d.ts +19 -0
  4. package/dist/browser/engine-transport.d.ts.map +1 -1
  5. package/dist/browser/engine-transport.js +67 -0
  6. package/dist/browser/engine-transport.js.map +1 -1
  7. package/dist/browser/firebird-browser.d.ts +44 -1
  8. package/dist/browser/firebird-browser.d.ts.map +1 -1
  9. package/dist/browser/firebird-browser.js +123 -0
  10. package/dist/browser/firebird-browser.js.map +1 -1
  11. package/dist/browser/index.d.ts +1 -1
  12. package/dist/browser/index.d.ts.map +1 -1
  13. package/dist/browser/opfs-fs.d.ts.map +1 -1
  14. package/dist/browser/opfs-fs.js +18 -7
  15. package/dist/browser/opfs-fs.js.map +1 -1
  16. package/dist/browser/shared-transport.d.ts +3 -0
  17. package/dist/browser/shared-transport.d.ts.map +1 -1
  18. package/dist/browser/shared-transport.js +9 -0
  19. package/dist/browser/shared-transport.js.map +1 -1
  20. package/dist/browser/worker-protocol.d.ts +1 -1
  21. package/dist/browser/worker-protocol.d.ts.map +1 -1
  22. package/dist/browser/worker-transport.d.ts +3 -0
  23. package/dist/browser/worker-transport.d.ts.map +1 -1
  24. package/dist/browser/worker-transport.js +9 -0
  25. package/dist/browser/worker-transport.js.map +1 -1
  26. package/dist/drizzle/index.d.ts +170 -0
  27. package/dist/drizzle/index.d.ts.map +1 -0
  28. package/dist/drizzle/index.js +627 -0
  29. package/dist/drizzle/index.js.map +1 -0
  30. package/dist/electric/apply.d.ts +1 -0
  31. package/dist/electric/apply.d.ts.map +1 -1
  32. package/dist/electric/apply.js +28 -8
  33. package/dist/electric/apply.js.map +1 -1
  34. package/dist/electric/index.js +6 -1
  35. package/dist/electric/index.js.map +1 -1
  36. package/dist/electric/values.d.ts.map +1 -1
  37. package/dist/electric/values.js +5 -2
  38. package/dist/electric/values.js.map +1 -1
  39. package/dist/electric-server/catalog.d.ts.map +1 -1
  40. package/dist/electric-server/catalog.js +3 -2
  41. package/dist/electric-server/catalog.js.map +1 -1
  42. package/dist/electric-server/index.d.ts +19 -0
  43. package/dist/electric-server/index.d.ts.map +1 -1
  44. package/dist/electric-server/index.js +147 -14
  45. package/dist/electric-server/index.js.map +1 -1
  46. package/dist/firebird.d.ts +48 -9
  47. package/dist/firebird.d.ts.map +1 -1
  48. package/dist/firebird.js +927 -60
  49. package/dist/firebird.js.map +1 -1
  50. package/dist/index.d.ts +4 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +7 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/kysely/index.d.ts +197 -0
  55. package/dist/kysely/index.d.ts.map +1 -0
  56. package/dist/kysely/index.js +777 -0
  57. package/dist/kysely/index.js.map +1 -0
  58. package/dist/sql-tag.d.ts +5 -16
  59. package/dist/sql-tag.d.ts.map +1 -1
  60. package/dist/sql-tag.js +15 -0
  61. package/dist/sql-tag.js.map +1 -1
  62. package/dist/types.d.ts +24 -3
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.d.ts.map +1 -1
  66. package/dist/version.js +1 -1
  67. package/dist/version.js.map +1 -1
  68. package/dist/wasm/firebird-embedded-st.js +1 -1
  69. package/dist/wasm/firebird-embedded-st.wasm +0 -0
  70. package/dist/wasm/firebird-embedded.js +1 -1
  71. package/dist/wasm/firebird-embedded.wasm +0 -0
  72. package/dist/wasm-loader.d.ts +16 -0
  73. package/dist/wasm-loader.d.ts.map +1 -1
  74. package/dist/wasm-loader.js.map +1 -1
  75. package/package.json +21 -3
package/dist/firebird.js CHANGED
@@ -1,10 +1,47 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.FirebirdTransaction = exports.FirebirdLite = void 0;
37
+ const os = __importStar(require("os"));
4
38
  const node_firebird_driver_1 = require("node-firebird-driver");
5
39
  const statement_types_1 = require("./statement-types");
40
+ const field_types_1 = require("./browser/field-types");
41
+ const value_types_1 = require("./browser/value-types");
6
42
  const sql_tag_1 = require("./sql-tag");
7
43
  const batch_block_1 = require("./batch-block");
44
+ const littleEndian = os.endianness() === 'LE';
8
45
  /** `isc_info_sql_stmt_select`: the statement type the shared read transaction runs. */
9
46
  const STMT_SELECT = 1;
10
47
  let nativeDriver;
@@ -71,6 +108,10 @@ class FirebirdLite {
71
108
  throw new RangeError('statementCache is a whole number of statements, 0 or more');
72
109
  }
73
110
  this.statementCacheSize = size;
111
+ this.types = options.types;
112
+ }
113
+ serialize(params) {
114
+ return (0, value_types_1.applySerializers)(params, this.types?.serializers);
74
115
  }
75
116
  /**
76
117
  * Lazily initialize the native client and open (or create) the database.
@@ -130,22 +171,22 @@ class FirebirdLite {
130
171
  return shared;
131
172
  }
132
173
  await this.endReadTransaction();
174
+ const serializedParams = this.serialize(statement.params);
133
175
  const txOptions = buildTransactionOptions(options);
134
176
  const transaction = await attachment.startTransaction(txOptions);
177
+ let rows;
178
+ let fields;
135
179
  try {
136
180
  const lease = await this.statements.acquire(transaction, statement.sql);
137
181
  const stmt = lease.stmt;
138
- let rows;
139
- let fields;
140
182
  // Released in `finally`, not after the fetch: a statement that throws on
141
183
  // execute — a lock conflict, an overflow, a wrong parameter count — would
142
184
  // otherwise leak its handle, and the retry loop such failures are usually
143
185
  // wrapped in leaks one per attempt until the attachment refuses more.
144
186
  let failed = true;
145
187
  try {
146
- const columnLabels = await stmt.columnLabels;
147
- fields = columnLabels.map((label) => ({ name: label }));
148
- rows = shapeRows(await fetchRows(stmt, transaction, statement.params, fields.length), fields, rowMode);
188
+ fields = await outputFields(stmt);
189
+ rows = shapeRows(await fetchRows(stmt, transaction, serializedParams, fields, this.types), fields, rowMode);
149
190
  failed = false;
150
191
  }
151
192
  finally {
@@ -154,12 +195,70 @@ class FirebirdLite {
154
195
  // After the statement is disposed, as before — the commit is the last
155
196
  // thing that happens on the success path.
156
197
  await transaction.commit();
157
- return { rows, fields };
158
198
  }
159
199
  catch (err) {
160
200
  await transaction.rollback().catch(() => undefined);
161
201
  throw err;
162
202
  }
203
+ return (0, value_types_1.applyTypes)({ rows, fields }, this.types, rowMode);
204
+ }
205
+ queryStream(sql, paramsOrOptions = [], maybeOptions = {}) {
206
+ const { statement, options } = (0, sql_tag_1.resolveQueryCall)(sql, paramsOrOptions, maybeOptions);
207
+ const batchSize = (0, sql_tag_1.resolveBatchSize)(options.batchSize);
208
+ return this.streamQuery(statement, options, batchSize);
209
+ }
210
+ async *streamQuery(statement, options, batchSize) {
211
+ await this.ensureReady();
212
+ const rowMode = options.rowMode ?? 'object';
213
+ const attachment = this.attachment;
214
+ await this.endReadTransaction();
215
+ const serializedParams = this.serialize(statement.params);
216
+ const txOptions = buildTransactionOptions(options);
217
+ const transaction = await attachment.startTransaction(txOptions);
218
+ let engineFailed = false;
219
+ let lease;
220
+ let txFinished = false;
221
+ try {
222
+ try {
223
+ lease = await this.statements.acquire(transaction, statement.sql);
224
+ }
225
+ catch (err) {
226
+ engineFailed = true;
227
+ throw err;
228
+ }
229
+ const stmt = lease.stmt;
230
+ let fields;
231
+ try {
232
+ fields = await outputFields(stmt);
233
+ }
234
+ catch (err) {
235
+ engineFailed = true;
236
+ throw err;
237
+ }
238
+ yield* streamRows(stmt, transaction, serializedParams, fields, rowMode, batchSize, this.types, () => {
239
+ engineFailed = true;
240
+ });
241
+ const toRelease = lease;
242
+ lease = undefined;
243
+ await toRelease.release(false);
244
+ txFinished = true;
245
+ await transaction.commit();
246
+ }
247
+ finally {
248
+ if (lease) {
249
+ await lease.release(engineFailed);
250
+ }
251
+ if (!txFinished) {
252
+ if (engineFailed) {
253
+ await transaction.rollback().catch(() => undefined);
254
+ }
255
+ else {
256
+ await transaction
257
+ .commit()
258
+ .catch(() => transaction.rollback().catch(() => undefined));
259
+ }
260
+ }
261
+ }
163
262
  }
164
263
  /**
165
264
  * Run a SELECT in the shared read-only transaction, starting it if need be.
@@ -171,6 +270,7 @@ class FirebirdLite {
171
270
  * next read starts afresh, and is thrown.
172
271
  */
173
272
  async sharedRead(sql, params, rowMode) {
273
+ const serializedParams = this.serialize(params);
174
274
  const attachment = this.attachment;
175
275
  this.readTransaction ?? (this.readTransaction = await attachment.startTransaction({
176
276
  isolation: node_firebird_driver_1.TransactionIsolation.READ_COMMITTED,
@@ -189,16 +289,15 @@ class FirebirdLite {
189
289
  const stmt = lease.stmt;
190
290
  // A writing SELECT stays kept: only the transaction was wrong for it.
191
291
  let failed = false;
292
+ let fields;
293
+ let rawRows;
192
294
  try {
193
295
  if ((await stmt.type) !== STMT_SELECT) {
194
296
  this.remember(sql);
195
297
  return undefined;
196
298
  }
197
- const fields = (await stmt.columnLabels).map((label) => ({
198
- name: label,
199
- }));
200
- const rawRows = await fetchRows(stmt, transaction, params, fields.length);
201
- return { rows: shapeRows(rawRows, fields, rowMode), fields };
299
+ fields = await outputFields(stmt);
300
+ rawRows = await fetchRows(stmt, transaction, serializedParams, fields, this.types);
202
301
  }
203
302
  catch (err) {
204
303
  // The native driver's errors carry only the message text.
@@ -213,6 +312,7 @@ class FirebirdLite {
213
312
  finally {
214
313
  await lease.release(failed);
215
314
  }
315
+ return (0, value_types_1.applyTypes)({ rows: shapeRows(rawRows, fields, rowMode), fields }, this.types, rowMode);
216
316
  }
217
317
  remember(sql) {
218
318
  if (this.ownTransaction.size >= 1024)
@@ -239,19 +339,16 @@ class FirebirdLite {
239
339
  *
240
340
  * ```ts
241
341
  * const shape = await db.describeQuery('SELECT id, name FROM items WHERE id = ?');
242
- * // shape.fields → [{ name: 'ID' }, { name: 'NAME' }]
342
+ * // shape.fields → [{ name: 'ID', type: 496, typeName: 'INTEGER', ... }, ...]
243
343
  * // shape.statementType → 'SELECT'
244
344
  * ```
245
345
  *
246
346
  * The statement is prepared and disposed, so nothing runs: describing an
247
347
  * `INSERT` inserts nothing.
248
348
  *
249
- * **`params` is `undefined` here.** The native driver exposes no input
250
- * metadata — `Statement` offers `columnLabels`, `hasResultSet` and `type`
251
- * and nothing about `?` placeholders — and an empty array would claim the
252
- * statement takes none. `fields` carries names only, for the same reason
253
- * `FieldInfo` does everywhere on this backend. The WASM backend reports both
254
- * in full.
349
+ * **`params` is `undefined` here.** The native driver exposes no public input
350
+ * metadata, and an empty array would claim the statement takes none. `fields`
351
+ * carries the full {@link FieldInfo} from the prepared output metadata.
255
352
  */
256
353
  async describeQuery(sql) {
257
354
  await this.ensureReady();
@@ -261,11 +358,11 @@ class FirebirdLite {
261
358
  try {
262
359
  const stmt = await attachment.prepare(transaction, text);
263
360
  try {
264
- const columnLabels = await stmt.columnLabels;
361
+ const fields = await outputFields(stmt);
265
362
  const type = await stmt.type;
266
363
  return {
267
364
  params: undefined,
268
- fields: columnLabels.map((label) => ({ name: label })),
365
+ fields,
269
366
  statementType: (0, statement_types_1.statementKind)(type),
270
367
  hasResultSet: stmt.hasResultSet,
271
368
  };
@@ -294,7 +391,7 @@ class FirebirdLite {
294
391
  const transaction = await attachment.startTransaction(txOptions);
295
392
  // A shared read made from inside the callback starts the shared
296
393
  // transaction again; each statement here ends it first.
297
- const tx = new FirebirdTransaction(attachment, transaction, () => this.endReadTransaction(), this.statements ?? undefined);
394
+ const tx = new FirebirdTransaction(attachment, transaction, () => this.endReadTransaction(), this.statements ?? undefined, this.types);
298
395
  let result;
299
396
  try {
300
397
  result = await fn(tx);
@@ -340,13 +437,17 @@ exports.FirebirdLite = FirebirdLite;
340
437
  * `FirebirdLite.transaction()`.
341
438
  */
342
439
  class FirebirdTransaction {
343
- constructor(attachment, transaction, beforeStatement = async () => undefined, statements = new StatementCache(attachment, 0)) {
440
+ constructor(attachment, transaction, beforeStatement = async () => undefined, statements = new StatementCache(attachment, 0), types) {
344
441
  this.attachment = attachment;
345
442
  this.transaction = transaction;
346
443
  this.beforeStatement = beforeStatement;
347
444
  this.statements = statements;
445
+ this.types = types;
348
446
  this.finished = false;
349
447
  }
448
+ serialize(params) {
449
+ return (0, value_types_1.applySerializers)(params, this.types?.serializers);
450
+ }
350
451
  /** Whether this transaction has already been rolled back. */
351
452
  get isFinished() {
352
453
  return this.finished;
@@ -376,14 +477,15 @@ class FirebirdTransaction {
376
477
  this.assertUsable();
377
478
  await this.beforeStatement();
378
479
  const statement = (0, sql_tag_1.toStatement)(sql, params);
480
+ const serialized = this.serialize(statement.params);
379
481
  // With the cache off, a statement without parameters runs as it always
380
482
  // did. With it on, every statement goes through the cache, which is also
381
483
  // what sees DDL coming and empties itself first.
382
- if (statement.params.length > 0 || this.statements.enabled) {
484
+ if (serialized.length > 0 || this.statements.enabled) {
383
485
  const lease = await this.statements.acquire(this.transaction, statement.sql);
384
486
  let failed = true;
385
487
  try {
386
- await lease.stmt.execute(this.transaction, await bindParams(lease.stmt, this.transaction, statement.params));
488
+ await lease.stmt.execute(this.transaction, await bindParams(lease.stmt, this.transaction, serialized));
387
489
  failed = false;
388
490
  }
389
491
  finally {
@@ -406,15 +508,23 @@ class FirebirdTransaction {
406
508
  }
407
509
  if (rows.length === 0)
408
510
  return;
511
+ const serializedRows = this.types?.serializers?.length
512
+ ? rows.map((row) => this.serialize(row))
513
+ : rows;
409
514
  const lease = await this.statements.acquire(this.transaction, sql);
410
515
  let failed = true;
411
516
  try {
412
- const plan = rows.length > 1 ? await this.blockPlan(sql, lease.stmt) : undefined;
517
+ if (serializedRows.length > 1 &&
518
+ (await tryExecuteNativeBatch(lease.stmt, this.transaction, serializedRows))) {
519
+ failed = false;
520
+ return;
521
+ }
522
+ const plan = serializedRows.length > 1 ? await this.blockPlan(sql, lease.stmt) : undefined;
413
523
  if (plan) {
414
- await this.executeInBlocks(lease.stmt, plan, rows);
524
+ await this.executeInBlocks(lease.stmt, plan, serializedRows);
415
525
  }
416
526
  else {
417
- for (const row of rows) {
527
+ for (const row of serializedRows) {
418
528
  await lease.stmt.execute(this.transaction, await bindParams(lease.stmt, this.transaction, row));
419
529
  }
420
530
  }
@@ -434,7 +544,7 @@ class FirebirdTransaction {
434
544
  // INSERT, UPDATE, DELETE, EXECUTE PROCEDURE; nothing with a result.
435
545
  if (![2, 3, 4, 8].includes(type) || stmt.hasResultSet)
436
546
  return undefined;
437
- if ((await stmt.columnLabels).length > 0)
547
+ if ((await outputFields(stmt)).length > 0)
438
548
  return undefined;
439
549
  const params = await inputParams(stmt);
440
550
  const offsets = (0, batch_block_1.placeholderOffsets)(sql);
@@ -500,6 +610,7 @@ class FirebirdTransaction {
500
610
  this.assertUsable();
501
611
  await this.beforeStatement();
502
612
  const statement = (0, sql_tag_1.toStatement)(sql, params);
613
+ const serialized = this.serialize(statement.params);
503
614
  const rowMode = options.rowMode ?? 'object';
504
615
  const lease = await this.statements.acquire(this.transaction, statement.sql);
505
616
  const stmt = lease.stmt;
@@ -507,18 +618,47 @@ class FirebirdTransaction {
507
618
  // throws must still be released. More so here, because a transaction
508
619
  // callback that catches its own errors goes on using the same attachment.
509
620
  let failed = true;
621
+ let fields;
622
+ let rawRows;
510
623
  try {
511
- const columnLabels = await stmt.columnLabels;
512
- const fields = columnLabels.map((label) => ({
513
- name: label,
514
- }));
515
- const rawRows = await fetchRows(stmt, this.transaction, statement.params, fields.length);
624
+ fields = await outputFields(stmt);
625
+ rawRows = await fetchRows(stmt, this.transaction, serialized, fields, this.types);
516
626
  failed = false;
517
- return { rows: shapeRows(rawRows, fields, rowMode), fields };
518
627
  }
519
628
  finally {
520
629
  await lease.release(failed);
521
630
  }
631
+ return (0, value_types_1.applyTypes)({ rows: shapeRows(rawRows, fields, rowMode), fields }, this.types, rowMode);
632
+ }
633
+ queryStream(sql, paramsOrOptions = [], maybeOptions = {}) {
634
+ this.assertUsable();
635
+ const { statement, options } = (0, sql_tag_1.resolveQueryCall)(sql, paramsOrOptions, maybeOptions);
636
+ const batchSize = (0, sql_tag_1.resolveBatchSize)(options.batchSize);
637
+ return this.streamQuery(statement, options.rowMode ?? 'object', batchSize);
638
+ }
639
+ async *streamQuery(statement, rowMode, batchSize) {
640
+ this.assertUsable();
641
+ await this.beforeStatement();
642
+ const serialized = this.serialize(statement.params);
643
+ let engineFailed = false;
644
+ const lease = await this.statements.acquire(this.transaction, statement.sql);
645
+ const stmt = lease.stmt;
646
+ try {
647
+ let fields;
648
+ try {
649
+ fields = await outputFields(stmt);
650
+ }
651
+ catch (err) {
652
+ engineFailed = true;
653
+ throw err;
654
+ }
655
+ yield* streamRows(stmt, this.transaction, serialized, fields, rowMode, batchSize, this.types, () => {
656
+ engineFailed = true;
657
+ });
658
+ }
659
+ finally {
660
+ await lease.release(engineFailed);
661
+ }
522
662
  }
523
663
  }
524
664
  exports.FirebirdTransaction = FirebirdTransaction;
@@ -535,10 +675,38 @@ async function bindingOf(stmt) {
535
675
  // Read before anything here rewrites the message; a statement is used
536
676
  // by one call at a time, so nothing else reads it meanwhile.
537
677
  binding = { params: await readInputParams(stmt), variants: new Map(), current: '' };
678
+ await setupOutput(stmt, binding);
538
679
  bindings.set(stmt, binding);
539
680
  }
540
681
  return binding;
541
682
  }
683
+ /**
684
+ * Return a fresh copy of `stmt`'s output column metadata (`FieldInfo[]`).
685
+ *
686
+ * When the native driver's prepared output metadata is available, each entry
687
+ * carries `type`, `typeName`, `subType`, `scale`, `length` and `nullable`,
688
+ * matching `FirebirdBrowser`. Falls back to `stmt.fields` (for unit test stubs)
689
+ * or `stmt.columnLabels` (names only).
690
+ */
691
+ async function outputFields(stmt) {
692
+ const binding = await bindingOf(stmt);
693
+ if (binding.fields) {
694
+ return binding.fields.map((f) => ({ ...f }));
695
+ }
696
+ const mockFields = stmt.fields;
697
+ if (Array.isArray(mockFields)) {
698
+ binding.fields = mockFields.map((f) => ({
699
+ ...f,
700
+ ...(f.type !== undefined && f.typeName === undefined
701
+ ? { typeName: (0, field_types_1.firebirdTypeName)(f.type, f.scale ?? 0) }
702
+ : {}),
703
+ }));
704
+ return binding.fields.map((f) => ({ ...f }));
705
+ }
706
+ const columnLabels = await stmt.columnLabels;
707
+ binding.fields = columnLabels.map((label) => ({ name: label }));
708
+ return binding.fields.map((f) => ({ ...f }));
709
+ }
542
710
  /**
543
711
  * A statement's `?` parameters: its input metadata as the driver rewrote it
544
712
  * (CHAR as VARCHAR, the zoned types in their `_EX` forms), except that an
@@ -594,25 +762,397 @@ async function readInputParams(stmt) {
594
762
  return undefined;
595
763
  }
596
764
  }
597
- /** `SQL_BLOB`, without the nullable bit. */
598
- const SQL_BLOB = 520;
599
- /** `SQL_DOUBLE`: what the driver binds every integer and NUMERIC as. */
765
+ // Firebird SQL type codes (without the nullable low bit).
766
+ const SQL_TEXT = 452;
767
+ const SQL_VARYING = 448;
768
+ const SQL_SHORT = 500;
769
+ const SQL_LONG = 496;
770
+ const SQL_FLOAT = 482;
600
771
  const SQL_DOUBLE = 480;
772
+ const SQL_D_FLOAT = 530;
773
+ const SQL_TIMESTAMP = 510;
774
+ const SQL_BLOB = 520;
775
+ const SQL_TYPE_TIME = 560;
776
+ const SQL_TYPE_DATE = 570;
777
+ const SQL_INT64 = 580;
778
+ const SQL_TIMESTAMP_TZ = 32754;
779
+ const SQL_TIME_TZ = 32756;
780
+ const SQL_INT128 = 32752;
781
+ const SQL_DEC16 = 32760;
782
+ const SQL_DEC34 = 32762;
783
+ const SQL_BOOLEAN = 32764;
784
+ const SQL_NULL = 32766;
601
785
  /** SMALLINT, INTEGER, BIGINT, and NUMERIC / DECIMAL kept in them. */
602
- const INTEGER_TYPES = new Set([500, 496, 580]);
603
- /** `SQL_VARYING`. */
604
- const SQL_VARYING = 448;
605
- /** Character set id of UTF8. */
786
+ const INTEGER_TYPES = new Set([SQL_SHORT, SQL_LONG, SQL_INT64]);
787
+ /** Character set ids from Firebird's `RDB$CHARACTER_SETS`. */
788
+ const CHARSET_OCTETS = 1;
789
+ const CS_UNICODE_FSS = 3;
606
790
  const CS_UTF8 = 4;
791
+ /** `Number.MAX_SAFE_INTEGER` as a `bigint`: beyond this, IEEE-754 doubles round. */
792
+ const SAFE_INT64 = 9007199254740991n;
793
+ /**
794
+ * Firebird 4+ output types whose binary format needs `IUtil` (`IInt128`,
795
+ * `IDecFloat16`/`34`, `decodeTimeTz`/`decodeTimeStampTz`): coerced in the
796
+ * output message to `SQL_VARYING` so Firebird formats them directly.
797
+ */
798
+ const COERCE_TO_VARYING = new Set([
799
+ SQL_INT128,
800
+ SQL_DEC16,
801
+ SQL_DEC34,
802
+ SQL_TIME_TZ,
803
+ SQL_TIMESTAMP_TZ,
804
+ ]);
607
805
  /**
608
806
  * DATE, TIME, TIMESTAMP, and TIME / TIMESTAMP WITH TIME ZONE in both forms
609
807
  * (the driver rewrites the zoned ones to their `_EX` forms).
610
808
  */
611
809
  const DATE_TIME_TYPES = new Set([570, 560, 510, 32756, 32754, 32750, 32748]);
612
810
  /** The types a string is sent to as text, for Firebird to convert. */
613
- const TEXT_TYPES = new Set([...DATE_TIME_TYPES, ...INTEGER_TYPES]);
811
+ const TEXT_TYPES = new Set([
812
+ ...DATE_TIME_TYPES,
813
+ ...INTEGER_TYPES,
814
+ SQL_INT128,
815
+ SQL_DEC16,
816
+ SQL_DEC34,
817
+ SQL_BOOLEAN,
818
+ ]);
614
819
  /** Bytes a value takes as text: a timestamp with the longest zone name fits twice. */
615
820
  const TEXT_LENGTH = 128;
821
+ /**
822
+ * Replace the driver's `outMetadata` and `dataReader` on `stmt` with ones built
823
+ * from Firebird's prepared output metadata.
824
+ *
825
+ * `node-firebird-driver-native`'s `fixMetadata` rewrites `SMALLINT`, `INTEGER`,
826
+ * `BIGINT`, `FLOAT`, `NUMERIC` and `DECIMAL` to `SQL_DOUBLE` (losing precision
827
+ * on `BIGINT` past 2^53 and turning exact `NUMERIC`/`DECIMAL` values into
828
+ * floats), decodes `DATE`/`TIME`/`TIMESTAMP` into local-timezone `Date`
829
+ * objects (truncating 100 µs fractions), and returns raw `Blob` handles that
830
+ * expire when `query()` commits its transaction. Keeping the prepared types and
831
+ * decoding them with the same rules as `fb_wasm_api.cpp` gives Node and
832
+ * browser queries identical wire values and `FieldInfo` metadata.
833
+ */
834
+ async function setupOutput(stmt, binding) {
835
+ const internal = stmt;
836
+ const client = internal.attachment?.client;
837
+ if (typeof internal.statementHandle?.getOutputMetadataAsync !== 'function' ||
838
+ typeof client?.statusAction !== 'function') {
839
+ return;
840
+ }
841
+ if (!internal.outMetadata) {
842
+ binding.fields = [];
843
+ return;
844
+ }
845
+ try {
846
+ await client.statusAction(async (status) => {
847
+ const prepared = await internal.statementHandle.getOutputMetadataAsync(status);
848
+ if (!prepared) {
849
+ binding.fields = [];
850
+ return;
851
+ }
852
+ try {
853
+ const count = prepared.getCountSync(status);
854
+ if (count === 0) {
855
+ binding.fields = [];
856
+ return;
857
+ }
858
+ const fields = [];
859
+ const builder = prepared.getBuilderSync(status);
860
+ let outMetadata;
861
+ try {
862
+ for (let i = 0; i < count; i++) {
863
+ const rawType = prepared.getTypeSync(status, i);
864
+ const type = rawType & ~1;
865
+ const subType = prepared.getSubTypeSync(status, i);
866
+ const scale = prepared.getScaleSync(status, i);
867
+ const length = prepared.getLengthSync(status, i);
868
+ const nullable = typeof prepared.isNullableSync === 'function'
869
+ ? Boolean(prepared.isNullableSync(status, i))
870
+ : (rawType & 1) !== 0;
871
+ const name = typeof prepared.getAliasSync === 'function'
872
+ ? prepared.getAliasSync(status, i)
873
+ : '';
874
+ fields.push({
875
+ name,
876
+ type,
877
+ typeName: (0, field_types_1.firebirdTypeName)(type, scale),
878
+ subType,
879
+ scale,
880
+ length,
881
+ nullable,
882
+ });
883
+ if (COERCE_TO_VARYING.has(type)) {
884
+ builder.setTypeSync(status, i, SQL_VARYING);
885
+ builder.setSubTypeSync(status, i, 0);
886
+ builder.setLengthSync(status, i, 64);
887
+ builder.setCharSetSync(status, i, CS_UTF8);
888
+ builder.setScaleSync(status, i, 0);
889
+ }
890
+ else if (type === SQL_D_FLOAT) {
891
+ builder.setTypeSync(status, i, SQL_DOUBLE);
892
+ builder.setLengthSync(status, i, 8);
893
+ builder.setScaleSync(status, i, 0);
894
+ }
895
+ }
896
+ outMetadata = builder.getMetadataSync(status);
897
+ }
898
+ finally {
899
+ builder.releaseSync();
900
+ }
901
+ const descriptors = [];
902
+ for (let i = 0; i < count; i++) {
903
+ descriptors.push({
904
+ msgType: outMetadata.getTypeSync(status, i) & ~1,
905
+ preparedType: fields[i].type,
906
+ subType: fields[i].subType ?? 0,
907
+ scale: fields[i].scale ?? 0,
908
+ length: outMetadata.getLengthSync(status, i),
909
+ preparedLength: fields[i].length ?? 0,
910
+ charSet: outMetadata.getCharSetSync(status, i),
911
+ preparedCharSet: prepared.getCharSetSync(status, i),
912
+ offset: outMetadata.getOffsetSync(status, i),
913
+ nullOffset: outMetadata.getNullOffsetSync(status, i),
914
+ });
915
+ }
916
+ binding.fields = fields;
917
+ binding.outDescriptors = descriptors;
918
+ binding.originalOut = {
919
+ outMetadata: internal.outMetadata,
920
+ outBuffer: internal.outBuffer,
921
+ dataReader: internal.dataReader,
922
+ };
923
+ binding.customOut = outMetadata;
924
+ hookDispose(internal, binding);
925
+ internal.outMetadata = outMetadata;
926
+ internal.outBuffer = new Uint8Array(outMetadata.getMessageLengthSync(status));
927
+ internal.dataReader = createOutputDataReader(descriptors);
928
+ }
929
+ finally {
930
+ prepared.releaseSync();
931
+ }
932
+ });
933
+ }
934
+ catch {
935
+ // Fall back to the driver's own output metadata if internals differ.
936
+ }
937
+ }
938
+ function createOutputDataReader(descriptors) {
939
+ return async (attachment, _transaction, buffer) => {
940
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
941
+ const row = new Array(descriptors.length);
942
+ for (let i = 0; i < descriptors.length; i++) {
943
+ const d = descriptors[i];
944
+ if (dataView.getInt16(d.nullOffset, littleEndian) === -1) {
945
+ row[i] = null;
946
+ }
947
+ else {
948
+ row[i] = readColumnValue(d, attachment, buffer, dataView);
949
+ }
950
+ }
951
+ return row;
952
+ };
953
+ }
954
+ function readColumnValue(d, attachment, buffer, dataView) {
955
+ switch (d.msgType) {
956
+ case SQL_TEXT: {
957
+ const raw = buffer.subarray(d.offset, d.offset + d.length);
958
+ if ((d.preparedCharSet & 0xff) === CHARSET_OCTETS) {
959
+ return Buffer.from(raw);
960
+ }
961
+ const byteLen = charTextLength(raw, d.length, d.preparedCharSet);
962
+ return decodeText(raw.subarray(0, byteLen), d.preparedCharSet, attachment);
963
+ }
964
+ case SQL_VARYING: {
965
+ const varLength = dataView.getUint16(d.offset, littleEndian);
966
+ const raw = buffer.subarray(d.offset + 2, d.offset + 2 + varLength);
967
+ if ((d.preparedCharSet & 0xff) === CHARSET_OCTETS) {
968
+ return Buffer.from(raw);
969
+ }
970
+ const text = decodeText(raw, d.charSet, attachment);
971
+ if (d.preparedType === SQL_TIMESTAMP_TZ) {
972
+ return text.replace(/^(\d{4}-\d{2}-\d{2}) /, '$1T');
973
+ }
974
+ return text;
975
+ }
976
+ case SQL_SHORT: {
977
+ const val = dataView.getInt16(d.offset, littleEndian);
978
+ return d.scale !== 0 ? formatScaledDecimal(BigInt(val), d.scale) : val;
979
+ }
980
+ case SQL_LONG: {
981
+ const val = dataView.getInt32(d.offset, littleEndian);
982
+ return d.scale !== 0 ? formatScaledDecimal(BigInt(val), d.scale) : val;
983
+ }
984
+ case SQL_INT64: {
985
+ const val = dataView.getBigInt64(d.offset, littleEndian);
986
+ if (d.scale !== 0)
987
+ return formatScaledDecimal(val, d.scale);
988
+ return val >= -SAFE_INT64 && val <= SAFE_INT64 ? Number(val) : val.toString();
989
+ }
990
+ case SQL_FLOAT: {
991
+ const val = dataView.getFloat32(d.offset, littleEndian);
992
+ return Number.isFinite(val) ? Number(val.toPrecision(9)) : null;
993
+ }
994
+ case SQL_DOUBLE: {
995
+ const val = dataView.getFloat64(d.offset, littleEndian);
996
+ return Number.isFinite(val) ? val : null;
997
+ }
998
+ case SQL_BOOLEAN:
999
+ return dataView.getInt8(d.offset) !== 0;
1000
+ case SQL_TYPE_DATE:
1001
+ return formatDate(dataView.getInt32(d.offset, littleEndian));
1002
+ case SQL_TYPE_TIME:
1003
+ return formatTime(dataView.getUint32(d.offset, littleEndian));
1004
+ case SQL_TIMESTAMP:
1005
+ return `${formatDate(dataView.getInt32(d.offset, littleEndian))}T${formatTime(dataView.getUint32(d.offset + 4, littleEndian))}`;
1006
+ case SQL_BLOB:
1007
+ return new node_firebird_driver_1.Blob(attachment, buffer.slice(d.offset, d.offset + 8));
1008
+ case SQL_NULL:
1009
+ default:
1010
+ return null;
1011
+ }
1012
+ }
1013
+ /**
1014
+ * How many bytes of a `CHAR(n)` buffer represent its `n` characters.
1015
+ * Mirrors `charTextLength` in `fb_wasm_api.cpp`.
1016
+ */
1017
+ function charTextLength(data, length, charSet) {
1018
+ const cs = charSet & 0xff;
1019
+ const bytesPerChar = cs === CS_UTF8 ? 4 : cs === CS_UNICODE_FSS ? 3 : 1;
1020
+ if (bytesPerChar === 1)
1021
+ return length;
1022
+ const chars = Math.floor(length / bytesPerChar);
1023
+ let pos = 0;
1024
+ for (let n = 0; n < chars && pos < length; n++) {
1025
+ const c = data[pos];
1026
+ pos += c < 0x80 ? 1 : (c >> 5) === 0x6 ? 2 : (c >> 4) === 0xe ? 3 : (c >> 3) === 0x1e ? 4 : 1;
1027
+ }
1028
+ return pos < length ? pos : length;
1029
+ }
1030
+ function decodeText(bytes, charSet, attachment) {
1031
+ const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1032
+ if ((charSet & 0xff) === 0) {
1033
+ const noneCharset = attachment?.charSetForNONE;
1034
+ if (noneCharset && noneCharset.toLowerCase() !== 'utf8' && noneCharset.toLowerCase() !== 'utf-8') {
1035
+ try {
1036
+ return new TextDecoder(noneCharset).decode(bytes);
1037
+ }
1038
+ catch {
1039
+ // fall through to utf8
1040
+ }
1041
+ }
1042
+ }
1043
+ return buf.toString('utf8');
1044
+ }
1045
+ /** Firebird `ISC_DATE` (days since 1858-11-17) to `'YYYY-MM-DD'`. */
1046
+ function formatDate(iscDate) {
1047
+ let nday = (iscDate + 2400001 - 1721119) | 0;
1048
+ const century = ((4 * nday - 1) / 146097) | 0;
1049
+ nday = (4 * nday - 1 - 146097 * century) | 0;
1050
+ let d = (nday / 4) | 0;
1051
+ nday = ((4 * d + 3) / 1461) | 0;
1052
+ d = (4 * d + 3 - 1461 * nday) | 0;
1053
+ d = ((d + 4) / 4) | 0;
1054
+ let m = ((5 * d - 3) / 153) | 0;
1055
+ d = (5 * d - 3 - 153 * m) | 0;
1056
+ d = ((d + 5) / 5) | 0;
1057
+ let y = (100 * century + nday) | 0;
1058
+ if (m < 10) {
1059
+ m += 3;
1060
+ }
1061
+ else {
1062
+ m -= 9;
1063
+ y += 1;
1064
+ }
1065
+ return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
1066
+ }
1067
+ /** Firebird `ISC_TIME` (1/10000 s since midnight) to `'HH:MM:SS.ffff'`. */
1068
+ function formatTime(iscTime) {
1069
+ const hours = Math.floor(iscTime / 36000000);
1070
+ let rem = iscTime % 36000000;
1071
+ const minutes = Math.floor(rem / 600000);
1072
+ rem %= 600000;
1073
+ const seconds = Math.floor(rem / 10000);
1074
+ const fractions = rem % 10000;
1075
+ return (`${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:` +
1076
+ `${String(seconds).padStart(2, '0')}.${String(fractions).padStart(4, '0')}`);
1077
+ }
1078
+ /** Scaled integer (`NUMERIC` / `DECIMAL`) to exact decimal string. */
1079
+ function formatScaledDecimal(value, scale) {
1080
+ if (scale >= 0) {
1081
+ return value === 0n ? '0' : `${value}${'0'.repeat(scale)}`;
1082
+ }
1083
+ const negative = value < 0n;
1084
+ const digits = (negative ? -value : value).toString();
1085
+ const decimals = -scale;
1086
+ const padded = digits.length <= decimals ? digits.padStart(decimals + 1, '0') : digits;
1087
+ const dot = padded.length - decimals;
1088
+ return `${negative ? '-' : ''}${padded.slice(0, dot)}.${padded.slice(dot)}`;
1089
+ }
1090
+ /**
1091
+ * Resolve `SQL_BLOB` handles and `OCTETS` byte buffers in `rawRows` after a
1092
+ * fetch call completes (when no pipelined `fetchNextAsync` call is in flight
1093
+ * on the attachment).
1094
+ */
1095
+ async function resolveOutputRows(stmt, transaction, fields, rawRows, types) {
1096
+ if (rawRows.length === 0)
1097
+ return;
1098
+ const binaryBlobs = types?.binary === true;
1099
+ const attachment = stmt.attachment;
1100
+ const outDescriptors = bindings.get(stmt)?.outDescriptors;
1101
+ for (const row of rawRows) {
1102
+ for (let i = 0; i < row.length; i++) {
1103
+ const cell = row[i];
1104
+ if (cell === null || cell === undefined)
1105
+ continue;
1106
+ const field = fields[i];
1107
+ const type = (field?.type ?? 0) & ~1;
1108
+ if (cell instanceof node_firebird_driver_1.Blob ||
1109
+ (type === SQL_BLOB &&
1110
+ typeof cell === 'object' &&
1111
+ !(cell instanceof Uint8Array) &&
1112
+ 'id' in cell)) {
1113
+ if (typeof attachment?.openBlob !== 'function')
1114
+ continue;
1115
+ const bytes = await readBlobBytes(attachment, transaction, cell);
1116
+ if (field?.subType === 1) {
1117
+ const charSet = outDescriptors?.[i]?.preparedCharSet ?? CS_UTF8;
1118
+ row[i] = decodeText(bytes, charSet, attachment);
1119
+ }
1120
+ else {
1121
+ row[i] = binaryBlobs ? new Uint8Array(bytes) : bytes.toString('base64');
1122
+ }
1123
+ }
1124
+ else if (Buffer.isBuffer(cell) || cell instanceof Uint8Array) {
1125
+ const buf = Buffer.isBuffer(cell)
1126
+ ? cell
1127
+ : Buffer.from(cell.buffer, cell.byteOffset, cell.byteLength);
1128
+ if (type === SQL_BLOB && field?.subType === 1) {
1129
+ row[i] = buf.toString('utf8');
1130
+ }
1131
+ else {
1132
+ row[i] = binaryBlobs ? new Uint8Array(buf) : buf.toString('base64');
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+ async function readBlobBytes(attachment, transaction, blob) {
1139
+ const stream = await attachment.openBlob(transaction, blob);
1140
+ try {
1141
+ const chunks = [];
1142
+ for (;;) {
1143
+ const buffer = Buffer.alloc(64 * 1024);
1144
+ const n = await stream.read(buffer);
1145
+ if (n < 0)
1146
+ break;
1147
+ if (n > 0)
1148
+ chunks.push(buffer.subarray(0, n));
1149
+ }
1150
+ return Buffer.concat(chunks);
1151
+ }
1152
+ finally {
1153
+ await stream.close().catch(() => undefined);
1154
+ }
1155
+ }
616
1156
  /**
617
1157
  * The parameters as the driver will take them, the statement made ready to
618
1158
  * take them.
@@ -638,12 +1178,12 @@ const TEXT_LENGTH = 128;
638
1178
  * so the same call now means the same there and here, to the last digit.
639
1179
  * A `Date` still goes the driver's way.
640
1180
  *
641
- * So does a string bound to a SMALLINT, INTEGER, BIGINT or NUMERIC parameter:
642
- * the driver binds those as doubles, and `'9007199254740993'` arrived as
643
- * 9007199254740992, `'0.1'` in a NUMERIC(18,17) as 0.10000000000000001.
644
- * A JS number still goes as a double (it is one); a string to a DOUBLE
645
- * PRECISION or FLOAT parameter is still converted by JS, which rounds
646
- * correctly.
1181
+ * So does a string or `bigint` bound to a SMALLINT, INTEGER, BIGINT or
1182
+ * NUMERIC parameter: the driver binds those as doubles, and
1183
+ * `'9007199254740993'` arrived as 9007199254740992, `'0.1'` in a
1184
+ * NUMERIC(18,17) as 0.10000000000000001. A JS number still goes as a double
1185
+ * (it is one); a string to a DOUBLE PRECISION or FLOAT parameter is still
1186
+ * converted by JS, which rounds correctly.
647
1187
  */
648
1188
  async function bindParams(stmt, transaction, given) {
649
1189
  // Bytes as the driver takes them: a Buffer (the browser engine takes a
@@ -654,7 +1194,8 @@ async function bindParams(stmt, transaction, given) {
654
1194
  : p instanceof Uint8Array && !Buffer.isBuffer(p) ? Buffer.from(p.buffer, p.byteOffset, p.byteLength) : p)
655
1195
  : given;
656
1196
  const binding = bindings.get(stmt);
657
- if (!params.some((p) => typeof p === 'string' || Buffer.isBuffer(p)) && (!binding || binding.current === '')) {
1197
+ if (!params.some((p) => typeof p === 'string' || typeof p === 'bigint' || Buffer.isBuffer(p)) &&
1198
+ (!binding || binding.current === '')) {
658
1199
  return params;
659
1200
  }
660
1201
  const described = (binding ?? (await bindingOf(stmt))).params;
@@ -663,7 +1204,11 @@ async function bindParams(stmt, transaction, given) {
663
1204
  const text = [];
664
1205
  const bound = [...params];
665
1206
  for (let i = 0; i < bound.length; i++) {
666
- const p = bound[i];
1207
+ let p = bound[i];
1208
+ if (typeof p === 'bigint') {
1209
+ p = p.toString();
1210
+ bound[i] = p;
1211
+ }
667
1212
  const type = (described[i]?.type ?? 0) & ~1;
668
1213
  if (type === SQL_BLOB && (typeof p === 'string' || Buffer.isBuffer(p))) {
669
1214
  bound[i] = await writeBlob(stmt, transaction, typeof p === 'string' ? Buffer.from(p, 'utf8') : p);
@@ -692,6 +1237,239 @@ async function writeBlob(stmt, transaction, bytes) {
692
1237
  await stream.close();
693
1238
  return stream.blob;
694
1239
  }
1240
+ const XPB_BATCH = 5;
1241
+ const BATCH_VERSION1 = 1;
1242
+ const BATCH_TAG_RECORD_COUNTS = 2;
1243
+ const BATCH_TAG_BUFFER_BYTES_SIZE = 3;
1244
+ const BATCH_TAG_BLOB_POLICY = 4;
1245
+ const BATCH_BLOB_ID_ENGINE = 1;
1246
+ const BATCH_EXECUTE_FAILED = -1;
1247
+ /**
1248
+ * Execute `rows` through Firebird 4+'s native `IBatch` interface
1249
+ * (`statementHandle.createBatchAsync`, added in
1250
+ * `asfernandes/node-firebird-drivers#178`).
1251
+ *
1252
+ * Returns `true` when the batch ran via `IBatch`, or `false` when the loaded
1253
+ * `node-firebird-native-api` build or the connected server (e.g. Firebird 3)
1254
+ * does not support `IBatch`, allowing `execBatch` to fall back to
1255
+ * `EXECUTE BLOCK`.
1256
+ */
1257
+ async function tryExecuteNativeBatch(stmt, transaction, rows) {
1258
+ const internal = stmt;
1259
+ const createBatchAsync = internal.statementHandle?.createBatchAsync;
1260
+ const client = internal.attachment?.client;
1261
+ const txHandle = transaction.transactionHandle;
1262
+ if (typeof createBatchAsync !== 'function' ||
1263
+ typeof client?.statusAction !== 'function' ||
1264
+ txHandle === undefined) {
1265
+ return false;
1266
+ }
1267
+ if (stmt.hasResultSet || (await outputFields(stmt)).length > 0) {
1268
+ return false;
1269
+ }
1270
+ const described = await inputParams(stmt);
1271
+ if (!described || described.length === 0) {
1272
+ return false;
1273
+ }
1274
+ const paramCount = described.length;
1275
+ for (const row of rows) {
1276
+ if (row.length !== paramCount)
1277
+ return false;
1278
+ }
1279
+ const textSet = new Set();
1280
+ const blobCols = [];
1281
+ for (let i = 0; i < paramCount; i++) {
1282
+ const type = (described[i]?.type ?? 0) & ~1;
1283
+ if (type === SQL_BLOB) {
1284
+ blobCols.push(i);
1285
+ continue;
1286
+ }
1287
+ if (!TEXT_TYPES.has(type))
1288
+ continue;
1289
+ let sawString = false;
1290
+ let sawDate = false;
1291
+ for (const row of rows) {
1292
+ const v = row[i];
1293
+ if (v === null || v === undefined)
1294
+ continue;
1295
+ if (typeof v === 'string' || typeof v === 'bigint') {
1296
+ sawString = true;
1297
+ }
1298
+ else if (v instanceof Date || (typeof v === 'object' && v !== null && 'date' in v)) {
1299
+ sawDate = true;
1300
+ }
1301
+ }
1302
+ if (sawString && sawDate)
1303
+ return false;
1304
+ if (sawString)
1305
+ textSet.add(i);
1306
+ }
1307
+ const textPositions = [...textSet].sort((a, b) => a - b);
1308
+ if (!(await useMessage(stmt, textPositions))) {
1309
+ return false;
1310
+ }
1311
+ const inMetadata = internal.inMetadata;
1312
+ const inBuffer = internal.inBuffer;
1313
+ const dataWriter = internal.dataWriter;
1314
+ if (!inMetadata ||
1315
+ !inBuffer ||
1316
+ typeof inMetadata.getAlignedLengthSync !== 'function' ||
1317
+ typeof dataWriter !== 'function') {
1318
+ return false;
1319
+ }
1320
+ const hasBlobs = blobCols.length > 0;
1321
+ let totalBlobBytes = 0;
1322
+ const normalizedRows = new Array(rows.length);
1323
+ const rowBlobs = hasBlobs ? new Array(rows.length) : undefined;
1324
+ for (let r = 0; r < rows.length; r++) {
1325
+ const src = rows[r];
1326
+ const dst = new Array(paramCount);
1327
+ const blobsForRow = hasBlobs ? new Array(paramCount).fill(null) : undefined;
1328
+ for (let i = 0; i < paramCount; i++) {
1329
+ let v = src[i];
1330
+ if (v === undefined || v === null) {
1331
+ dst[i] = null;
1332
+ continue;
1333
+ }
1334
+ if (v instanceof ArrayBuffer) {
1335
+ v = Buffer.from(v);
1336
+ }
1337
+ else if (v instanceof Uint8Array && !Buffer.isBuffer(v)) {
1338
+ v = Buffer.from(v.buffer, v.byteOffset, v.byteLength);
1339
+ }
1340
+ else if (typeof v === 'bigint') {
1341
+ v = v.toString();
1342
+ }
1343
+ if (hasBlobs && (described[i].type & ~1) === SQL_BLOB) {
1344
+ if (typeof v === 'string') {
1345
+ const buf = Buffer.from(v, 'utf8');
1346
+ blobsForRow[i] = buf;
1347
+ totalBlobBytes += buf.byteLength;
1348
+ dst[i] = null;
1349
+ continue;
1350
+ }
1351
+ if (Buffer.isBuffer(v)) {
1352
+ blobsForRow[i] = v;
1353
+ totalBlobBytes += v.byteLength;
1354
+ dst[i] = null;
1355
+ continue;
1356
+ }
1357
+ return false;
1358
+ }
1359
+ if (textSet.has(i) && typeof v !== 'string') {
1360
+ v = String(v);
1361
+ }
1362
+ dst[i] = v;
1363
+ }
1364
+ normalizedRows[r] = dst;
1365
+ if (rowBlobs && blobsForRow)
1366
+ rowBlobs[r] = blobsForRow;
1367
+ }
1368
+ return client.statusAction(async (status) => {
1369
+ const stride = inMetadata.getAlignedLengthSync(status);
1370
+ const msgLen = inMetadata.getMessageLengthSync(status);
1371
+ if (stride <= 0 || msgLen <= 0)
1372
+ return false;
1373
+ const blobOffsets = hasBlobs
1374
+ ? blobCols.map((col) => ({
1375
+ col,
1376
+ offset: inMetadata.getOffsetSync(status, col),
1377
+ nullOffset: inMetadata.getNullOffsetSync(status, col),
1378
+ }))
1379
+ : [];
1380
+ if (hasBlobs && typeof internal.statementHandle?.createBatchAsync === 'function') {
1381
+ // Verify addBlobAsync will be present on the batch below.
1382
+ }
1383
+ const needed = stride * rows.length + totalBlobBytes + 4096;
1384
+ let pb;
1385
+ let parLength = 0;
1386
+ let parBuffer;
1387
+ try {
1388
+ pb = client.util?.getXpbBuilderSync?.(status, XPB_BATCH, undefined, 0);
1389
+ if (pb) {
1390
+ pb.insertIntSync(status, BATCH_TAG_RECORD_COUNTS, 1);
1391
+ if (hasBlobs) {
1392
+ pb.insertIntSync(status, BATCH_TAG_BLOB_POLICY, BATCH_BLOB_ID_ENGINE);
1393
+ }
1394
+ if (needed > 16 * 1024 * 1024) {
1395
+ pb.insertIntSync(status, BATCH_TAG_BUFFER_BYTES_SIZE, Math.min(256 * 1024 * 1024, needed));
1396
+ }
1397
+ parLength = pb.getBufferLengthSync(status);
1398
+ parBuffer = pb.getBufferSync(status);
1399
+ }
1400
+ else {
1401
+ const bytes = [BATCH_VERSION1, BATCH_TAG_RECORD_COUNTS, 4, 1, 0, 0, 0];
1402
+ if (hasBlobs) {
1403
+ bytes.push(BATCH_TAG_BLOB_POLICY, 4, BATCH_BLOB_ID_ENGINE, 0, 0, 0);
1404
+ }
1405
+ const raw = new Uint8Array(bytes);
1406
+ parLength = raw.byteLength;
1407
+ parBuffer = raw;
1408
+ }
1409
+ let batch;
1410
+ try {
1411
+ batch = await createBatchAsync.call(internal.statementHandle, status, inMetadata, parLength, parBuffer);
1412
+ }
1413
+ catch {
1414
+ // Unsupported by server (e.g. Firebird 3) or client library: fall back to EXECUTE BLOCK.
1415
+ return false;
1416
+ }
1417
+ if (!batch)
1418
+ return false;
1419
+ if (hasBlobs && typeof batch.addBlobAsync !== 'function') {
1420
+ await batch.closeAsync(status).catch(() => undefined);
1421
+ return false;
1422
+ }
1423
+ try {
1424
+ const messages = new Uint8Array(stride * rows.length);
1425
+ const blobIdBuf = hasBlobs ? new Uint8Array(8) : undefined;
1426
+ for (let r = 0; r < rows.length; r++) {
1427
+ inBuffer.fill(0);
1428
+ await dataWriter(internal.attachment, transaction, inBuffer, normalizedRows[r]);
1429
+ if (hasBlobs) {
1430
+ const blobsForRow = rowBlobs[r];
1431
+ for (const { col, offset, nullOffset } of blobOffsets) {
1432
+ const blobBytes = blobsForRow[col];
1433
+ if (!blobBytes)
1434
+ continue;
1435
+ blobIdBuf.fill(0);
1436
+ await batch.addBlobAsync(status, blobBytes.byteLength, blobBytes, blobIdBuf, 0, undefined);
1437
+ inBuffer[nullOffset] = 0;
1438
+ inBuffer[nullOffset + 1] = 0;
1439
+ inBuffer.set(blobIdBuf, offset);
1440
+ }
1441
+ }
1442
+ messages.set(inBuffer.subarray(0, msgLen), r * stride);
1443
+ }
1444
+ await batch.addAsync(status, rows.length, messages);
1445
+ const completion = await batch.executeAsync(status, txHandle);
1446
+ if (!completion) {
1447
+ throw new Error('IBatch.executeAsync returned no completion state');
1448
+ }
1449
+ try {
1450
+ const reported = completion.getSizeSync(status);
1451
+ for (let i = 0; i < reported; i++) {
1452
+ const state = completion.getStateSync(status, i);
1453
+ if (state === BATCH_EXECUTE_FAILED) {
1454
+ completion.getStatusSync(status, status, i);
1455
+ throw new Error(`IBatch execution failed at row ${i}`);
1456
+ }
1457
+ }
1458
+ }
1459
+ finally {
1460
+ completion.disposeSync();
1461
+ }
1462
+ }
1463
+ finally {
1464
+ await batch.closeAsync(status).catch(() => undefined);
1465
+ }
1466
+ }
1467
+ finally {
1468
+ pb?.disposeSync();
1469
+ }
1470
+ return true;
1471
+ });
1472
+ }
695
1473
  /**
696
1474
  * Put in place the input message that takes the parameters at `positions`
697
1475
  * as VARCHAR text and every other as the driver prepared it, making it the
@@ -721,7 +1499,7 @@ async function useMessage(stmt, positions) {
721
1499
  inBuffer: internal.inBuffer,
722
1500
  dataWriter: internal.dataWriter,
723
1501
  };
724
- releaseMessagesOnDispose(internal, binding);
1502
+ hookDispose(internal, binding);
725
1503
  }
726
1504
  binding.variants.set(key, made);
727
1505
  message = made;
@@ -777,13 +1555,17 @@ async function makeTextMessage(internal, binding, positions) {
777
1555
  });
778
1556
  }
779
1557
  /**
780
- * Hand the driver back its own message when the statement is disposed, for
781
- * it to release as it always does, and release the ones made here.
1558
+ * Hand the driver back its own input/output messages when the statement is
1559
+ * disposed, for it to release as it always does, and release the ones made
1560
+ * here.
782
1561
  */
783
- function releaseMessagesOnDispose(internal, binding) {
1562
+ function hookDispose(internal, binding) {
1563
+ if (binding.disposeHooked)
1564
+ return;
784
1565
  const dispose = internal.internalDispose;
785
1566
  if (typeof dispose !== 'function')
786
1567
  return;
1568
+ binding.disposeHooked = true;
787
1569
  internal.internalDispose = async function () {
788
1570
  if (binding.original) {
789
1571
  internal.inMetadata = binding.original.inMetadata;
@@ -794,6 +1576,15 @@ function releaseMessagesOnDispose(internal, binding) {
794
1576
  message.inMetadata.releaseSync();
795
1577
  binding.variants.clear();
796
1578
  binding.current = '';
1579
+ if (binding.customOut) {
1580
+ if (binding.originalOut) {
1581
+ internal.outMetadata = binding.originalOut.outMetadata;
1582
+ internal.outBuffer = binding.originalOut.outBuffer;
1583
+ internal.dataReader = binding.originalOut.dataReader;
1584
+ }
1585
+ binding.customOut.releaseSync();
1586
+ binding.customOut = undefined;
1587
+ }
797
1588
  return dispose.call(this);
798
1589
  };
799
1590
  }
@@ -920,23 +1711,99 @@ class StatementCache {
920
1711
  * `executeQuery` refused it outright inside a transaction. Anything else
921
1712
  * runs and returns nothing.
922
1713
  */
923
- async function fetchRows(stmt, transaction, params, columnCount) {
1714
+ async function fetchRows(stmt, transaction, params, fields, types) {
924
1715
  params = await bindParams(stmt, transaction, params);
1716
+ let rawRows;
925
1717
  if (stmt.hasResultSet) {
926
1718
  const resultSet = await stmt.executeQuery(transaction, params);
927
1719
  // A failing fetch must still close.
928
1720
  try {
929
- return await resultSet.fetch();
1721
+ rawRows = await resultSet.fetch();
930
1722
  }
931
1723
  finally {
932
1724
  await resultSet.close().catch(() => undefined);
933
1725
  }
934
1726
  }
935
- if (columnCount > 0) {
936
- return [await stmt.executeSingleton(transaction, params)];
1727
+ else if (fields.length > 0) {
1728
+ rawRows = [await stmt.executeSingleton(transaction, params)];
1729
+ }
1730
+ else {
1731
+ await stmt.execute(transaction, params);
1732
+ return [];
1733
+ }
1734
+ await resolveOutputRows(stmt, transaction, fields, rawRows, types);
1735
+ return rawRows;
1736
+ }
1737
+ /**
1738
+ * Stream a prepared statement's result set in batches of up to `batchSize` rows.
1739
+ */
1740
+ async function* streamRows(stmt, transaction, params, fields, rowMode, batchSize, types, onEngineError) {
1741
+ let boundParams;
1742
+ try {
1743
+ boundParams = await bindParams(stmt, transaction, params);
1744
+ }
1745
+ catch (err) {
1746
+ onEngineError();
1747
+ throw err;
1748
+ }
1749
+ if (stmt.hasResultSet) {
1750
+ let resultSet;
1751
+ try {
1752
+ resultSet = await stmt.executeQuery(transaction, boundParams);
1753
+ }
1754
+ catch (err) {
1755
+ onEngineError();
1756
+ throw err;
1757
+ }
1758
+ try {
1759
+ while (true) {
1760
+ let rawRows;
1761
+ try {
1762
+ rawRows = await resultSet.fetch({ fetchSize: batchSize });
1763
+ await resolveOutputRows(stmt, transaction, fields, rawRows, types);
1764
+ }
1765
+ catch (err) {
1766
+ onEngineError();
1767
+ throw err;
1768
+ }
1769
+ if (rawRows.length === 0)
1770
+ break;
1771
+ const batchFields = fields.map((f) => ({ ...f }));
1772
+ yield (0, value_types_1.applyTypes)({
1773
+ rows: shapeRows(rawRows, batchFields, rowMode),
1774
+ fields: batchFields,
1775
+ }, types, rowMode);
1776
+ }
1777
+ }
1778
+ finally {
1779
+ await resultSet.close().catch(() => undefined);
1780
+ }
1781
+ return;
1782
+ }
1783
+ if (fields.length > 0) {
1784
+ let rawRows;
1785
+ try {
1786
+ rawRows = [await stmt.executeSingleton(transaction, boundParams)];
1787
+ await resolveOutputRows(stmt, transaction, fields, rawRows, types);
1788
+ }
1789
+ catch (err) {
1790
+ onEngineError();
1791
+ throw err;
1792
+ }
1793
+ const batchFields = fields.map((f) => ({ ...f }));
1794
+ yield (0, value_types_1.applyTypes)({
1795
+ rows: shapeRows(rawRows, batchFields, rowMode),
1796
+ fields: batchFields,
1797
+ }, types, rowMode);
1798
+ return;
1799
+ }
1800
+ try {
1801
+ await stmt.execute(transaction, boundParams);
1802
+ }
1803
+ catch (err) {
1804
+ onEngineError();
1805
+ throw err;
937
1806
  }
938
- await stmt.execute(transaction, params);
939
- return [];
940
1807
  }
941
1808
  /**
942
1809
  * The driver hands back positional rows, so array mode is the shape that