node-firebird 2.6.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.
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  - [Connection types](#connection-types) — connection options, classic connections, pooling
15
15
  - [Database object (db)](#database-object-db) — database, transaction and statement methods/options
16
16
  - [Examples](#examples) — parametrized queries, BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
17
- - [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, query cancellation (AbortSignal), statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
17
+ - [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, query cancellation (AbortSignal), batch execution (bulk inserts), statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
18
18
  - [Using node-firebird with Express.js](#using-node-firebird-with-expressjs)
19
19
  - [FAQ](#faq)
20
20
  - [Contributing](#contributing) · [Contributors](#contributors)
@@ -133,9 +133,9 @@ Available wrappers:
133
133
 
134
134
  - **module** — `Firebird.attachAsync(options)`, `createAsync`, `attachOrCreateAsync`, `dropAsync`; resolve with a `Database` (or a `ServiceManager` when `options.manager` is `true`)
135
135
  - **pool** — `pool.getAsync()`, `pool.destroyAsync()`, `pool.withConnection(work)`
136
- - **database** — `db.queryAsync(sql, params?, options?)`, `executeAsync`, `sequentiallyAsync(sql, params?, onRow, options?)`, `transactionAsync(options?)`, `newStatementAsync(sql)`, `attachEventAsync()`, `detachAsync()`, `dropAsync()`, `db.withTransaction(work, options?)`
137
- - **transaction** — `queryAsync`, `executeAsync`, `sequentiallyAsync`, `newStatementAsync`, `commitAsync`, `rollbackAsync`, `commitRetainingAsync`, `rollbackRetainingAsync`
138
- - **statement** — `executeAsync(transaction, params?, options?)`, `fetchAsync`, `fetchScrollAsync`, `fetchAllAsync`, `closeAsync`, `dropAsync`, `releaseAsync`
136
+ - **database** — `db.queryAsync(sql, params?, options?)`, `executeAsync`, `executeBatchAsync(sql, rows, options?)`, `sequentiallyAsync(sql, params?, onRow, options?)`, `transactionAsync(options?)`, `newStatementAsync(sql)`, `attachEventAsync()`, `detachAsync()`, `dropAsync()`, `db.withTransaction(work, options?)`
137
+ - **transaction** — `queryAsync`, `executeAsync`, `executeBatchAsync`, `sequentiallyAsync`, `newStatementAsync`, `commitAsync`, `rollbackAsync`, `commitRetainingAsync`, `rollbackRetainingAsync`
138
+ - **statement** — `executeAsync(transaction, params?, options?)`, `executeBatchAsync(transaction, rows, options?)`, `fetchAsync`, `fetchScrollAsync`, `fetchAllAsync`, `closeAsync`, `dropAsync`, `releaseAsync`
139
139
 
140
140
  Notes:
141
141
 
@@ -277,6 +277,7 @@ sequenceDiagram
277
277
 
278
278
  - `db.query(query, [params], function(err, result), options)` - classic query, returns Array of Object
279
279
  - `db.execute(query, [params], function(err, result), options)` - classic query, returns Array of Array
280
+ - `db.executeBatch(query, rows, function(err, result), options)` - bulk execution in one round-trip, all-or-nothing (FB >= 4.0, see [Batch Execution](#batch-execution-firebird-40))
280
281
  - `db.sequentially(query, [params], function(row, index), function(err), options)` - sequentially query
281
282
  - `db.detach(function(err))` detach a database
282
283
  - `db.transaction(options, function(err, transaction))` create transaction
@@ -303,6 +304,7 @@ const options = {
303
304
 
304
305
  - `transaction.query(query, [params], function(err, result), options)` - classic query, returns Array of Object
305
306
  - `transaction.execute(query, [params], function(err, result), options)` - classic query, returns Array of Array
307
+ - `transaction.executeBatch(query, rows, function(err, result), options)` - bulk execution with per-record errors (FB >= 4.0, see [Batch Execution](#batch-execution-firebird-40))
306
308
  - `transaction.sequentially(query, [params], function(row, index), function(err), options)` - sequentially query
307
309
  - `transaction.commit(function(err))` commit current transaction
308
310
  - `transaction.rollback(function(err))` rollback current transaction
@@ -1192,6 +1194,60 @@ Notes:
1192
1194
  - A signal that is already aborted rejects immediately with an `AbortError`
1193
1195
  without contacting the server; cancelling an idle connection is harmless.
1194
1196
 
1197
+ ### Batch Execution (Firebird 4.0+)
1198
+
1199
+ `executeBatch` sends many parameter rows for one statement in a single
1200
+ round-trip using the Firebird 4 wire batch API (`op_batch_create` /
1201
+ `op_batch_msg` / `op_batch_exec`) — typically 5–10× faster than executing
1202
+ row by row. Available on the database, transaction and statement objects, in
1203
+ callback and promise flavours.
1204
+
1205
+ ```js
1206
+ const rows = [
1207
+ [1, 'Alice', 10.50, new Date(), true, 100n],
1208
+ [2, 'Bob', null, new Date(), false, null], // NULLs per column
1209
+ [3, 'Carol', 7.25, new Date(), true, 300n],
1210
+ ];
1211
+
1212
+ // database level: own transaction, all-or-nothing (rollback if any record fails)
1213
+ const res = await db.executeBatchAsync(
1214
+ 'INSERT INTO t (id, name, amount, created, active, big) VALUES (?, ?, ?, ?, ?, ?)',
1215
+ rows);
1216
+ console.log(res.recordCount, res.updateCounts, res.success);
1217
+
1218
+ // transaction level: partial success — failed records are reported,
1219
+ // the rest can still be committed
1220
+ const tr = await db.transactionAsync();
1221
+ const r = await tr.executeBatchAsync('INSERT INTO t (...) VALUES (?, ?, ?, ?, ?, ?)', rows);
1222
+ if (!r.success) {
1223
+ console.log('failed record indexes:', r.errorRecordNumbers); // 0-based
1224
+ console.log('first error:', r.errors[0].error.message);
1225
+ }
1226
+ await tr.commitAsync();
1227
+ ```
1228
+
1229
+ The result object contains `recordCount`, `updateCounts` (one entry per
1230
+ record), `errors` (`{ recordNumber, error }` with full gdscode details),
1231
+ `errorRecordNumbers` and `success`. At the database level a failed batch
1232
+ rejects with the first record error, carrying the full completion state as
1233
+ `err.batchCompletion`.
1234
+
1235
+ Options (last argument): `chunkSize` (rows per `op_batch_msg` packet,
1236
+ default 500), `multiError` (default `true` — collect all record errors
1237
+ instead of stopping at the first), `bufferSize` (server-side batch buffer
1238
+ in bytes).
1239
+
1240
+ Notes:
1241
+
1242
+ - Requires wire protocol 16+ (Firebird 4.0 or newer server).
1243
+ - Values are encoded from the statement's own parameter metadata, so
1244
+ NUMERIC/DECIMAL scale, `BIGINT`/`INT128` (pass `BigInt`), `BOOLEAN`,
1245
+ `TIMESTAMP`/`DATE`/`TIME`, `FLOAT`/`DOUBLE` and `DECFLOAT` all round-trip
1246
+ exactly. BLOB and ARRAY parameters are not supported in batches yet.
1247
+ - Oversized `CHAR`/`VARCHAR` values fail the batch client-side before
1248
+ anything is sent; server-side record errors (constraint violations,
1249
+ truncation…) are reported per record.
1250
+
1195
1251
  ### Statement Timeouts (Firebird 4.0+)
1196
1252
  Setting a statement timeout allows the client to automatically abort queries that take too long on the server.
1197
1253
  ```js
package/lib/types.d.ts CHANGED
@@ -64,6 +64,32 @@ export type TransactionOptions = {
64
64
  wait?: boolean;
65
65
  waitTimeout?: number;
66
66
  };
67
+ /** Result of an executeBatch call (Firebird 4 batch API, protocol 16+). */
68
+ export interface BatchResult {
69
+ /** Total number of records processed by the server. */
70
+ recordCount: number;
71
+ /** Per-record update counts (in record order). */
72
+ updateCounts: number[];
73
+ /** Per-record failures with full status vectors (capped by `detailedErrors`). */
74
+ errors: Array<{
75
+ recordNumber: number;
76
+ error: Error;
77
+ }>;
78
+ /** Record numbers of ALL failed records (detailed + status-less). */
79
+ errorRecordNumbers: number[];
80
+ /** True when every record executed without error. */
81
+ success: boolean;
82
+ }
83
+ export type BatchOptions = {
84
+ /** Continue past per-record errors and report them all (default true). */
85
+ multiError?: boolean;
86
+ /** Server-side batch buffer limit in bytes (BATCH_TAG_BUFFER_BYTES_SIZE). */
87
+ bufferSize?: number;
88
+ /** Max number of detailed per-record status vectors returned (server default 64). */
89
+ detailedErrors?: number;
90
+ /** Rows per op_batch_msg packet (default 500). */
91
+ chunkSize?: number;
92
+ };
67
93
  export type QueryOptions = {
68
94
  timeout?: number;
69
95
  scrollable?: boolean;
@@ -83,6 +109,8 @@ export interface Database {
83
109
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
84
110
  query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
85
111
  execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
112
+ /** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
113
+ executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
86
114
  sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
87
115
  drop(callback: SimpleCallback): void;
88
116
  escape(value: any): string;
@@ -93,6 +121,7 @@ export interface Database {
93
121
  createSchema(schemaName: string, tablespaceName?: string | QueryCallback, callback?: QueryCallback): Database;
94
122
  queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
95
123
  executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
124
+ executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
96
125
  sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
97
126
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
98
127
  transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
@@ -116,6 +145,8 @@ export interface Transaction {
116
145
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
117
146
  query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
118
147
  execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
148
+ /** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
149
+ executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
119
150
  sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
120
151
  commit(callback?: SimpleCallback): void;
121
152
  commitRetaining(callback?: SimpleCallback): void;
@@ -123,6 +154,7 @@ export interface Transaction {
123
154
  rollbackRetaining(callback?: SimpleCallback): void;
124
155
  queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
125
156
  executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
157
+ executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
126
158
  sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
127
159
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
128
160
  newStatementAsync(query: string): Promise<Statement>;
@@ -139,7 +171,10 @@ export interface Statement {
139
171
  fetch(transaction: Transaction, count: number, callback: QueryCallback): void;
140
172
  fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
141
173
  fetchAll(transaction: Transaction, callback: QueryCallback): void;
174
+ /** Execute this prepared statement once per row (Firebird 4.0+ batch API). */
175
+ executeBatch(transaction: Transaction, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
142
176
  executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
177
+ executeBatchAsync(transaction: Transaction, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
143
178
  fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
144
179
  fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
145
180
  fetchAllAsync(transaction: Transaction): Promise<any>;
@@ -64,6 +64,8 @@ declare class Connection {
64
64
  * op_cancel packet itself has no response, so nothing is queued here.
65
65
  */
66
66
  cancelOperation(kind: any, callback: any): this;
67
+ /** Write a prebuilt packet and queue its response callback. */
68
+ _queueEventBuffer(buffer: any, callback: any): void;
67
69
  _queueEvent(callback: any, defer?: boolean): void;
68
70
  connect(options: any, callback: any): void;
69
71
  attach(options: any, callback?: any, db?: any): void;
@@ -82,6 +84,19 @@ declare class Connection {
82
84
  allocateAndPrepareStatement(transaction: any, query: any, plan: any, callback: any): void;
83
85
  prepare(transaction: any, query: any, plan: any, callback: any): void;
84
86
  prepareStatement(transaction: any, statement: any, query: any, plan: any, callback: any): this;
87
+ /**
88
+ * Execute a statement once per row using the Firebird 4 batch API
89
+ * (protocol 16+): op_batch_create + op_batch_msg(s) + op_batch_exec +
90
+ * op_batch_rls, all pipelined in a single network flush. Every packet
91
+ * gets an in-order response (op_batch_cs for exec), so the regular
92
+ * response queue keeps everything in sync.
93
+ *
94
+ * rows: array of parameter arrays, one per record. BLOB/ARRAY columns
95
+ * are not supported yet. The callback receives a completion object:
96
+ * { recordCount, updateCounts, errors: [{recordNumber, error}],
97
+ * errorRecordNumbers, success }.
98
+ */
99
+ executeBatch(transaction: any, statement: any, rows: any, callback: any, options: any): this;
85
100
  executeStatement(transaction: any, statement: any, params: any, callback: any, custom: any): this;
86
101
  sendExecute(op: number, statement: any, transaction: any, callback: any, parameters?: any[]): void;
87
102
  fetch(statement: any, transaction: any, count: any, callback: any): void;