node-firebird 2.11.0 → 2.13.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 +287 -9
- package/lib/pool.d.ts +14 -1
- package/lib/pool.js +59 -16
- package/lib/sql-template.d.ts +81 -0
- package/lib/sql-template.js +162 -0
- package/lib/types.d.ts +152 -5
- package/lib/uri.js +53 -2
- package/lib/utils.d.ts +12 -0
- package/lib/utils.js +20 -2
- package/lib/wire/batch-stream.d.ts +26 -0
- package/lib/wire/batch-stream.js +109 -0
- package/lib/wire/codepages.d.ts +23 -0
- package/lib/wire/codepages.js +137 -0
- package/lib/wire/connection.d.ts +44 -4
- package/lib/wire/connection.js +344 -80
- package/lib/wire/const.d.ts +5 -0
- package/lib/wire/const.js +12 -0
- package/lib/wire/database.d.ts +18 -0
- package/lib/wire/database.js +40 -13
- package/lib/wire/serialize.d.ts +2 -0
- package/lib/wire/serialize.js +14 -0
- package/lib/wire/socket.js +9 -3
- package/lib/wire/transaction.d.ts +33 -0
- package/lib/wire/transaction.js +156 -13
- package/lib/wire/xsqlvar.d.ts +118 -2
- package/lib/wire/xsqlvar.js +271 -34
- package/package.json +19 -1
- package/src/pool.ts +57 -14
- package/src/sql-template.ts +196 -0
- package/src/types.ts +153 -6
- package/src/uri.ts +54 -2
- package/src/utils.ts +19 -1
- package/src/wire/batch-stream.ts +121 -0
- package/src/wire/codepages.ts +147 -0
- package/src/wire/connection.ts +374 -86
- package/src/wire/const.ts +13 -0
- package/src/wire/database.ts +46 -15
- package/src/wire/serialize.ts +16 -1
- package/src/wire/socket.ts +9 -3
- package/src/wire/transaction.ts +166 -19
- package/src/wire/xsqlvar.ts +298 -34
package/src/wire/const.ts
CHANGED
|
@@ -587,6 +587,12 @@ const sqlInfo = {
|
|
|
587
587
|
isc_info_sql_stmt_type : 21,
|
|
588
588
|
isc_info_sql_get_plan : 22,
|
|
589
589
|
isc_info_sql_records : 23,
|
|
590
|
+
// per-verb row counts nested inside an isc_info_sql_records cluster
|
|
591
|
+
// (inf_pub.h isc_info_req_*)
|
|
592
|
+
isc_info_req_select_count : 13,
|
|
593
|
+
isc_info_req_insert_count : 14,
|
|
594
|
+
isc_info_req_update_count : 15,
|
|
595
|
+
isc_info_req_delete_count : 16,
|
|
590
596
|
isc_info_sql_batch_fetch : 24,
|
|
591
597
|
isc_info_sql_relation_alias : 25, // >: 2.0
|
|
592
598
|
isc_info_sql_explain_plan : 26, // >= 3.0
|
|
@@ -669,6 +675,12 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
669
675
|
sqlInfo.isc_info_sql_describe_end
|
|
670
676
|
];
|
|
671
677
|
|
|
678
|
+
// op_info_sql request for the per-verb DML row counts of an executed
|
|
679
|
+
// statement (withMeta / affectedRows).
|
|
680
|
+
const RECORDS_INFO = [
|
|
681
|
+
sqlInfo.isc_info_sql_records,
|
|
682
|
+
];
|
|
683
|
+
|
|
672
684
|
/***********************/
|
|
673
685
|
/* ISC Services */
|
|
674
686
|
/***********************/
|
|
@@ -910,6 +922,7 @@ const Const = Object.freeze({
|
|
|
910
922
|
...defaultOptions,
|
|
911
923
|
DESCRIBE,
|
|
912
924
|
DESCRIBE_WITH_SCHEMA,
|
|
925
|
+
RECORDS_INFO,
|
|
913
926
|
...dpb,
|
|
914
927
|
...dsql,
|
|
915
928
|
...fetchOp,
|
package/src/wire/database.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import Events from 'events';
|
|
2
2
|
import { doError, fromCallback, type Callback, type SimpleCallback } from '../callback';
|
|
3
|
-
import { escape } from '../utils';
|
|
3
|
+
import { batchResultToError, escape } from '../utils';
|
|
4
4
|
import Const from './const';
|
|
5
|
-
import {
|
|
5
|
+
import { makeSqlTag, type SqlTag } from '../sql-template';
|
|
6
|
+
import { computeColumnKeys, nestCell, resolveKeyTransform, resolveNestTables } from './xsqlvar';
|
|
6
7
|
import EventConnection from './eventConnection';
|
|
7
8
|
import FbEventManager from './fbEventManager';
|
|
8
9
|
import makeQueryStream from './query-stream';
|
|
10
|
+
import makeBatchStream from './batch-stream';
|
|
9
11
|
import type Connection from './connection';
|
|
10
12
|
import type Transaction from './transaction';
|
|
11
13
|
import type Statement from './statement';
|
|
@@ -95,7 +97,7 @@ function readblob(blob: any, callback: Callback): void {
|
|
|
95
97
|
});
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
function fetchBlobSyncRow(row: any, meta: any[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined, callback: Callback): void {
|
|
100
|
+
function fetchBlobSyncRow(row: any, meta: any[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined, transform: ((key: string) => string) | undefined, callback: Callback): void {
|
|
99
101
|
if (!row || !meta || !meta.length || !meta.some((m) => m && m.type === Const.SQL_BLOB)) {
|
|
100
102
|
callback(null, row);
|
|
101
103
|
return;
|
|
@@ -106,7 +108,7 @@ function fetchBlobSyncRow(row: any, meta: any[], nestTables: boolean | string |
|
|
|
106
108
|
// duplicate JOIN column names (and nested rows) break that alignment.
|
|
107
109
|
// Array rows (sequentially's legacy boolean form) are keyed by index.
|
|
108
110
|
const isArrayRow = Array.isArray(row);
|
|
109
|
-
const keys = isArrayRow ? null : computeColumnKeys(meta, nestTables, lowercaseKeys);
|
|
111
|
+
const keys = isArrayRow ? null : computeColumnKeys(meta, nestTables, lowercaseKeys, transform);
|
|
110
112
|
const blobCells: { target: any; key: string | number }[] = [];
|
|
111
113
|
|
|
112
114
|
for (let i = 0; i < meta.length; i++) {
|
|
@@ -147,6 +149,7 @@ function fetchBlobSyncRow(row: any, meta: any[], nestTables: boolean | string |
|
|
|
147
149
|
class Database extends Events.EventEmitter {
|
|
148
150
|
connection: Connection;
|
|
149
151
|
eventid: number;
|
|
152
|
+
private _sql?: SqlTag;
|
|
150
153
|
|
|
151
154
|
constructor(connection: Connection) {
|
|
152
155
|
super();
|
|
@@ -155,6 +158,17 @@ class Database extends Events.EventEmitter {
|
|
|
155
158
|
this.eventid = 1;
|
|
156
159
|
}
|
|
157
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Tagged-template query API: db.sql`SELECT ... ${value}` (see README).
|
|
163
|
+
* Built lazily on first access; the compiled text is positional-only,
|
|
164
|
+
* so the namedPlaceholders rewriter is disabled — any `:token` in the
|
|
165
|
+
* template is PSQL (EXECUTE BLOCK), not a placeholder.
|
|
166
|
+
*/
|
|
167
|
+
get sql(): SqlTag {
|
|
168
|
+
return this._sql || (this._sql = makeSqlTag((text, params, options) =>
|
|
169
|
+
this.queryAsync(text, params, { ...options, namedPlaceholders: false })));
|
|
170
|
+
}
|
|
171
|
+
|
|
158
172
|
escape(value: any): string {
|
|
159
173
|
return escape(value, this.connection.accept.protocolVersion);
|
|
160
174
|
}
|
|
@@ -287,12 +301,7 @@ class Database extends Events.EventEmitter {
|
|
|
287
301
|
|
|
288
302
|
if (!result.success) {
|
|
289
303
|
transaction.rollback(function() {
|
|
290
|
-
|
|
291
|
-
var batchError: any = first
|
|
292
|
-
? first.error
|
|
293
|
-
: new Error('Batch failed for record(s) ' + result.errorRecordNumbers.join(', '));
|
|
294
|
-
batchError.batchCompletion = result;
|
|
295
|
-
doError(batchError, callback);
|
|
304
|
+
doError(batchResultToError(result), callback);
|
|
296
305
|
});
|
|
297
306
|
return;
|
|
298
307
|
}
|
|
@@ -325,6 +334,9 @@ class Database extends Events.EventEmitter {
|
|
|
325
334
|
}
|
|
326
335
|
|
|
327
336
|
var self = this;
|
|
337
|
+
var keyResolutionDone = false;
|
|
338
|
+
var resolvedNest: boolean | string | undefined;
|
|
339
|
+
var resolvedTransform: ((key: string) => string) | undefined;
|
|
328
340
|
var _on = function(row: any, i: number, meta: any[], next: (err?: any) => void) {
|
|
329
341
|
var done = false;
|
|
330
342
|
var finish = function(err?: any) {
|
|
@@ -335,9 +347,15 @@ class Database extends Events.EventEmitter {
|
|
|
335
347
|
next(err);
|
|
336
348
|
};
|
|
337
349
|
|
|
338
|
-
// options is read at call time, after the normalization below
|
|
339
|
-
|
|
340
|
-
|
|
350
|
+
// options is read at call time, after the normalization below;
|
|
351
|
+
// both values are query-invariant, so resolve them once on the
|
|
352
|
+
// first row instead of allocating per row
|
|
353
|
+
if (!keyResolutionDone) {
|
|
354
|
+
resolvedNest = resolveNestTables(options as any, self.connection.options);
|
|
355
|
+
resolvedTransform = resolveKeyTransform(options as any, self.connection.options);
|
|
356
|
+
keyResolutionDone = true;
|
|
357
|
+
}
|
|
358
|
+
fetchBlobSyncRow(row, meta, resolvedNest, self.connection._lowercase_keys, resolvedTransform, function(blobErr: any) {
|
|
341
359
|
if (blobErr) {
|
|
342
360
|
finish(blobErr);
|
|
343
361
|
return;
|
|
@@ -393,6 +411,18 @@ class Database extends Events.EventEmitter {
|
|
|
393
411
|
return makeQueryStream(this, query, params, options);
|
|
394
412
|
}
|
|
395
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Bulk-insert Writable (the COPY FROM analogue, Firebird 4.0+): write
|
|
416
|
+
* parameter-array rows, they are flushed in chunks through the batch
|
|
417
|
+
* API on one prepared statement. Runs its own transaction — committed
|
|
418
|
+
* on finish, rolled back on error/destroy (all-or-nothing for the
|
|
419
|
+
* whole stream). BLOB columns accept Buffers/strings. After 'finish',
|
|
420
|
+
* stream.recordCount / stream.affectedRows carry the totals.
|
|
421
|
+
*/
|
|
422
|
+
batchStream(query: string, options?: any) {
|
|
423
|
+
return makeBatchStream(this, query, options, true);
|
|
424
|
+
}
|
|
425
|
+
|
|
396
426
|
query(query: string, params?: QueryParams | Callback, callback?: any, options: InternalQueryOptions = {}): this {
|
|
397
427
|
if (params instanceof Function) {
|
|
398
428
|
options = callback || {};
|
|
@@ -552,8 +582,9 @@ class Database extends Events.EventEmitter {
|
|
|
552
582
|
/*
|
|
553
583
|
* Promise / async-await API.
|
|
554
584
|
* Each *Async method wraps its callback counterpart; the callback API
|
|
555
|
-
* stays untouched.
|
|
556
|
-
*
|
|
585
|
+
* stays untouched. The promises resolve with the rows alone unless
|
|
586
|
+
* { withMeta: true } is passed, which resolves the full
|
|
587
|
+
* { rows, fields, affectedRows, recordCounts, warnings } result.
|
|
557
588
|
*/
|
|
558
589
|
|
|
559
590
|
queryAsync(query: string, params?: QueryParams, options?: InternalQueryOptions): Promise<any[]> {
|
package/src/wire/serialize.ts
CHANGED
|
@@ -197,7 +197,11 @@ export class BlrReader {
|
|
|
197
197
|
value = this.buffer.readInt16LE(this.pos);
|
|
198
198
|
break;
|
|
199
199
|
case 4:
|
|
200
|
-
value = this.buffer.readInt32LE(this.pos)
|
|
200
|
+
value = this.buffer.readInt32LE(this.pos);
|
|
201
|
+
break;
|
|
202
|
+
case 8:
|
|
203
|
+
// e.g. record counts above 2^31 (isc_info_sql_records)
|
|
204
|
+
value = Number(this.buffer.readBigInt64LE(this.pos));
|
|
201
205
|
}
|
|
202
206
|
this.pos += len;
|
|
203
207
|
return value;
|
|
@@ -342,6 +346,17 @@ export class XdrWriter {
|
|
|
342
346
|
this.pos += alen;
|
|
343
347
|
}
|
|
344
348
|
|
|
349
|
+
/** addString for pre-encoded bytes (codepage connection charsets). */
|
|
350
|
+
addStringBuffer(b: Buffer): void {
|
|
351
|
+
var alen = align(b.length);
|
|
352
|
+
this.ensure(alen + 4);
|
|
353
|
+
this.buffer.writeInt32BE(b.length, this.pos);
|
|
354
|
+
this.pos += 4;
|
|
355
|
+
b.copy(this.buffer, this.pos);
|
|
356
|
+
this.buffer.fill(0, this.pos + b.length, this.pos + alen);
|
|
357
|
+
this.pos += alen;
|
|
358
|
+
}
|
|
359
|
+
|
|
345
360
|
addText(s: string, encoding: BufferEncoding): void {
|
|
346
361
|
var len = Buffer.byteLength(s, encoding);
|
|
347
362
|
var alen = align(len);
|
package/src/wire/socket.ts
CHANGED
|
@@ -163,9 +163,15 @@ class Socket {
|
|
|
163
163
|
* Compress and/or encrypt data before sending to socket.
|
|
164
164
|
*/
|
|
165
165
|
write(data: Buffer | Uint8Array, defer = false): void {
|
|
166
|
+
// Callers pass views of the connection's shared _msg buffer, and both
|
|
167
|
+
// net.Socket (when it cannot flush immediately) and zlib keep a
|
|
168
|
+
// REFERENCE to the chunk — a later sender rebuilding _msg would then
|
|
169
|
+
// corrupt this queued packet (intermittent, load-dependent). Own the
|
|
170
|
+
// bytes at the send boundary, once.
|
|
171
|
+
data = Buffer.from(data);
|
|
166
172
|
if (process.env.FIREBIRD_DEBUG) {
|
|
167
173
|
console.log('[fb-debug] socket.write: length=%d bytes=%s encrypt=%s defer=%s',
|
|
168
|
-
data.length,
|
|
174
|
+
data.length, (data as Buffer).toString('hex'), this.encrypt, defer);
|
|
169
175
|
}
|
|
170
176
|
if (defer) {
|
|
171
177
|
// Accumulate deferred packets instead of overwriting. Multiple
|
|
@@ -174,8 +180,8 @@ class Socket {
|
|
|
174
180
|
// overwriting the buffer silently drops packets and desynchronises
|
|
175
181
|
// the request/response queue, causing the connection to hang.
|
|
176
182
|
this.buffer = this.buffer
|
|
177
|
-
? Buffer.concat([this.buffer,
|
|
178
|
-
:
|
|
183
|
+
? Buffer.concat([this.buffer, data])
|
|
184
|
+
: (data as Buffer);
|
|
179
185
|
return;
|
|
180
186
|
}
|
|
181
187
|
|
package/src/wire/transaction.ts
CHANGED
|
@@ -2,12 +2,15 @@ import { doCallback, doError, fromCallback, type Callback, type SimpleCallback }
|
|
|
2
2
|
import { parseNamedPlaceholders } from '../named-params';
|
|
3
3
|
import { noop } from '../utils';
|
|
4
4
|
import Const from './const';
|
|
5
|
+
import { makeSqlTag, type SqlTag } from '../sql-template';
|
|
6
|
+
import { describeFields, parseRecordCounts } from './xsqlvar';
|
|
5
7
|
import makeQueryStream from './query-stream';
|
|
8
|
+
import makeBatchStream from './batch-stream';
|
|
6
9
|
import type Connection from './connection';
|
|
7
10
|
import type Database from './database';
|
|
8
11
|
import type Statement from './statement';
|
|
9
12
|
import type { BatchCb, StatementCb, InternalQueryOptions } from './wire-types';
|
|
10
|
-
import type { BatchOptions, BatchResult, QueryOptions, QueryParams, QueryStreamOptions, SequentialCallback } from '../types';
|
|
13
|
+
import type { BatchOptions, BatchResult, QueryOptions, QueryParams, QueryStreamOptions, RecordCounts, SequentialCallback } from '../types';
|
|
11
14
|
|
|
12
15
|
/***************************************
|
|
13
16
|
*
|
|
@@ -53,11 +56,80 @@ class Transaction {
|
|
|
53
56
|
// populated externally from the op_transaction response
|
|
54
57
|
handle!: number;
|
|
55
58
|
|
|
59
|
+
private _sql?: SqlTag;
|
|
60
|
+
|
|
56
61
|
constructor(connection: Connection) {
|
|
57
62
|
this.connection = connection;
|
|
58
63
|
this.db = connection.db;
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Tagged-template query API: tx.sql`SELECT ... ${value}` (see README).
|
|
68
|
+
* Built lazily — transactions are created per-query internally, and
|
|
69
|
+
* those throwaway instances must not pay for the tag. The compiled text
|
|
70
|
+
* is positional-only, so the namedPlaceholders rewriter is disabled:
|
|
71
|
+
* any `:token` in the template is PSQL (EXECUTE BLOCK), not a
|
|
72
|
+
* placeholder.
|
|
73
|
+
*/
|
|
74
|
+
get sql(): SqlTag {
|
|
75
|
+
return this._sql || (this._sql = makeSqlTag((text, params, options) =>
|
|
76
|
+
this.queryAsync(text, params, { ...options, namedPlaceholders: false })));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Current savepoint nesting depth (names savepoints, see savepoint()). */
|
|
80
|
+
private _savepointDepth = 0;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run `work` inside a savepoint (Firebird 1.5+): on resolve the
|
|
84
|
+
* savepoint is released, on reject the transaction rolls back TO the
|
|
85
|
+
* savepoint — undoing only work's changes — and the error is rethrown,
|
|
86
|
+
* leaving the transaction itself usable. Nestable (each call generates
|
|
87
|
+
* a fresh NF_SP_n name), mirroring db.withTransaction's style and
|
|
88
|
+
* Postgres.js's sql.savepoint().
|
|
89
|
+
*
|
|
90
|
+
* Do NOT run sibling savepoints concurrently on one transaction
|
|
91
|
+
* (Promise.all): Firebird's RELEASE SAVEPOINT also releases every
|
|
92
|
+
* savepoint created after it, so interleaved siblings release each
|
|
93
|
+
* other. Nested (awaited) savepoints are fine.
|
|
94
|
+
*/
|
|
95
|
+
async savepoint<T>(work: (transaction: this) => Promise<T> | T): Promise<T> {
|
|
96
|
+
if (typeof work !== 'function') {
|
|
97
|
+
throw new Error('savepoint(work) expects a function');
|
|
98
|
+
}
|
|
99
|
+
// named by nesting depth, not a global counter: sequential
|
|
100
|
+
// savepoints at the same depth reuse the same three SQL strings, so
|
|
101
|
+
// the statement cache serves them instead of accumulating
|
|
102
|
+
// single-use entries (redefining a released savepoint name is legal)
|
|
103
|
+
const name = 'NF_SP_' + (++this._savepointDepth);
|
|
104
|
+
try {
|
|
105
|
+
await this.queryAsync('SAVEPOINT ' + name);
|
|
106
|
+
|
|
107
|
+
let result: T;
|
|
108
|
+
try {
|
|
109
|
+
result = await work(this);
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
// only a work() failure rolls back to the savepoint — a
|
|
112
|
+
// RELEASE failure below must NOT undo work's successful
|
|
113
|
+
// changes
|
|
114
|
+
try {
|
|
115
|
+
await this.queryAsync('ROLLBACK TO SAVEPOINT ' + name);
|
|
116
|
+
} catch (rollbackErr: any) {
|
|
117
|
+
// the original failure matters more; keep the rollback
|
|
118
|
+
// failure attached for diagnosis
|
|
119
|
+
if (err && typeof err === 'object') {
|
|
120
|
+
err.savepointRollbackError = rollbackErr;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await this.queryAsync('RELEASE SAVEPOINT ' + name);
|
|
127
|
+
return result;
|
|
128
|
+
} finally {
|
|
129
|
+
this._savepointDepth--;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
61
133
|
/** Per-call options.namedPlaceholders overrides the connection option. */
|
|
62
134
|
private namedPlaceholdersEnabled(options?: InternalQueryOptions): boolean {
|
|
63
135
|
if (options && options.namedPlaceholders !== undefined)
|
|
@@ -138,6 +210,69 @@ class Transaction {
|
|
|
138
210
|
return;
|
|
139
211
|
}
|
|
140
212
|
|
|
213
|
+
// withMeta applies to query/execute only: in streaming mode
|
|
214
|
+
// (sequentially/queryStream, which spread user options) rows
|
|
215
|
+
// bypass fetchAll's array, so a result object here would
|
|
216
|
+
// carry rows: [] and a meaningless affectedRows
|
|
217
|
+
var withMeta = Boolean(options && typeof options === 'object' &&
|
|
218
|
+
(options as any).withMeta && !(options as any).asStream);
|
|
219
|
+
|
|
220
|
+
// Deliver the historic result shape, or — when options.withMeta
|
|
221
|
+
// is set — request the per-verb DML row counts while the
|
|
222
|
+
// statement handle is still open and wrap everything in a
|
|
223
|
+
// { rows, fields, affectedRows, recordCounts, warnings } object.
|
|
224
|
+
function deliver(rows: any, isSelect: boolean, plainDml?: boolean) {
|
|
225
|
+
if (!withMeta) {
|
|
226
|
+
statement!.release();
|
|
227
|
+
if (callback) {
|
|
228
|
+
if (plainDml) {
|
|
229
|
+
// plain DML historically calls back with no args
|
|
230
|
+
callback();
|
|
231
|
+
} else {
|
|
232
|
+
callback(undefined, rows, statement!.output, isSelect);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var execWarnings = (ret && ret.warnings) || [];
|
|
239
|
+
var finalize = function(counts?: RecordCounts) {
|
|
240
|
+
statement!.release();
|
|
241
|
+
if (!callback) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// DML: what the server actually changed; SELECT: rows
|
|
245
|
+
// returned (pg's rowCount convention)
|
|
246
|
+
var affectedRows = counts
|
|
247
|
+
? counts.insertCount + counts.updateCount + counts.deleteCount
|
|
248
|
+
: (Array.isArray(rows) ? rows.length : (rows !== undefined ? 1 : 0));
|
|
249
|
+
callback(undefined, {
|
|
250
|
+
rows: rows,
|
|
251
|
+
fields: describeFields(statement!.output),
|
|
252
|
+
affectedRows: affectedRows,
|
|
253
|
+
recordCounts: counts,
|
|
254
|
+
warnings: execWarnings,
|
|
255
|
+
}, statement!.output, isSelect);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
var t = statement!.type;
|
|
259
|
+
var isDml = t === Const.isc_info_sql_stmt_insert ||
|
|
260
|
+
t === Const.isc_info_sql_stmt_update ||
|
|
261
|
+
t === Const.isc_info_sql_stmt_delete ||
|
|
262
|
+
t === Const.isc_info_sql_stmt_exec_procedure;
|
|
263
|
+
if (!isDml) {
|
|
264
|
+
finalize();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
self.connection.statementInfo(statement!, Const.RECORDS_INFO, function(err: any, info: any) {
|
|
268
|
+
if (err) {
|
|
269
|
+
dropError(err);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
finalize(parseRecordCounts(info && info.buffer));
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
141
276
|
switch (statement.type) {
|
|
142
277
|
case Const.isc_info_sql_stmt_select:
|
|
143
278
|
statement.fetchAll(self, function(err: any, r: any) {
|
|
@@ -146,34 +281,39 @@ class Transaction {
|
|
|
146
281
|
return;
|
|
147
282
|
}
|
|
148
283
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (callback)
|
|
152
|
-
callback(undefined, r, statement.output, true);
|
|
153
|
-
|
|
284
|
+
deliver(r, true);
|
|
154
285
|
});
|
|
155
286
|
|
|
156
287
|
break;
|
|
157
288
|
|
|
158
289
|
case Const.isc_info_sql_stmt_exec_procedure:
|
|
159
290
|
if (ret && ret.data && ret.data.length > 0) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
291
|
+
// singleton op_execute2 rows never pass through
|
|
292
|
+
// fetchAll, so their blobAsText fetches must be
|
|
293
|
+
// resolved here (issue #305: EXECUTE PROCEDURE
|
|
294
|
+
// returned text blobs as unresolved functions)
|
|
295
|
+
self.connection.resolveTextBlobs(self, ret, function(blobErr?: any) {
|
|
296
|
+
if (blobErr) {
|
|
297
|
+
dropError(blobErr);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
deliver(ret.data[0], true);
|
|
301
|
+
});
|
|
165
302
|
break;
|
|
166
303
|
} else if (statement.output.length) {
|
|
167
|
-
statement.fetch(self, 1, function(err: any,
|
|
304
|
+
statement.fetch(self, 1, function(err: any, fret: any) {
|
|
168
305
|
if (err) {
|
|
169
306
|
dropError(err);
|
|
170
307
|
return;
|
|
171
308
|
}
|
|
172
309
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
310
|
+
self.connection.resolveTextBlobs(self, fret, function(blobErr?: any) {
|
|
311
|
+
if (blobErr) {
|
|
312
|
+
dropError(blobErr);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
deliver(fret.data[0], false);
|
|
316
|
+
});
|
|
177
317
|
});
|
|
178
318
|
|
|
179
319
|
break;
|
|
@@ -181,9 +321,7 @@ class Transaction {
|
|
|
181
321
|
|
|
182
322
|
// Fall through is normal
|
|
183
323
|
default:
|
|
184
|
-
|
|
185
|
-
if (callback)
|
|
186
|
-
callback()
|
|
324
|
+
deliver(undefined, false, true);
|
|
187
325
|
break;
|
|
188
326
|
}
|
|
189
327
|
|
|
@@ -265,6 +403,15 @@ class Transaction {
|
|
|
265
403
|
return makeQueryStream(this, query, params, options);
|
|
266
404
|
}
|
|
267
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Bulk-insert Writable running inside this transaction (see
|
|
408
|
+
* Database.batchStream). The transaction is NOT committed or rolled
|
|
409
|
+
* back by the stream — settle it yourself after 'finish'/'error'.
|
|
410
|
+
*/
|
|
411
|
+
batchStream(query: string, options?: any) {
|
|
412
|
+
return makeBatchStream(this, query, options, false);
|
|
413
|
+
}
|
|
414
|
+
|
|
268
415
|
query(query: string, params?: QueryParams | Callback, callback?: any, options: InternalQueryOptions = {}): void {
|
|
269
416
|
if (params instanceof Function) {
|
|
270
417
|
callback = params;
|