node-firebird 2.6.0 → 2.8.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.
@@ -320,6 +320,16 @@ class Connection {
320
320
  if (callback)
321
321
  callback();
322
322
  }
323
+ /** Write a prebuilt packet and queue its response callback. */
324
+ _queueEventBuffer(buffer, callback) {
325
+ if (this._isClosed) {
326
+ if (callback)
327
+ callback(new Error('Connection is closed.'));
328
+ return;
329
+ }
330
+ this._socket.write(buffer);
331
+ this._queue.push(callback);
332
+ }
323
333
  _queueEvent(callback, defer = false) {
324
334
  var self = this;
325
335
  if (self._isClosed) {
@@ -923,6 +933,187 @@ class Connection {
923
933
  callback(err, ret);
924
934
  });
925
935
  }
936
+ /**
937
+ * Execute a statement once per row using the Firebird 4 batch API
938
+ * (protocol 16+): op_batch_create + op_batch_msg(s) + op_batch_exec +
939
+ * op_batch_rls, all pipelined in a single network flush. Every packet
940
+ * gets an in-order response (op_batch_cs for exec), so the regular
941
+ * response queue keeps everything in sync.
942
+ *
943
+ * rows: array of parameter arrays, one per record. BLOB/ARRAY columns
944
+ * are not supported yet. The callback receives a completion object:
945
+ * { recordCount, updateCounts, errors: [{recordNumber, error}],
946
+ * errorRecordNumbers, success }.
947
+ */
948
+ executeBatch(transaction, statement, rows, callback, options) {
949
+ options = options || {};
950
+ if (this._isClosed)
951
+ return this.throwClosed(callback);
952
+ if (!this.accept || this.accept.protocolVersion < const_1.default.PROTOCOL_VERSION16) {
953
+ (0, callback_1.doError)(new Error('executeBatch requires protocol 16+ (Firebird 4.0 or newer)'), callback);
954
+ return;
955
+ }
956
+ var input = statement.input;
957
+ if (!input || !input.length) {
958
+ (0, callback_1.doError)(new Error('executeBatch requires a statement with input parameters'), callback);
959
+ return;
960
+ }
961
+ if (!Array.isArray(rows)) {
962
+ (0, callback_1.doError)(new Error('executeBatch expects an array of parameter rows'), callback);
963
+ return;
964
+ }
965
+ if (rows.length === 0) {
966
+ if (callback)
967
+ callback(undefined, {
968
+ recordCount: 0, updateCounts: [], errors: [], errorRecordNumbers: [], success: true,
969
+ });
970
+ return;
971
+ }
972
+ for (var i = 0; i < rows.length; i++) {
973
+ if (!Array.isArray(rows[i]) || rows[i].length !== input.length) {
974
+ (0, callback_1.doError)(new Error('executeBatch row ' + i + ' must be an array of ' + input.length + ' values'), callback);
975
+ return;
976
+ }
977
+ }
978
+ var built;
979
+ try {
980
+ built = buildBatchEncoders(input, Object.assign({}, this.options, options));
981
+ }
982
+ catch (err) {
983
+ (0, callback_1.doError)(err, callback);
984
+ return;
985
+ }
986
+ var self = this;
987
+ var encoders = built.encoders;
988
+ var chunkSize = options.chunkSize > 0 ? options.chunkSize : 500;
989
+ var chunkCount = Math.ceil(rows.length / chunkSize);
990
+ var failure = null;
991
+ var completion = null;
992
+ var remaining = 2 + chunkCount; // create + msg chunks + exec
993
+ function settle(err) {
994
+ if (err && !failure)
995
+ failure = err;
996
+ remaining--;
997
+ if (remaining > 0)
998
+ return;
999
+ if (failure) {
1000
+ (0, callback_1.doError)(failure, callback);
1001
+ return;
1002
+ }
1003
+ var detailed = completion ? completion.detailedErrors : [];
1004
+ var errorRecordNumbers = detailed.map(function (e) { return e.recordNumber; })
1005
+ .concat(completion ? completion.errorRecordNumbers : []);
1006
+ if (callback)
1007
+ callback(undefined, {
1008
+ recordCount: completion ? completion.recordCount : 0,
1009
+ updateCounts: completion ? completion.updateCounts : [],
1010
+ errors: detailed,
1011
+ errorRecordNumbers: errorRecordNumbers,
1012
+ success: errorRecordNumbers.length === 0,
1013
+ });
1014
+ }
1015
+ var msg = this._msg;
1016
+ var blr = this._blr;
1017
+ // Build every packet before writing anything: an encoding failure
1018
+ // (e.g. an oversized CHAR/VARCHAR value) must abort the batch before
1019
+ // a single byte hits the wire, or the response queue desyncs.
1020
+ var packets = [];
1021
+ try {
1022
+ // 1. op_batch_create — the BLR is the statement's own described
1023
+ // input format: the engine requires batch messages to match it
1024
+ // exactly (a value-derived format is rejected with SQLDA errors).
1025
+ msg.pos = 0;
1026
+ blr.pos = 0;
1027
+ CalcBlr(blr, input);
1028
+ msg.addInt(const_1.default.op_batch_create);
1029
+ msg.addInt(statement.handle);
1030
+ msg.addBlr(blr);
1031
+ msg.addInt(built.msglen);
1032
+ var pb = buildBatchPb(options);
1033
+ msg.addInt(pb.length);
1034
+ msg.addParamBuffer(pb);
1035
+ packets.push(Buffer.from(msg.getData()));
1036
+ // 2. op_batch_msg — packed messages (null bitmap + non-null
1037
+ // values), exactly the op_execute protocol-13 message encoding.
1038
+ for (var c = 0; c < chunkCount; c++) {
1039
+ var start = c * chunkSize;
1040
+ var end = Math.min(start + chunkSize, rows.length);
1041
+ msg.pos = 0;
1042
+ msg.addInt(const_1.default.op_batch_msg);
1043
+ msg.addInt(statement.handle);
1044
+ msg.addInt(end - start);
1045
+ for (var i = start; i < end; i++) {
1046
+ var row = rows[i];
1047
+ var nullBits = new serialize_1.BitSet();
1048
+ for (var j = 0; j < input.length; j++) {
1049
+ nullBits.set(j, row[j] === null || row[j] === undefined ? 1 : 0);
1050
+ }
1051
+ var nullBuffer = nullBits.toBuffer();
1052
+ var requireBytes = Math.floor((input.length + 7) / 8);
1053
+ var remainingBytes = requireBytes - nullBuffer.length;
1054
+ if (nullBuffer.length) {
1055
+ msg.addBuffer(nullBuffer);
1056
+ }
1057
+ if (remainingBytes > 0) {
1058
+ msg.addBuffer(Buffer.alloc(remainingBytes));
1059
+ }
1060
+ msg.addAlignment(requireBytes);
1061
+ for (var j = 0; j < input.length; j++) {
1062
+ if (row[j] !== null && row[j] !== undefined) {
1063
+ encoders[j](msg, row[j]);
1064
+ }
1065
+ }
1066
+ }
1067
+ packets.push(Buffer.from(msg.getData()));
1068
+ }
1069
+ // 3. op_batch_exec — answered with op_batch_cs.
1070
+ msg.pos = 0;
1071
+ msg.addInt(const_1.default.op_batch_exec);
1072
+ msg.addInt(statement.handle);
1073
+ msg.addInt(transaction.handle);
1074
+ packets.push(Buffer.from(msg.getData()));
1075
+ // 4. op_batch_rls — free the server-side batch.
1076
+ msg.pos = 0;
1077
+ msg.addInt(const_1.default.op_batch_rls);
1078
+ msg.addInt(statement.handle);
1079
+ packets.push(Buffer.from(msg.getData()));
1080
+ }
1081
+ catch (err) {
1082
+ (0, callback_1.doError)(err, callback);
1083
+ return;
1084
+ }
1085
+ // Pipeline everything: the server answers each packet in order
1086
+ // (op_response for create/msg, op_batch_cs for exec), so one queued
1087
+ // callback per packet keeps the response stream in sync. The server
1088
+ // DEFERS the responses to create/msg/rls (PORT_lazy send_partial) —
1089
+ // they only hit the wire when a flushing send occurs, which the
1090
+ // op_batch_cs answer to exec provides. The rls response however is
1091
+ // deferred past our last packet and arrives with the NEXT flushed
1092
+ // response (commit, query, detach…), so completion must not wait
1093
+ // for it: its queued callback is a no-op that just keeps the
1094
+ // response stream aligned.
1095
+ var execIndex = packets.length - 2;
1096
+ for (var p = 0; p < packets.length; p++) {
1097
+ this._pending.push('executeBatch');
1098
+ if (p === execIndex) {
1099
+ this._queueEventBuffer(packets[p], function (err, ret) {
1100
+ if (!err && ret && ret.batchCompletion) {
1101
+ completion = ret.batchCompletion;
1102
+ settle();
1103
+ }
1104
+ else {
1105
+ settle(err || new Error('op_batch_exec did not return a completion state'));
1106
+ }
1107
+ });
1108
+ }
1109
+ else if (p === packets.length - 1) {
1110
+ this._queueEventBuffer(packets[p], function () { });
1111
+ }
1112
+ else {
1113
+ this._queueEventBuffer(packets[p], function (err) { settle(err); });
1114
+ }
1115
+ }
1116
+ }
926
1117
  executeStatement(transaction, statement, params, callback, custom) {
927
1118
  if (this._isClosed)
928
1119
  return this.throwClosed(callback);
@@ -1666,6 +1857,42 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
1666
1857
  };
1667
1858
  // Parse normal and lazy response
1668
1859
  return parseOpResponse(data, response, loop);
1860
+ case const_1.default.op_batch_cs: {
1861
+ // Batch completion state (response to op_batch_exec):
1862
+ // statement, reccount, updates count, vectors count, errors
1863
+ // count, then the update-count array, the (recnum + status
1864
+ // vector) pairs and finally the status-less error recnums.
1865
+ var completion = {
1866
+ statementHandle: data.readInt(),
1867
+ recordCount: data.readInt(),
1868
+ };
1869
+ var updatesCount = data.readInt();
1870
+ var vectorsCount = data.readInt();
1871
+ var simpleErrorsCount = data.readInt();
1872
+ completion.updateCounts = new Array(updatesCount);
1873
+ for (var bi = 0; bi < updatesCount; bi++) {
1874
+ completion.updateCounts[bi] = data.readInt();
1875
+ }
1876
+ completion.detailedErrors = [];
1877
+ for (var bi = 0; bi < vectorsCount; bi++) {
1878
+ var recordNumber = data.readInt();
1879
+ var vector = readStatusVector(data);
1880
+ var recordError = new Error((0, utils_1.lookupMessages)(vector.status) || 'Batch record failed');
1881
+ if (vector.status.length) {
1882
+ recordError.gdscode = vector.status[0].gdscode;
1883
+ recordError.gdsparams = vector.status[0].params;
1884
+ }
1885
+ if (vector.sqlcode !== undefined) {
1886
+ recordError.sqlcode = vector.sqlcode;
1887
+ }
1888
+ completion.detailedErrors.push({ recordNumber: recordNumber, error: recordError });
1889
+ }
1890
+ completion.errorRecordNumbers = [];
1891
+ for (var bi = 0; bi < simpleErrorsCount; bi++) {
1892
+ completion.errorRecordNumbers.push(data.readInt());
1893
+ }
1894
+ return cb(null, { batchCompletion: completion });
1895
+ }
1669
1896
  case const_1.default.op_fetch_response:
1670
1897
  case const_1.default.op_sql_response:
1671
1898
  var statement = callback.statement;
@@ -2080,6 +2307,44 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
2080
2307
  throw err;
2081
2308
  }
2082
2309
  }
2310
+ /**
2311
+ * Read one XDR status vector (as in op_response / op_batch_cs error
2312
+ * vectors): a stream of isc_arg_* items terminated by isc_arg_end.
2313
+ */
2314
+ function readStatusVector(data) {
2315
+ var result = { status: [] };
2316
+ var item = {};
2317
+ while (true) {
2318
+ var op = data.readInt();
2319
+ switch (op) {
2320
+ case const_1.default.isc_arg_end:
2321
+ return result;
2322
+ case const_1.default.isc_arg_gds:
2323
+ var num = data.readInt();
2324
+ if (!num) {
2325
+ break;
2326
+ }
2327
+ item = { gdscode: num };
2328
+ result.status.push(item);
2329
+ break;
2330
+ case const_1.default.isc_arg_string:
2331
+ case const_1.default.isc_arg_interpreted:
2332
+ case const_1.default.isc_arg_sql_state:
2333
+ var str = data.readString(const_1.default.DEFAULT_ENCODING);
2334
+ (item.params = item.params || []).push(str);
2335
+ break;
2336
+ case const_1.default.isc_arg_number:
2337
+ var n = data.readInt();
2338
+ (item.params = item.params || []).push(n);
2339
+ if (item.gdscode === const_1.default.isc_sqlerr) {
2340
+ result.sqlcode = n;
2341
+ }
2342
+ break;
2343
+ default:
2344
+ throw new Error('Unexpected status vector item: ' + op);
2345
+ }
2346
+ }
2347
+ }
2083
2348
  function parseOpResponse(data, response, cb) {
2084
2349
  var handle = data.readInt();
2085
2350
  if (!response.handle) {
@@ -2316,6 +2581,202 @@ function describe(buff, statement) {
2316
2581
  unpackCharSetCollation(statement.input);
2317
2582
  unpackCharSetCollation(statement.output);
2318
2583
  }
2584
+ /**
2585
+ * Batch support: the engine requires every batch message to use EXACTLY the
2586
+ * statement's described input format (unlike op_execute, where the client
2587
+ * may declare its own format and the server converts). The BLR is therefore
2588
+ * CalcBlr(statement.input), and this helper returns one value encoder per
2589
+ * column that writes the wire representation mirroring the corresponding
2590
+ * SQLVar* decode, including scale. Returns { encoders, msglen } where
2591
+ * encoders[j](msg, value) writes a non-null value and msglen is the
2592
+ * unpacked message length sent with op_batch_create.
2593
+ */
2594
+ function buildBatchEncoders(input, options) {
2595
+ var encoders = [];
2596
+ var offset = 0;
2597
+ var align = function (a) { offset = (offset + a - 1) & ~(a - 1); };
2598
+ var jsonAsObject = options && options.jsonAsObject;
2599
+ var toText = function (v) {
2600
+ if (typeof v === 'string')
2601
+ return v;
2602
+ if (v instanceof Date)
2603
+ return v.toString();
2604
+ if (typeof v === 'object' && !Buffer.isBuffer(v))
2605
+ return jsonAsObject ? JSON.stringify(v) : v.toString();
2606
+ return String(v);
2607
+ };
2608
+ var toBytes = function (v, meta, column) {
2609
+ var b = Buffer.isBuffer(v) ? v : Buffer.from(toText(v), const_1.default.DEFAULT_ENCODING);
2610
+ if (b.length > meta.length) {
2611
+ throw new Error('Batch value for column ' + column + ' is ' + b.length +
2612
+ ' bytes but the column accepts at most ' + meta.length + ' (' + (meta.field || '?') + ')');
2613
+ }
2614
+ return b;
2615
+ };
2616
+ var toDate = function (v) {
2617
+ if (v instanceof Date)
2618
+ return v;
2619
+ if (typeof v === 'string')
2620
+ return (0, utils_1.parseDate)(v);
2621
+ return new Date(v);
2622
+ };
2623
+ var scaled = function (v, scale) {
2624
+ var n = typeof v === 'string' ? parseFloat(v) : Number(v);
2625
+ return scale ? Math.round(n * Math.pow(10, -scale)) : n;
2626
+ };
2627
+ var scaledBig = function (v, scale) {
2628
+ if (typeof v === 'bigint') {
2629
+ return scale ? v * (10n ** BigInt(-scale)) : v;
2630
+ }
2631
+ return BigInt(scaled(v, scale));
2632
+ };
2633
+ for (var j = 0; j < input.length; j++) {
2634
+ var meta = input[j];
2635
+ var column = j + 1;
2636
+ switch (meta.type) {
2637
+ case const_1.default.SQL_TEXT: // CHAR: fixed meta.length bytes, space-padded
2638
+ encoders.push((function (m, col) {
2639
+ return function (msg, v) {
2640
+ var b = toBytes(v, m, col);
2641
+ if (b.length < m.length) {
2642
+ b = Buffer.concat([b, Buffer.alloc(m.length - b.length, 0x20)]);
2643
+ }
2644
+ msg.addParamBuffer(b);
2645
+ };
2646
+ })(meta, column));
2647
+ offset += meta.length;
2648
+ break;
2649
+ case const_1.default.SQL_VARYING:
2650
+ case const_1.default.SQL_NULL:
2651
+ encoders.push((function (m, col) {
2652
+ return function (msg, v) {
2653
+ var b = toBytes(v, m, col);
2654
+ msg.addInt(b.length);
2655
+ msg.addParamBuffer(b);
2656
+ };
2657
+ })(meta, column));
2658
+ align(2);
2659
+ offset += 2 + meta.length;
2660
+ break;
2661
+ case const_1.default.SQL_SHORT:
2662
+ // 2 bytes in the message struct (msglen), 4 on the XDR wire
2663
+ encoders.push((function (m) {
2664
+ return function (msg, v) { msg.addInt(scaled(v, m.scale)); };
2665
+ })(meta));
2666
+ align(2);
2667
+ offset += 2;
2668
+ break;
2669
+ case const_1.default.SQL_LONG:
2670
+ encoders.push((function (m) {
2671
+ return function (msg, v) { msg.addInt(scaled(v, m.scale)); };
2672
+ })(meta));
2673
+ align(4);
2674
+ offset += 4;
2675
+ break;
2676
+ case const_1.default.SQL_INT64:
2677
+ encoders.push((function (m) {
2678
+ return function (msg, v) {
2679
+ msg.addInt64(typeof v === 'bigint' ? scaledBig(v, m.scale) : scaled(v, m.scale));
2680
+ };
2681
+ })(meta));
2682
+ align(8);
2683
+ offset += 8;
2684
+ break;
2685
+ case const_1.default.SQL_INT128:
2686
+ encoders.push((function (m) {
2687
+ return function (msg, v) { msg.addInt128(scaledBig(v, m.scale)); };
2688
+ })(meta));
2689
+ align(8);
2690
+ offset += 16;
2691
+ break;
2692
+ case const_1.default.SQL_FLOAT:
2693
+ case const_1.default.SQL_D_FLOAT:
2694
+ encoders.push(function (msg, v) { msg.addFloat(Number(v)); });
2695
+ align(4);
2696
+ offset += 4;
2697
+ break;
2698
+ case const_1.default.SQL_DOUBLE:
2699
+ encoders.push(function (msg, v) { msg.addDouble(Number(v)); });
2700
+ align(8);
2701
+ offset += 8;
2702
+ break;
2703
+ case const_1.default.SQL_DEC16:
2704
+ encoders.push(function (msg, v) { msg.addDecFloat16(v); });
2705
+ align(8);
2706
+ offset += 8;
2707
+ break;
2708
+ case const_1.default.SQL_DEC34:
2709
+ encoders.push(function (msg, v) { msg.addDecFloat34(v); });
2710
+ align(8);
2711
+ offset += 16;
2712
+ break;
2713
+ case const_1.default.SQL_BOOLEAN:
2714
+ // xdr_datum sends booleans as a 1-byte opaque (value byte
2715
+ // first, then 3 pad bytes), not as a big-endian int.
2716
+ encoders.push(function (msg, v) {
2717
+ msg.addBuffer(Buffer.from([v ? 1 : 0]));
2718
+ msg.addAlignment(1);
2719
+ });
2720
+ offset += 1;
2721
+ break;
2722
+ case const_1.default.SQL_TIMESTAMP:
2723
+ encoders.push(function (msg, v) {
2724
+ var parts = Xsql.encodeDateTimeParts(toDate(v));
2725
+ msg.addInt(parts.date);
2726
+ msg.addUInt(parts.time);
2727
+ });
2728
+ align(4);
2729
+ offset += 8;
2730
+ break;
2731
+ case const_1.default.SQL_TYPE_DATE:
2732
+ encoders.push(function (msg, v) {
2733
+ msg.addInt(Xsql.encodeDateTimeParts(toDate(v)).date);
2734
+ });
2735
+ align(4);
2736
+ offset += 4;
2737
+ break;
2738
+ case const_1.default.SQL_TYPE_TIME:
2739
+ encoders.push(function (msg, v) {
2740
+ msg.addUInt(Xsql.encodeDateTimeParts(toDate(v)).time);
2741
+ });
2742
+ align(4);
2743
+ offset += 4;
2744
+ break;
2745
+ default:
2746
+ throw new Error('executeBatch does not support the type of parameter ' + column +
2747
+ ' yet (' + (meta.field || '?') + ', SQL type ' + meta.type + ')');
2748
+ }
2749
+ // null indicator short that CalcBlr appends per parameter
2750
+ align(2);
2751
+ offset += 2;
2752
+ }
2753
+ // msglen must EXACTLY match the length the server computes from the BLR
2754
+ // (MsgMetadata::makeOffsets — no trailing alignment), or op_batch_create
2755
+ // fails with -804 SQLDA errors.
2756
+ return { encoders: encoders, msglen: offset };
2757
+ }
2758
+ /**
2759
+ * Build the batch parameter buffer (p_batch_pb): a wide-tagged clumplet
2760
+ * buffer — version byte, then per clumplet a tag byte, int32 LE length and
2761
+ * the value (ints are 4-byte LE).
2762
+ */
2763
+ function buildBatchPb(options) {
2764
+ var parts = [const_1.default.BATCH_VERSION1];
2765
+ var addIntClumplet = function (tag, value) {
2766
+ parts.push(tag, 4, 0, 0, 0, value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF);
2767
+ };
2768
+ addIntClumplet(const_1.default.BATCH_TAG_RECORD_COUNTS, 1);
2769
+ if (!options || options.multiError !== false) {
2770
+ addIntClumplet(const_1.default.BATCH_TAG_MULTIERROR, 1);
2771
+ }
2772
+ if (options && options.bufferSize) {
2773
+ addIntClumplet(const_1.default.BATCH_TAG_BUFFER_BYTES_SIZE, options.bufferSize);
2774
+ }
2775
+ if (options && options.detailedErrors !== undefined) {
2776
+ addIntClumplet(const_1.default.BATCH_TAG_DETAILED_ERRORS, options.detailedErrors);
2777
+ }
2778
+ return Buffer.from(parts);
2779
+ }
2319
2780
  function CalcBlr(blr, xsqlda) {
2320
2781
  blr.addBytes([const_1.default.blr_version5, const_1.default.blr_begin, const_1.default.blr_message, 0]); // + message number
2321
2782
  blr.addWord(xsqlda.length * 2);
@@ -98,12 +98,27 @@ declare const Const: Readonly<{
98
98
  op_crypt: number;
99
99
  op_crypt_key_callback: number;
100
100
  op_cond_accept: number;
101
+ op_batch_create: number;
102
+ op_batch_msg: number;
103
+ op_batch_exec: number;
104
+ op_batch_rls: number;
105
+ op_batch_cs: number;
106
+ op_batch_cancel: number;
107
+ op_batch_sync: number;
108
+ op_info_batch: number;
101
109
  op_fetch_scroll: number;
102
110
  op_info_cursor: number;
103
111
  op_inline_blob: number;
104
112
  DSQL_close: number;
105
113
  DSQL_drop: number;
106
114
  DSQL_unprepare: number;
115
+ BATCH_VERSION1: number;
116
+ BATCH_TAG_MULTIERROR: number;
117
+ BATCH_TAG_RECORD_COUNTS: number;
118
+ BATCH_TAG_BUFFER_BYTES_SIZE: number;
119
+ BATCH_TAG_BLOB_POLICY: number;
120
+ BATCH_TAG_DETAILED_ERRORS: number;
121
+ BATCH_BLOB_NONE: number;
107
122
  fb_cancel_disable: number;
108
123
  fb_cancel_enable: number;
109
124
  fb_cancel_raise: number;
package/lib/wire/const.js CHANGED
@@ -108,6 +108,14 @@ const op = {
108
108
  op_crypt_key_callback: 97,
109
109
  op_cond_accept: 98, // Server accepts connection, returns some data to client
110
110
  // and asks client to continue authentication before attach call
111
+ op_batch_create: 99, // Create a statement batch (protocol 16+)
112
+ op_batch_msg: 100, // Add a bunch of messages to the batch
113
+ op_batch_exec: 101, // Execute the batch; server answers with op_batch_cs
114
+ op_batch_rls: 102, // Release the batch
115
+ op_batch_cs: 103, // Batch completion state (response to op_batch_exec)
116
+ op_batch_cancel: 109, // Cancel the batch
117
+ op_batch_sync: 110, // Wait for batch processing
118
+ op_info_batch: 111,
111
119
  op_fetch_scroll: 112,
112
120
  op_info_cursor: 113,
113
121
  op_inline_blob: 114,
@@ -117,6 +125,18 @@ const dsql = {
117
125
  DSQL_drop: 2,
118
126
  DSQL_unprepare: 4, // >: 2.5
119
127
  };
128
+ // Batch parameter buffer (op_batch_create p_batch_pb) — a wide-tagged
129
+ // clumplet buffer: version byte, then per clumplet a tag byte, an int32 LE
130
+ // length and the value bytes. Tags from Firebird's IBatch interface.
131
+ const batch = {
132
+ BATCH_VERSION1: 1,
133
+ BATCH_TAG_MULTIERROR: 1, // continue after per-record errors
134
+ BATCH_TAG_RECORD_COUNTS: 2, // return per-record update counts
135
+ BATCH_TAG_BUFFER_BYTES_SIZE: 3, // server-side batch buffer limit
136
+ BATCH_TAG_BLOB_POLICY: 4,
137
+ BATCH_TAG_DETAILED_ERRORS: 5, // max detailed status vectors returned
138
+ BATCH_BLOB_NONE: 0,
139
+ };
120
140
  // fb_cancel_operation kinds sent with op_cancel (protocol 12+ / Firebird 2.5+)
121
141
  const cancelKind = {
122
142
  fb_cancel_disable: 1, // disable execution of fb_cancel_raise requests
@@ -800,6 +820,7 @@ const Const = Object.freeze({
800
820
  ...authOptions,
801
821
  ...blr,
802
822
  ...blobType,
823
+ ...batch,
803
824
  ...buffer,
804
825
  ...cancelKind,
805
826
  ...cnct,
@@ -9,6 +9,16 @@ declare class Database extends Events.EventEmitter {
9
9
  startTransaction(options: any, callback?: (err: any, transaction?: any) => void): this;
10
10
  newStatement(query: string, callback: (err: any, statement?: any) => void): this;
11
11
  execute(query: string, params?: any, callback?: any, options?: any): this;
12
+ /**
13
+ * Bulk-execute `query` once per row via the Firebird 4 batch API
14
+ * (protocol 16+) with all-or-nothing semantics: the batch runs in its
15
+ * own transaction, committed only when every record succeeded and
16
+ * rolled back otherwise. On failure the error of the first failed
17
+ * record is reported, with the full completion attached as
18
+ * err.batchCompletion. Use transaction.executeBatch for partial-success
19
+ * handling.
20
+ */
21
+ executeBatch(query: string, rows: any[][], callback?: any, options?: any): this;
12
22
  sequentially(query: string, params?: any, on?: any, callback?: any, options?: any): this;
13
23
  query(query: string, params?: any, callback?: any, options?: any): this;
14
24
  drop(callback?: (err?: any) => void): void;
@@ -63,6 +73,7 @@ declare class Database extends Events.EventEmitter {
63
73
  createSchema(schemaName: string, tablespaceName?: string | ((err?: any) => void), callback?: any): this;
64
74
  queryAsync(query: string, params?: any, options?: any): Promise<any[]>;
65
75
  executeAsync(query: string, params?: any, options?: any): Promise<any[]>;
76
+ executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any>;
66
77
  sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void>;
67
78
  transactionAsync(options?: any): Promise<any>;
68
79
  startTransactionAsync(options?: any): Promise<any>;
@@ -189,6 +189,48 @@ class Database extends events_1.default.EventEmitter {
189
189
  });
190
190
  return self;
191
191
  }
192
+ /**
193
+ * Bulk-execute `query` once per row via the Firebird 4 batch API
194
+ * (protocol 16+) with all-or-nothing semantics: the batch runs in its
195
+ * own transaction, committed only when every record succeeded and
196
+ * rolled back otherwise. On failure the error of the first failed
197
+ * record is reported, with the full completion attached as
198
+ * err.batchCompletion. Use transaction.executeBatch for partial-success
199
+ * handling.
200
+ */
201
+ executeBatch(query, rows, callback, options) {
202
+ var self = this;
203
+ self.connection.startTransaction(function (err, transaction) {
204
+ if (err) {
205
+ (0, callback_1.doError)(err, callback);
206
+ return;
207
+ }
208
+ transaction.executeBatch(query, rows, function (err, result) {
209
+ if (err) {
210
+ transaction.rollback(function () {
211
+ (0, callback_1.doError)(err, callback);
212
+ });
213
+ return;
214
+ }
215
+ if (!result.success) {
216
+ transaction.rollback(function () {
217
+ var first = result.errors.length ? result.errors[0] : null;
218
+ var batchError = first
219
+ ? first.error
220
+ : new Error('Batch failed for record(s) ' + result.errorRecordNumbers.join(', '));
221
+ batchError.batchCompletion = result;
222
+ (0, callback_1.doError)(batchError, callback);
223
+ });
224
+ return;
225
+ }
226
+ transaction.commit(function (err) {
227
+ if (callback)
228
+ callback(err, result);
229
+ });
230
+ }, options);
231
+ });
232
+ return self;
233
+ }
192
234
  sequentially(query, params, on, callback, options = {}) {
193
235
  if (params instanceof Function) {
194
236
  options = callback;
@@ -406,6 +448,10 @@ class Database extends events_1.default.EventEmitter {
406
448
  var self = this;
407
449
  return (0, callback_1.fromCallback)(function (cb) { self.execute(query, params, cb, options); });
408
450
  }
451
+ executeBatchAsync(query, rows, options) {
452
+ var self = this;
453
+ return (0, callback_1.fromCallback)(function (cb) { self.executeBatch(query, rows, cb, options); });
454
+ }
409
455
  sequentiallyAsync(query, params, on, options) {
410
456
  if (params instanceof Function) {
411
457
  options = on;
@@ -42,7 +42,7 @@ export declare class XdrWriter {
42
42
  constructor(size?: number);
43
43
  ensure(len: number): void;
44
44
  addInt(value: number): void;
45
- addInt64(value: number): void;
45
+ addInt64(value: number | bigint): void;
46
46
  addInt128(value: number | bigint | string): void;
47
47
  addDecFloat16(value: number | string | bigint): void;
48
48
  addDecFloat34(value: number | string | bigint): void;
@@ -53,6 +53,7 @@ export declare class XdrWriter {
53
53
  addBlr(blr: BlrWriter): void;
54
54
  getData(): Buffer;
55
55
  addDouble(value: number): void;
56
+ addFloat(value: number): void;
56
57
  addQuad(quad: {
57
58
  low: number;
58
59
  high: number;