node-firebird 2.5.0 → 2.7.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.
@@ -369,6 +369,51 @@ class Connection {
369
369
  }
370
370
 
371
371
 
372
+ /**
373
+ * Send an out-of-band op_cancel packet (protocol 12+ / Firebird 2.5+).
374
+ * The server reads it asynchronously while an operation is executing and
375
+ * makes that operation fail with isc_cancelled (GDSCode.CANCELLED); the
376
+ * op_cancel packet itself has no response, so nothing is queued here.
377
+ */
378
+ cancelOperation(kind, callback) {
379
+ if (typeof kind === 'function') {
380
+ callback = kind;
381
+ kind = undefined;
382
+ }
383
+ kind = kind || Const.fb_cancel_raise;
384
+
385
+ if (this._isClosed)
386
+ return this.throwClosed(callback);
387
+
388
+ if (!this.accept || this.accept.protocolVersion < Const.PROTOCOL_VERSION12) {
389
+ doError(new Error('Query cancellation requires protocol 12+ (Firebird 2.5 or newer)'), callback);
390
+ return;
391
+ }
392
+
393
+ var msg = this._msg;
394
+ msg.pos = 0;
395
+ msg.addInt(Const.op_cancel);
396
+ msg.addInt(kind);
397
+ this._socket.write(msg.getData());
398
+
399
+ if (callback)
400
+ callback();
401
+ }
402
+
403
+
404
+ /** Write a prebuilt packet and queue its response callback. */
405
+ _queueEventBuffer(buffer, callback) {
406
+ if (this._isClosed) {
407
+ if (callback)
408
+ callback(new Error('Connection is closed.'));
409
+ return;
410
+ }
411
+
412
+ this._socket.write(buffer);
413
+ this._queue.push(callback);
414
+ }
415
+
416
+
372
417
  _queueEvent(callback, defer = false) {
373
418
  var self = this;
374
419
 
@@ -1126,6 +1171,208 @@ class Connection {
1126
1171
 
1127
1172
 
1128
1173
 
1174
+ /**
1175
+ * Execute a statement once per row using the Firebird 4 batch API
1176
+ * (protocol 16+): op_batch_create + op_batch_msg(s) + op_batch_exec +
1177
+ * op_batch_rls, all pipelined in a single network flush. Every packet
1178
+ * gets an in-order response (op_batch_cs for exec), so the regular
1179
+ * response queue keeps everything in sync.
1180
+ *
1181
+ * rows: array of parameter arrays, one per record. BLOB/ARRAY columns
1182
+ * are not supported yet. The callback receives a completion object:
1183
+ * { recordCount, updateCounts, errors: [{recordNumber, error}],
1184
+ * errorRecordNumbers, success }.
1185
+ */
1186
+ executeBatch(transaction, statement, rows, callback, options) {
1187
+ options = options || {};
1188
+
1189
+ if (this._isClosed)
1190
+ return this.throwClosed(callback);
1191
+
1192
+ if (!this.accept || this.accept.protocolVersion < Const.PROTOCOL_VERSION16) {
1193
+ doError(new Error('executeBatch requires protocol 16+ (Firebird 4.0 or newer)'), callback);
1194
+ return;
1195
+ }
1196
+
1197
+ var input = statement.input;
1198
+ if (!input || !input.length) {
1199
+ doError(new Error('executeBatch requires a statement with input parameters'), callback);
1200
+ return;
1201
+ }
1202
+
1203
+ if (!Array.isArray(rows)) {
1204
+ doError(new Error('executeBatch expects an array of parameter rows'), callback);
1205
+ return;
1206
+ }
1207
+
1208
+ if (rows.length === 0) {
1209
+ if (callback) callback(undefined, {
1210
+ recordCount: 0, updateCounts: [], errors: [], errorRecordNumbers: [], success: true,
1211
+ });
1212
+ return;
1213
+ }
1214
+
1215
+ for (var i = 0; i < rows.length; i++) {
1216
+ if (!Array.isArray(rows[i]) || rows[i].length !== input.length) {
1217
+ doError(new Error('executeBatch row ' + i + ' must be an array of ' + input.length + ' values'), callback);
1218
+ return;
1219
+ }
1220
+ }
1221
+
1222
+ var built;
1223
+ try {
1224
+ built = buildBatchEncoders(input, Object.assign({}, this.options, options));
1225
+ } catch (err) {
1226
+ doError(err, callback);
1227
+ return;
1228
+ }
1229
+
1230
+ var self = this;
1231
+ var encoders = built.encoders;
1232
+ var chunkSize = options.chunkSize > 0 ? options.chunkSize : 500;
1233
+ var chunkCount = Math.ceil(rows.length / chunkSize);
1234
+
1235
+ var failure: any = null;
1236
+ var completion: any = null;
1237
+ var remaining = 2 + chunkCount; // create + msg chunks + exec
1238
+
1239
+ function settle(err?: any) {
1240
+ if (err && !failure)
1241
+ failure = err;
1242
+ remaining--;
1243
+ if (remaining > 0)
1244
+ return;
1245
+
1246
+ if (failure) {
1247
+ doError(failure, callback);
1248
+ return;
1249
+ }
1250
+
1251
+ var detailed = completion ? completion.detailedErrors : [];
1252
+ var errorRecordNumbers = detailed.map(function(e) { return e.recordNumber; })
1253
+ .concat(completion ? completion.errorRecordNumbers : []);
1254
+
1255
+ if (callback) callback(undefined, {
1256
+ recordCount: completion ? completion.recordCount : 0,
1257
+ updateCounts: completion ? completion.updateCounts : [],
1258
+ errors: detailed,
1259
+ errorRecordNumbers: errorRecordNumbers,
1260
+ success: errorRecordNumbers.length === 0,
1261
+ });
1262
+ }
1263
+
1264
+ var msg = this._msg;
1265
+ var blr = this._blr;
1266
+
1267
+ // Build every packet before writing anything: an encoding failure
1268
+ // (e.g. an oversized CHAR/VARCHAR value) must abort the batch before
1269
+ // a single byte hits the wire, or the response queue desyncs.
1270
+ var packets: Buffer[] = [];
1271
+ try {
1272
+ // 1. op_batch_create — the BLR is the statement's own described
1273
+ // input format: the engine requires batch messages to match it
1274
+ // exactly (a value-derived format is rejected with SQLDA errors).
1275
+ msg.pos = 0;
1276
+ blr.pos = 0;
1277
+ CalcBlr(blr, input);
1278
+
1279
+ msg.addInt(Const.op_batch_create);
1280
+ msg.addInt(statement.handle);
1281
+ msg.addBlr(blr);
1282
+ msg.addInt(built.msglen);
1283
+ var pb = buildBatchPb(options);
1284
+ msg.addInt(pb.length);
1285
+ msg.addParamBuffer(pb);
1286
+ packets.push(Buffer.from(msg.getData()));
1287
+
1288
+ // 2. op_batch_msg — packed messages (null bitmap + non-null
1289
+ // values), exactly the op_execute protocol-13 message encoding.
1290
+ for (var c = 0; c < chunkCount; c++) {
1291
+ var start = c * chunkSize;
1292
+ var end = Math.min(start + chunkSize, rows.length);
1293
+
1294
+ msg.pos = 0;
1295
+ msg.addInt(Const.op_batch_msg);
1296
+ msg.addInt(statement.handle);
1297
+ msg.addInt(end - start);
1298
+
1299
+ for (var i = start; i < end; i++) {
1300
+ var row = rows[i];
1301
+
1302
+ var nullBits = new BitSet();
1303
+ for (var j = 0; j < input.length; j++) {
1304
+ nullBits.set(j, row[j] === null || row[j] === undefined ? 1 : 0);
1305
+ }
1306
+ var nullBuffer = nullBits.toBuffer();
1307
+ var requireBytes = Math.floor((input.length + 7) / 8);
1308
+ var remainingBytes = requireBytes - nullBuffer.length;
1309
+
1310
+ if (nullBuffer.length) {
1311
+ msg.addBuffer(nullBuffer);
1312
+ }
1313
+ if (remainingBytes > 0) {
1314
+ msg.addBuffer(Buffer.alloc(remainingBytes));
1315
+ }
1316
+ msg.addAlignment(requireBytes);
1317
+
1318
+ for (var j = 0; j < input.length; j++) {
1319
+ if (row[j] !== null && row[j] !== undefined) {
1320
+ encoders[j](msg, row[j]);
1321
+ }
1322
+ }
1323
+ }
1324
+
1325
+ packets.push(Buffer.from(msg.getData()));
1326
+ }
1327
+
1328
+ // 3. op_batch_exec — answered with op_batch_cs.
1329
+ msg.pos = 0;
1330
+ msg.addInt(Const.op_batch_exec);
1331
+ msg.addInt(statement.handle);
1332
+ msg.addInt(transaction.handle);
1333
+ packets.push(Buffer.from(msg.getData()));
1334
+
1335
+ // 4. op_batch_rls — free the server-side batch.
1336
+ msg.pos = 0;
1337
+ msg.addInt(Const.op_batch_rls);
1338
+ msg.addInt(statement.handle);
1339
+ packets.push(Buffer.from(msg.getData()));
1340
+ } catch (err) {
1341
+ doError(err, callback);
1342
+ return;
1343
+ }
1344
+
1345
+ // Pipeline everything: the server answers each packet in order
1346
+ // (op_response for create/msg, op_batch_cs for exec), so one queued
1347
+ // callback per packet keeps the response stream in sync. The server
1348
+ // DEFERS the responses to create/msg/rls (PORT_lazy send_partial) —
1349
+ // they only hit the wire when a flushing send occurs, which the
1350
+ // op_batch_cs answer to exec provides. The rls response however is
1351
+ // deferred past our last packet and arrives with the NEXT flushed
1352
+ // response (commit, query, detach…), so completion must not wait
1353
+ // for it: its queued callback is a no-op that just keeps the
1354
+ // response stream aligned.
1355
+ var execIndex = packets.length - 2;
1356
+ for (var p = 0; p < packets.length; p++) {
1357
+ this._pending.push('executeBatch');
1358
+ if (p === execIndex) {
1359
+ this._queueEventBuffer(packets[p], function(err, ret) {
1360
+ if (!err && ret && ret.batchCompletion) {
1361
+ completion = ret.batchCompletion;
1362
+ settle();
1363
+ } else {
1364
+ settle(err || new Error('op_batch_exec did not return a completion state'));
1365
+ }
1366
+ });
1367
+ } else if (p === packets.length - 1) {
1368
+ this._queueEventBuffer(packets[p], function() {});
1369
+ } else {
1370
+ this._queueEventBuffer(packets[p], function(err) { settle(err); });
1371
+ }
1372
+ }
1373
+ }
1374
+
1375
+
1129
1376
  executeStatement(transaction, statement, params, callback, custom) {
1130
1377
 
1131
1378
  if (this._isClosed)
@@ -1978,6 +2225,46 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
1978
2225
  };
1979
2226
  // Parse normal and lazy response
1980
2227
  return parseOpResponse(data, response, loop);
2228
+ case Const.op_batch_cs: {
2229
+ // Batch completion state (response to op_batch_exec):
2230
+ // statement, reccount, updates count, vectors count, errors
2231
+ // count, then the update-count array, the (recnum + status
2232
+ // vector) pairs and finally the status-less error recnums.
2233
+ var completion: any = {
2234
+ statementHandle: data.readInt(),
2235
+ recordCount: data.readInt(),
2236
+ };
2237
+ var updatesCount = data.readInt();
2238
+ var vectorsCount = data.readInt();
2239
+ var simpleErrorsCount = data.readInt();
2240
+
2241
+ completion.updateCounts = new Array(updatesCount);
2242
+ for (var bi = 0; bi < updatesCount; bi++) {
2243
+ completion.updateCounts[bi] = data.readInt();
2244
+ }
2245
+
2246
+ completion.detailedErrors = [];
2247
+ for (var bi = 0; bi < vectorsCount; bi++) {
2248
+ var recordNumber = data.readInt();
2249
+ var vector = readStatusVector(data);
2250
+ var recordError: any = new Error(lookupMessages(vector.status) || 'Batch record failed');
2251
+ if (vector.status.length) {
2252
+ recordError.gdscode = vector.status[0].gdscode;
2253
+ recordError.gdsparams = vector.status[0].params;
2254
+ }
2255
+ if (vector.sqlcode !== undefined) {
2256
+ recordError.sqlcode = vector.sqlcode;
2257
+ }
2258
+ completion.detailedErrors.push({ recordNumber: recordNumber, error: recordError });
2259
+ }
2260
+
2261
+ completion.errorRecordNumbers = [];
2262
+ for (var bi = 0; bi < simpleErrorsCount; bi++) {
2263
+ completion.errorRecordNumbers.push(data.readInt());
2264
+ }
2265
+
2266
+ return cb(null, { batchCompletion: completion });
2267
+ }
1981
2268
  case Const.op_fetch_response:
1982
2269
  case Const.op_sql_response:
1983
2270
  var statement = callback.statement;
@@ -2483,6 +2770,47 @@ function decodeResponse(data: any, callback: any, cnx: any, lowercase_keys: any,
2483
2770
  }
2484
2771
  }
2485
2772
 
2773
+ /**
2774
+ * Read one XDR status vector (as in op_response / op_batch_cs error
2775
+ * vectors): a stream of isc_arg_* items terminated by isc_arg_end.
2776
+ */
2777
+ function readStatusVector(data: any): { status: any[]; sqlcode?: number } {
2778
+ var result: { status: any[]; sqlcode?: number } = { status: [] };
2779
+ var item: any = {};
2780
+
2781
+ while (true) {
2782
+ var op = data.readInt();
2783
+
2784
+ switch (op) {
2785
+ case Const.isc_arg_end:
2786
+ return result;
2787
+ case Const.isc_arg_gds:
2788
+ var num = data.readInt();
2789
+ if (!num) {
2790
+ break;
2791
+ }
2792
+ item = { gdscode: num };
2793
+ result.status.push(item);
2794
+ break;
2795
+ case Const.isc_arg_string:
2796
+ case Const.isc_arg_interpreted:
2797
+ case Const.isc_arg_sql_state:
2798
+ var str = data.readString(Const.DEFAULT_ENCODING);
2799
+ (item.params = item.params || []).push(str);
2800
+ break;
2801
+ case Const.isc_arg_number:
2802
+ var n = data.readInt();
2803
+ (item.params = item.params || []).push(n);
2804
+ if (item.gdscode === Const.isc_sqlerr) {
2805
+ result.sqlcode = n;
2806
+ }
2807
+ break;
2808
+ default:
2809
+ throw new Error('Unexpected status vector item: ' + op);
2810
+ }
2811
+ }
2812
+ }
2813
+
2486
2814
  function parseOpResponse(data: any, response: any, cb?: (err?: any, response?: any) => void) {
2487
2815
  var handle = data.readInt();
2488
2816
 
@@ -2684,6 +3012,208 @@ function describe(buff: Buffer, statement: any) {
2684
3012
  unpackCharSetCollation(statement.output);
2685
3013
  }
2686
3014
 
3015
+ /**
3016
+ * Batch support: the engine requires every batch message to use EXACTLY the
3017
+ * statement's described input format (unlike op_execute, where the client
3018
+ * may declare its own format and the server converts). The BLR is therefore
3019
+ * CalcBlr(statement.input), and this helper returns one value encoder per
3020
+ * column that writes the wire representation mirroring the corresponding
3021
+ * SQLVar* decode, including scale. Returns { encoders, msglen } where
3022
+ * encoders[j](msg, value) writes a non-null value and msglen is the
3023
+ * unpacked message length sent with op_batch_create.
3024
+ */
3025
+ function buildBatchEncoders(input: any[], options: any) {
3026
+ var encoders: Array<(msg: any, v: any) => void> = [];
3027
+ var offset = 0;
3028
+ var align = function(a: number) { offset = (offset + a - 1) & ~(a - 1); };
3029
+
3030
+ var jsonAsObject = options && options.jsonAsObject;
3031
+ var toText = function(v: any): string {
3032
+ if (typeof v === 'string') return v;
3033
+ if (v instanceof Date) return v.toString();
3034
+ if (typeof v === 'object' && !Buffer.isBuffer(v)) return jsonAsObject ? JSON.stringify(v) : v.toString();
3035
+ return String(v);
3036
+ };
3037
+ var toBytes = function(v: any, meta: any, column: number): Buffer {
3038
+ var b = Buffer.isBuffer(v) ? v : Buffer.from(toText(v), Const.DEFAULT_ENCODING);
3039
+ if (b.length > meta.length) {
3040
+ throw new Error('Batch value for column ' + column + ' is ' + b.length +
3041
+ ' bytes but the column accepts at most ' + meta.length + ' (' + (meta.field || '?') + ')');
3042
+ }
3043
+ return b;
3044
+ };
3045
+ var toDate = function(v: any): Date {
3046
+ if (v instanceof Date) return v;
3047
+ if (typeof v === 'string') return parseDate(v);
3048
+ return new Date(v);
3049
+ };
3050
+ var scaled = function(v: any, scale: number): number {
3051
+ var n = typeof v === 'string' ? parseFloat(v) : Number(v);
3052
+ return scale ? Math.round(n * Math.pow(10, -scale)) : n;
3053
+ };
3054
+ var scaledBig = function(v: any, scale: number): bigint {
3055
+ if (typeof v === 'bigint') {
3056
+ return scale ? v * (10n ** BigInt(-scale)) : v;
3057
+ }
3058
+ return BigInt(scaled(v, scale));
3059
+ };
3060
+
3061
+ for (var j = 0; j < input.length; j++) {
3062
+ var meta = input[j];
3063
+ var column = j + 1;
3064
+
3065
+ switch (meta.type) {
3066
+ case Const.SQL_TEXT: // CHAR: fixed meta.length bytes, space-padded
3067
+ encoders.push((function(m, col) {
3068
+ return function(msg: any, v: any) {
3069
+ var b = toBytes(v, m, col);
3070
+ if (b.length < m.length) {
3071
+ b = Buffer.concat([b, Buffer.alloc(m.length - b.length, 0x20)]);
3072
+ }
3073
+ msg.addParamBuffer(b);
3074
+ };
3075
+ })(meta, column));
3076
+ offset += meta.length;
3077
+ break;
3078
+
3079
+ case Const.SQL_VARYING:
3080
+ case Const.SQL_NULL:
3081
+ encoders.push((function(m, col) {
3082
+ return function(msg: any, v: any) {
3083
+ var b = toBytes(v, m, col);
3084
+ msg.addInt(b.length);
3085
+ msg.addParamBuffer(b);
3086
+ };
3087
+ })(meta, column));
3088
+ align(2); offset += 2 + meta.length;
3089
+ break;
3090
+
3091
+ case Const.SQL_SHORT:
3092
+ // 2 bytes in the message struct (msglen), 4 on the XDR wire
3093
+ encoders.push((function(m) {
3094
+ return function(msg: any, v: any) { msg.addInt(scaled(v, m.scale)); };
3095
+ })(meta));
3096
+ align(2); offset += 2;
3097
+ break;
3098
+
3099
+ case Const.SQL_LONG:
3100
+ encoders.push((function(m) {
3101
+ return function(msg: any, v: any) { msg.addInt(scaled(v, m.scale)); };
3102
+ })(meta));
3103
+ align(4); offset += 4;
3104
+ break;
3105
+
3106
+ case Const.SQL_INT64:
3107
+ encoders.push((function(m) {
3108
+ return function(msg: any, v: any) {
3109
+ msg.addInt64(typeof v === 'bigint' ? (scaledBig(v, m.scale) as any) : scaled(v, m.scale));
3110
+ };
3111
+ })(meta));
3112
+ align(8); offset += 8;
3113
+ break;
3114
+
3115
+ case Const.SQL_INT128:
3116
+ encoders.push((function(m) {
3117
+ return function(msg: any, v: any) { msg.addInt128(scaledBig(v, m.scale)); };
3118
+ })(meta));
3119
+ align(8); offset += 16;
3120
+ break;
3121
+
3122
+ case Const.SQL_FLOAT:
3123
+ case Const.SQL_D_FLOAT:
3124
+ encoders.push(function(msg: any, v: any) { msg.addFloat(Number(v)); });
3125
+ align(4); offset += 4;
3126
+ break;
3127
+
3128
+ case Const.SQL_DOUBLE:
3129
+ encoders.push(function(msg: any, v: any) { msg.addDouble(Number(v)); });
3130
+ align(8); offset += 8;
3131
+ break;
3132
+
3133
+ case Const.SQL_DEC16:
3134
+ encoders.push(function(msg: any, v: any) { msg.addDecFloat16(v); });
3135
+ align(8); offset += 8;
3136
+ break;
3137
+
3138
+ case Const.SQL_DEC34:
3139
+ encoders.push(function(msg: any, v: any) { msg.addDecFloat34(v); });
3140
+ align(8); offset += 16;
3141
+ break;
3142
+
3143
+ case Const.SQL_BOOLEAN:
3144
+ // xdr_datum sends booleans as a 1-byte opaque (value byte
3145
+ // first, then 3 pad bytes), not as a big-endian int.
3146
+ encoders.push(function(msg: any, v: any) {
3147
+ msg.addBuffer(Buffer.from([v ? 1 : 0]));
3148
+ msg.addAlignment(1);
3149
+ });
3150
+ offset += 1;
3151
+ break;
3152
+
3153
+ case Const.SQL_TIMESTAMP:
3154
+ encoders.push(function(msg: any, v: any) {
3155
+ var parts = Xsql.encodeDateTimeParts(toDate(v));
3156
+ msg.addInt(parts.date);
3157
+ msg.addUInt(parts.time);
3158
+ });
3159
+ align(4); offset += 8;
3160
+ break;
3161
+
3162
+ case Const.SQL_TYPE_DATE:
3163
+ encoders.push(function(msg: any, v: any) {
3164
+ msg.addInt(Xsql.encodeDateTimeParts(toDate(v)).date);
3165
+ });
3166
+ align(4); offset += 4;
3167
+ break;
3168
+
3169
+ case Const.SQL_TYPE_TIME:
3170
+ encoders.push(function(msg: any, v: any) {
3171
+ msg.addUInt(Xsql.encodeDateTimeParts(toDate(v)).time);
3172
+ });
3173
+ align(4); offset += 4;
3174
+ break;
3175
+
3176
+ default:
3177
+ throw new Error('executeBatch does not support the type of parameter ' + column +
3178
+ ' yet (' + (meta.field || '?') + ', SQL type ' + meta.type + ')');
3179
+ }
3180
+
3181
+ // null indicator short that CalcBlr appends per parameter
3182
+ align(2); offset += 2;
3183
+ }
3184
+
3185
+ // msglen must EXACTLY match the length the server computes from the BLR
3186
+ // (MsgMetadata::makeOffsets — no trailing alignment), or op_batch_create
3187
+ // fails with -804 SQLDA errors.
3188
+ return { encoders: encoders, msglen: offset };
3189
+ }
3190
+
3191
+ /**
3192
+ * Build the batch parameter buffer (p_batch_pb): a wide-tagged clumplet
3193
+ * buffer — version byte, then per clumplet a tag byte, int32 LE length and
3194
+ * the value (ints are 4-byte LE).
3195
+ */
3196
+ function buildBatchPb(options: any): Buffer {
3197
+ var parts: number[] = [Const.BATCH_VERSION1];
3198
+
3199
+ var addIntClumplet = function(tag: number, value: number) {
3200
+ parts.push(tag, 4, 0, 0, 0, value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF);
3201
+ };
3202
+
3203
+ addIntClumplet(Const.BATCH_TAG_RECORD_COUNTS, 1);
3204
+ if (!options || options.multiError !== false) {
3205
+ addIntClumplet(Const.BATCH_TAG_MULTIERROR, 1);
3206
+ }
3207
+ if (options && options.bufferSize) {
3208
+ addIntClumplet(Const.BATCH_TAG_BUFFER_BYTES_SIZE, options.bufferSize);
3209
+ }
3210
+ if (options && options.detailedErrors !== undefined) {
3211
+ addIntClumplet(Const.BATCH_TAG_DETAILED_ERRORS, options.detailedErrors);
3212
+ }
3213
+
3214
+ return Buffer.from(parts);
3215
+ }
3216
+
2687
3217
  function CalcBlr(blr: BlrWriter, xsqlda: any[]) {
2688
3218
  blr.addBytes([Const.blr_version5, Const.blr_begin, Const.blr_message, 0]); // + message number
2689
3219
  blr.addWord(xsqlda.length * 2);
package/src/wire/const.ts CHANGED
@@ -121,6 +121,14 @@ const op = {
121
121
  op_crypt_key_callback : 97,
122
122
  op_cond_accept : 98, // Server accepts connection, returns some data to client
123
123
  // and asks client to continue authentication before attach call
124
+ op_batch_create : 99, // Create a statement batch (protocol 16+)
125
+ op_batch_msg : 100, // Add a bunch of messages to the batch
126
+ op_batch_exec : 101, // Execute the batch; server answers with op_batch_cs
127
+ op_batch_rls : 102, // Release the batch
128
+ op_batch_cs : 103, // Batch completion state (response to op_batch_exec)
129
+ op_batch_cancel : 109, // Cancel the batch
130
+ op_batch_sync : 110, // Wait for batch processing
131
+ op_info_batch : 111,
124
132
  op_fetch_scroll : 112,
125
133
  op_info_cursor : 113,
126
134
  op_inline_blob : 114,
@@ -132,6 +140,27 @@ const dsql = {
132
140
  DSQL_unprepare : 4, // >: 2.5
133
141
  };
134
142
 
143
+ // Batch parameter buffer (op_batch_create p_batch_pb) — a wide-tagged
144
+ // clumplet buffer: version byte, then per clumplet a tag byte, an int32 LE
145
+ // length and the value bytes. Tags from Firebird's IBatch interface.
146
+ const batch = {
147
+ BATCH_VERSION1 : 1,
148
+ BATCH_TAG_MULTIERROR : 1, // continue after per-record errors
149
+ BATCH_TAG_RECORD_COUNTS : 2, // return per-record update counts
150
+ BATCH_TAG_BUFFER_BYTES_SIZE: 3, // server-side batch buffer limit
151
+ BATCH_TAG_BLOB_POLICY : 4,
152
+ BATCH_TAG_DETAILED_ERRORS : 5, // max detailed status vectors returned
153
+ BATCH_BLOB_NONE : 0,
154
+ };
155
+
156
+ // fb_cancel_operation kinds sent with op_cancel (protocol 12+ / Firebird 2.5+)
157
+ const cancelKind = {
158
+ fb_cancel_disable : 1, // disable execution of fb_cancel_raise requests
159
+ fb_cancel_enable : 2, // re-enable delivery of cancel requests
160
+ fb_cancel_raise : 3, // cancel the operation currently executing on the attachment
161
+ fb_cancel_abort : 4, // forcibly abort the attachment's current activity
162
+ };
163
+
135
164
  const fetchOp = {
136
165
  fetch_next : 0,
137
166
  fetch_prior : 1,
@@ -858,7 +887,9 @@ const Const = Object.freeze({
858
887
  ...authOptions,
859
888
  ...blr,
860
889
  ...blobType,
890
+ ...batch,
861
891
  ...buffer,
892
+ ...cancelKind,
862
893
  ...cnct,
863
894
  ...common,
864
895
  ...connect,