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.
@@ -229,6 +229,54 @@ class Database extends Events.EventEmitter {
229
229
  return self;
230
230
  }
231
231
 
232
+ /**
233
+ * Bulk-execute `query` once per row via the Firebird 4 batch API
234
+ * (protocol 16+) with all-or-nothing semantics: the batch runs in its
235
+ * own transaction, committed only when every record succeeded and
236
+ * rolled back otherwise. On failure the error of the first failed
237
+ * record is reported, with the full completion attached as
238
+ * err.batchCompletion. Use transaction.executeBatch for partial-success
239
+ * handling.
240
+ */
241
+ executeBatch(query: string, rows: any[][], callback?: any, options?: any): this {
242
+ var self = this;
243
+
244
+ self.connection.startTransaction(function(err: any, transaction: any) {
245
+ if (err) {
246
+ doError(err, callback);
247
+ return;
248
+ }
249
+
250
+ transaction.executeBatch(query, rows, function(err: any, result: any) {
251
+ if (err) {
252
+ transaction.rollback(function() {
253
+ doError(err, callback);
254
+ });
255
+ return;
256
+ }
257
+
258
+ if (!result.success) {
259
+ transaction.rollback(function() {
260
+ var first = result.errors.length ? result.errors[0] : null;
261
+ var batchError: any = first
262
+ ? first.error
263
+ : new Error('Batch failed for record(s) ' + result.errorRecordNumbers.join(', '));
264
+ batchError.batchCompletion = result;
265
+ doError(batchError, callback);
266
+ });
267
+ return;
268
+ }
269
+
270
+ transaction.commit(function(err: any) {
271
+ if (callback)
272
+ callback(err, result);
273
+ });
274
+ }, options);
275
+ });
276
+
277
+ return self;
278
+ }
279
+
232
280
  sequentially(query: string, params?: any, on?: any, callback?: any, options: any = {}): this {
233
281
  if (params instanceof Function) {
234
282
  options = callback;
@@ -322,6 +370,22 @@ class Database extends Events.EventEmitter {
322
370
  return this.connection.dropDatabase(callback);
323
371
  }
324
372
 
373
+ /**
374
+ * Cancel the operation currently executing on this connection by sending
375
+ * an out-of-band op_cancel (Firebird 2.5+ / protocol 12+). The cancelled
376
+ * operation fails through its own callback with err.gdscode ===
377
+ * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
378
+ * per-attachment, not per-statement.
379
+ */
380
+ cancel(kind?: number | ((err?: any) => void), callback?: (err?: any) => void): this {
381
+ if (typeof kind === 'function') {
382
+ callback = kind;
383
+ kind = undefined;
384
+ }
385
+ this.connection.cancelOperation(kind, callback);
386
+ return this;
387
+ }
388
+
325
389
  attachEvent(callback: (err: any, evt?: any) => void): this {
326
390
  var self = this;
327
391
  const eventid = self.eventid++;
@@ -457,6 +521,11 @@ class Database extends Events.EventEmitter {
457
521
  return fromCallback(function(cb) { self.execute(query, params, cb, options); });
458
522
  }
459
523
 
524
+ executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any> {
525
+ var self = this;
526
+ return fromCallback(function(cb) { self.executeBatch(query, rows, cb, options); });
527
+ }
528
+
460
529
  sequentiallyAsync(query: string, params?: any, on?: any, options?: any): Promise<void> {
461
530
  if (params instanceof Function) {
462
531
  options = on;
@@ -496,6 +565,11 @@ class Database extends Events.EventEmitter {
496
565
  return fromCallback(function(cb) { self.attachEvent(cb); });
497
566
  }
498
567
 
568
+ cancelAsync(kind?: number): Promise<void> {
569
+ var self = this;
570
+ return fromCallback(function(cb) { self.cancel(kind, cb); });
571
+ }
572
+
499
573
  /**
500
574
  * Run `work` inside a transaction: commits when the returned promise
501
575
  * resolves, rolls back when it rejects (the original error is rethrown,
@@ -282,12 +282,12 @@ export class XdrWriter {
282
282
  this.pos += 4;
283
283
  }
284
284
 
285
- addInt64(value: number): void {
285
+ addInt64(value: number | bigint): void {
286
286
  this.ensure(8);
287
- // Note: precision is limited to Number.MAX_SAFE_INTEGER (±2^53-1).
288
- // Values outside this range lose precision, which matches the previous
289
- // Long.fromNumber() behaviour.
290
- this.buffer.writeBigInt64BE(BigInt(Math.trunc(value)), this.pos);
287
+ // Note: for numbers, precision is limited to Number.MAX_SAFE_INTEGER
288
+ // (±2^53-1); values outside this range lose precision, which matches
289
+ // the previous Long.fromNumber() behaviour. BigInts keep full precision.
290
+ this.buffer.writeBigInt64BE(typeof value === 'bigint' ? value : BigInt(Math.trunc(value)), this.pos);
291
291
  this.pos += 8;
292
292
  }
293
293
 
@@ -381,6 +381,12 @@ export class XdrWriter {
381
381
  this.pos += 8;
382
382
  }
383
383
 
384
+ addFloat(value: number): void {
385
+ this.ensure(4);
386
+ this.buffer.writeFloatBE(value, this.pos);
387
+ this.pos += 4;
388
+ }
389
+
384
390
  addQuad(quad: { low: number; high: number }): void {
385
391
  this.ensure(8);
386
392
  var b = this.buffer;
@@ -69,6 +69,14 @@ class Statement {
69
69
  this.connection.fetchAll(this, transaction, callback);
70
70
  }
71
71
 
72
+ /**
73
+ * Execute this statement once per row via the Firebird 4 batch API
74
+ * (protocol 16+). `rows` is an array of parameter arrays.
75
+ */
76
+ executeBatch(transaction: any, rows: any[][], callback?: any, options?: any): void {
77
+ this.connection.executeBatch(transaction, this, rows, callback, options);
78
+ }
79
+
72
80
  /* Promise / async-await API — wrappers over the callback methods above. */
73
81
 
74
82
  executeAsync(transaction: any, params?: any, options?: any): Promise<any> {
@@ -76,6 +84,11 @@ class Statement {
76
84
  return fromCallback(function(cb) { self.execute(transaction, params, cb, options); });
77
85
  }
78
86
 
87
+ executeBatchAsync(transaction: any, rows: any[][], options?: any): Promise<any> {
88
+ var self = this;
89
+ return fromCallback(function(cb) { self.executeBatch(transaction, rows, cb, options); });
90
+ }
91
+
79
92
  fetchAsync(transaction: any, count: number | string): Promise<any> {
80
93
  var self = this;
81
94
  return fromCallback(function(cb) { self.fetch(transaction, count, cb); });
@@ -8,6 +8,38 @@ import Const from './const';
8
8
  *
9
9
  ***************************************/
10
10
 
11
+ /** The error delivered when options.signal was already aborted on entry. */
12
+ function abortError(signal: any): Error {
13
+ if (signal && signal.reason instanceof Error)
14
+ return signal.reason;
15
+ var err: any = new Error('The operation was aborted');
16
+ err.name = 'AbortError';
17
+ err.code = 'ABORT_ERR';
18
+ return err;
19
+ }
20
+
21
+ /**
22
+ * Wire an AbortSignal to a running statement: on abort, send an out-of-band
23
+ * op_cancel so the server fails the executing operation with isc_cancelled
24
+ * (surfaced through the statement's own callback as err.gdscode ===
25
+ * GDSCode.CANCELLED). Returns the wrapped callback that detaches the
26
+ * listener once the operation settles.
27
+ */
28
+ function hookAbortSignal(connection: any, signal: any, callback: any): any {
29
+ var settled = false;
30
+ var onAbort = function() {
31
+ if (!settled)
32
+ connection.cancelOperation(Const.fb_cancel_raise);
33
+ };
34
+ signal.addEventListener('abort', onAbort, { once: true });
35
+ return function(err?: any, result?: any, meta?: any, isSelect?: boolean) {
36
+ settled = true;
37
+ signal.removeEventListener('abort', onAbort);
38
+ if (callback)
39
+ callback(err, result, meta, isSelect);
40
+ };
41
+ }
42
+
11
43
  class Transaction {
12
44
  connection: any;
13
45
  db: any;
@@ -38,6 +70,15 @@ class Transaction {
38
70
  params = undefined;
39
71
  }
40
72
 
73
+ var signal = options && options.signal;
74
+ if (signal) {
75
+ if (signal.aborted) {
76
+ doError(abortError(signal), callback);
77
+ return;
78
+ }
79
+ callback = hookAbortSignal(this.connection, signal, callback);
80
+ }
81
+
41
82
  var self = this;
42
83
  this.newStatement(query, function(err: any, statement: any) {
43
84
 
@@ -192,6 +233,36 @@ class Transaction {
192
233
  this.execute(query, params, callback, options);
193
234
  }
194
235
 
236
+ /**
237
+ * Execute `query` once per row in `rows` using the Firebird 4 batch API
238
+ * (protocol 16+, single network flush). The callback receives a
239
+ * completion object: { recordCount, updateCounts, errors:
240
+ * [{recordNumber, error}], errorRecordNumbers, success }. Per-record
241
+ * failures do NOT roll anything back here — inspect the completion and
242
+ * commit or roll back yourself (or use db.executeBatch for
243
+ * all-or-nothing semantics).
244
+ */
245
+ executeBatch(query: string, rows: any[][], callback?: any, options?: any): void {
246
+ var self = this;
247
+ this.newStatement(query, function(err: any, statement: any) {
248
+ if (err) {
249
+ doError(err, callback);
250
+ return;
251
+ }
252
+
253
+ statement.executeBatch(self, rows, function(err: any, result: any) {
254
+ statement.release();
255
+ if (callback)
256
+ callback(err, result);
257
+ }, options);
258
+ });
259
+ }
260
+
261
+ executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any> {
262
+ var self = this;
263
+ return fromCallback(function(cb) { self.executeBatch(query, rows, cb, options); });
264
+ }
265
+
195
266
  commit(callback?: (err?: any) => void): void {
196
267
  this.connection.commit(this, callback);
197
268
  }
@@ -718,6 +718,28 @@ export class SQLParamBuffer {
718
718
 
719
719
  //------------------------------------------------------
720
720
 
721
+ /**
722
+ * Split a JS Date into the Firebird wire representation used by
723
+ * TIMESTAMP/DATE/TIME columns: `date` is the modified-Julian day number and
724
+ * `time` the count of 100-microsecond units since midnight (local time).
725
+ */
726
+ export function encodeDateTimeParts(value: Date): { date: number; time: number } {
727
+ var ms = value.getTime() - value.getTimezoneOffset() * MsPerMinute;
728
+ var time = ms % TimeCoeff;
729
+ var date = (ms - time) / TimeCoeff + DateOffset;
730
+ time *= 10;
731
+
732
+ // check overflow (dates before the epoch)
733
+ if (time < 0) {
734
+ date--;
735
+ time = TimeCoeff * 10 + time;
736
+ }
737
+
738
+ return { date: date, time: time };
739
+ }
740
+
741
+ //------------------------------------------------------
742
+
721
743
  export class SQLParamQuad {
722
744
  value: any;
723
745
 
@@ -753,20 +775,9 @@ export class SQLParamDate {
753
775
 
754
776
  encode(data: XdrWriter): void {
755
777
  if (this.value != null) {
756
-
757
- var value = this.value.getTime() - this.value.getTimezoneOffset() * MsPerMinute;
758
- var time = value % TimeCoeff;
759
- var date = (value - time) / TimeCoeff + DateOffset;
760
- time *= 10;
761
-
762
- // check overflow
763
- if (time < 0) {
764
- date--;
765
- time = TimeCoeff*10 + time;
766
- }
767
-
768
- data.addInt(date);
769
- data.addUInt(time);
778
+ var parts = encodeDateTimeParts(this.value);
779
+ data.addInt(parts.date);
780
+ data.addUInt(parts.time);
770
781
  } else {
771
782
  data.addInt(0);
772
783
  data.addUInt(0);