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.
- package/CHANGELOG.md +112 -0
- package/dist/.tsbuildinfo.build +1 -1
- package/dist/browser/engine-transport.d.ts +19 -0
- package/dist/browser/engine-transport.d.ts.map +1 -1
- package/dist/browser/engine-transport.js +67 -0
- package/dist/browser/engine-transport.js.map +1 -1
- package/dist/browser/firebird-browser.d.ts +44 -1
- package/dist/browser/firebird-browser.d.ts.map +1 -1
- package/dist/browser/firebird-browser.js +123 -0
- package/dist/browser/firebird-browser.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/opfs-fs.d.ts.map +1 -1
- package/dist/browser/opfs-fs.js +18 -7
- package/dist/browser/opfs-fs.js.map +1 -1
- package/dist/browser/shared-transport.d.ts +3 -0
- package/dist/browser/shared-transport.d.ts.map +1 -1
- package/dist/browser/shared-transport.js +9 -0
- package/dist/browser/shared-transport.js.map +1 -1
- package/dist/browser/worker-protocol.d.ts +1 -1
- package/dist/browser/worker-protocol.d.ts.map +1 -1
- package/dist/browser/worker-transport.d.ts +3 -0
- package/dist/browser/worker-transport.d.ts.map +1 -1
- package/dist/browser/worker-transport.js +9 -0
- package/dist/browser/worker-transport.js.map +1 -1
- package/dist/drizzle/index.d.ts +170 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +627 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/electric/apply.d.ts +1 -0
- package/dist/electric/apply.d.ts.map +1 -1
- package/dist/electric/apply.js +28 -8
- package/dist/electric/apply.js.map +1 -1
- package/dist/electric/index.js +6 -1
- package/dist/electric/index.js.map +1 -1
- package/dist/electric/values.d.ts.map +1 -1
- package/dist/electric/values.js +5 -2
- package/dist/electric/values.js.map +1 -1
- package/dist/electric-server/catalog.d.ts.map +1 -1
- package/dist/electric-server/catalog.js +3 -2
- package/dist/electric-server/catalog.js.map +1 -1
- package/dist/electric-server/index.d.ts +19 -0
- package/dist/electric-server/index.d.ts.map +1 -1
- package/dist/electric-server/index.js +147 -14
- package/dist/electric-server/index.js.map +1 -1
- package/dist/firebird.d.ts +48 -9
- package/dist/firebird.d.ts.map +1 -1
- package/dist/firebird.js +927 -60
- package/dist/firebird.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/kysely/index.d.ts +197 -0
- package/dist/kysely/index.d.ts.map +1 -0
- package/dist/kysely/index.js +777 -0
- package/dist/kysely/index.js.map +1 -0
- package/dist/sql-tag.d.ts +5 -16
- package/dist/sql-tag.d.ts.map +1 -1
- package/dist/sql-tag.js +15 -0
- package/dist/sql-tag.js.map +1 -1
- package/dist/types.d.ts +24 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/wasm/firebird-embedded-st.js +1 -1
- package/dist/wasm/firebird-embedded-st.wasm +0 -0
- package/dist/wasm/firebird-embedded.js +1 -1
- package/dist/wasm/firebird-embedded.wasm +0 -0
- package/dist/wasm-loader.d.ts +16 -0
- package/dist/wasm-loader.d.ts.map +1 -1
- package/dist/wasm-loader.js.map +1 -1
- 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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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'
|
|
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
|
|
251
|
-
*
|
|
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
|
|
361
|
+
const fields = await outputFields(stmt);
|
|
265
362
|
const type = await stmt.type;
|
|
266
363
|
return {
|
|
267
364
|
params: undefined,
|
|
268
|
-
fields
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
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,
|
|
524
|
+
await this.executeInBlocks(lease.stmt, plan, serializedRows);
|
|
415
525
|
}
|
|
416
526
|
else {
|
|
417
|
-
for (const row of
|
|
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
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
598
|
-
const
|
|
599
|
-
|
|
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([
|
|
603
|
-
/** `
|
|
604
|
-
const
|
|
605
|
-
|
|
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([
|
|
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
|
|
642
|
-
* the driver binds those as doubles, and
|
|
643
|
-
* 9007199254740992, `'0.1'` in a
|
|
644
|
-
* A JS number still goes as a double
|
|
645
|
-
* PRECISION or FLOAT parameter is still
|
|
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' ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
781
|
-
* it to release as it always does, and release the ones made
|
|
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
|
|
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,
|
|
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
|
-
|
|
1721
|
+
rawRows = await resultSet.fetch();
|
|
930
1722
|
}
|
|
931
1723
|
finally {
|
|
932
1724
|
await resultSet.close().catch(() => undefined);
|
|
933
1725
|
}
|
|
934
1726
|
}
|
|
935
|
-
if (
|
|
936
|
-
|
|
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
|