node-firebird 2.10.0 → 2.12.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 +236 -3
- 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 +136 -0
- package/lib/uri.js +53 -2
- package/lib/wire/connection.d.ts +7 -1
- package/lib/wire/connection.js +67 -46
- package/lib/wire/const.d.ts +5 -0
- package/lib/wire/const.js +14 -0
- package/lib/wire/database.d.ts +9 -0
- package/lib/wire/database.js +49 -14
- package/lib/wire/serialize.d.ts +3 -1
- package/lib/wire/serialize.js +4 -0
- package/lib/wire/transaction.d.ts +27 -0
- package/lib/wire/transaction.js +131 -13
- package/lib/wire/xsqlvar.d.ts +100 -0
- package/lib/wire/xsqlvar.js +219 -1
- package/package.json +1 -1
- package/src/pool.ts +57 -14
- package/src/sql-template.ts +196 -0
- package/src/types.ts +135 -1
- package/src/uri.ts +54 -2
- package/src/wire/connection.ts +77 -46
- package/src/wire/const.ts +15 -0
- package/src/wire/database.ts +52 -14
- package/src/wire/serialize.ts +8 -2
- package/src/wire/transaction.ts +140 -19
- package/src/wire/xsqlvar.ts +241 -0
package/lib/wire/const.js
CHANGED
|
@@ -540,6 +540,12 @@ const sqlInfo = {
|
|
|
540
540
|
isc_info_sql_stmt_type: 21,
|
|
541
541
|
isc_info_sql_get_plan: 22,
|
|
542
542
|
isc_info_sql_records: 23,
|
|
543
|
+
// per-verb row counts nested inside an isc_info_sql_records cluster
|
|
544
|
+
// (inf_pub.h isc_info_req_*)
|
|
545
|
+
isc_info_req_select_count: 13,
|
|
546
|
+
isc_info_req_insert_count: 14,
|
|
547
|
+
isc_info_req_update_count: 15,
|
|
548
|
+
isc_info_req_delete_count: 16,
|
|
543
549
|
isc_info_sql_batch_fetch: 24,
|
|
544
550
|
isc_info_sql_relation_alias: 25, // >: 2.0
|
|
545
551
|
isc_info_sql_explain_plan: 26, // >= 3.0
|
|
@@ -577,6 +583,7 @@ const DESCRIBE = [
|
|
|
577
583
|
sqlInfo.isc_info_sql_length,
|
|
578
584
|
sqlInfo.isc_info_sql_field,
|
|
579
585
|
sqlInfo.isc_info_sql_relation,
|
|
586
|
+
sqlInfo.isc_info_sql_relation_alias, // FB 2.0+: query alias of the source relation (nestTables)
|
|
580
587
|
//isc_info_sql_owner,
|
|
581
588
|
sqlInfo.isc_info_sql_alias,
|
|
582
589
|
sqlInfo.isc_info_sql_describe_end,
|
|
@@ -604,6 +611,7 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
604
611
|
sqlInfo.isc_info_sql_field,
|
|
605
612
|
sqlInfo.isc_info_sql_relation,
|
|
606
613
|
sqlInfo.isc_info_sql_relation_schema, // FB 6.0: schema of source relation
|
|
614
|
+
sqlInfo.isc_info_sql_relation_alias, // query alias of the source relation (nestTables)
|
|
607
615
|
//isc_info_sql_owner,
|
|
608
616
|
sqlInfo.isc_info_sql_alias,
|
|
609
617
|
sqlInfo.isc_info_sql_describe_end,
|
|
@@ -616,6 +624,11 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
616
624
|
sqlInfo.isc_info_sql_length,
|
|
617
625
|
sqlInfo.isc_info_sql_describe_end
|
|
618
626
|
];
|
|
627
|
+
// op_info_sql request for the per-verb DML row counts of an executed
|
|
628
|
+
// statement (withMeta / affectedRows).
|
|
629
|
+
const RECORDS_INFO = [
|
|
630
|
+
sqlInfo.isc_info_sql_records,
|
|
631
|
+
];
|
|
619
632
|
/***********************/
|
|
620
633
|
/* ISC Services */
|
|
621
634
|
/***********************/
|
|
@@ -841,6 +854,7 @@ const Const = Object.freeze({
|
|
|
841
854
|
...defaultOptions,
|
|
842
855
|
DESCRIBE,
|
|
843
856
|
DESCRIBE_WITH_SCHEMA,
|
|
857
|
+
RECORDS_INFO,
|
|
844
858
|
...dpb,
|
|
845
859
|
...dsql,
|
|
846
860
|
...fetchOp,
|
package/lib/wire/database.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Events from 'events';
|
|
2
2
|
import { type Callback, type SimpleCallback } from '../callback';
|
|
3
|
+
import { type SqlTag } from '../sql-template';
|
|
3
4
|
import FbEventManager from './fbEventManager';
|
|
4
5
|
import type Connection from './connection';
|
|
5
6
|
import type Transaction from './transaction';
|
|
@@ -15,7 +16,15 @@ type TransactionArg = TransactionOptions | Isolation | TransactionCb | undefined
|
|
|
15
16
|
declare class Database extends Events.EventEmitter {
|
|
16
17
|
connection: Connection;
|
|
17
18
|
eventid: number;
|
|
19
|
+
private _sql?;
|
|
18
20
|
constructor(connection: Connection);
|
|
21
|
+
/**
|
|
22
|
+
* Tagged-template query API: db.sql`SELECT ... ${value}` (see README).
|
|
23
|
+
* Built lazily on first access; the compiled text is positional-only,
|
|
24
|
+
* so the namedPlaceholders rewriter is disabled — any `:token` in the
|
|
25
|
+
* template is PSQL (EXECUTE BLOCK), not a placeholder.
|
|
26
|
+
*/
|
|
27
|
+
get sql(): SqlTag;
|
|
19
28
|
escape(value: any): string;
|
|
20
29
|
detach(callback?: Callback, force?: boolean): this;
|
|
21
30
|
transaction(options: TransactionArg, callback?: TransactionCb): this;
|
package/lib/wire/database.js
CHANGED
|
@@ -6,6 +6,8 @@ const events_1 = __importDefault(require("events"));
|
|
|
6
6
|
const callback_1 = require("../callback");
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const const_1 = __importDefault(require("./const"));
|
|
9
|
+
const sql_template_1 = require("../sql-template");
|
|
10
|
+
const xsqlvar_1 = require("./xsqlvar");
|
|
9
11
|
const eventConnection_1 = __importDefault(require("./eventConnection"));
|
|
10
12
|
const fbEventManager_1 = __importDefault(require("./fbEventManager"));
|
|
11
13
|
const query_stream_1 = __importDefault(require("./query-stream"));
|
|
@@ -75,30 +77,42 @@ function readblob(blob, callback) {
|
|
|
75
77
|
});
|
|
76
78
|
});
|
|
77
79
|
}
|
|
78
|
-
function fetchBlobSyncRow(row, meta, callback) {
|
|
79
|
-
if (!row || !meta || !meta.length) {
|
|
80
|
+
function fetchBlobSyncRow(row, meta, nestTables, lowercaseKeys, transform, callback) {
|
|
81
|
+
if (!row || !meta || !meta.length || !meta.some((m) => m && m.type === const_1.default.SQL_BLOB)) {
|
|
80
82
|
callback(null, row);
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
// locate blob cells by the same key computation the fetch decoder used,
|
|
86
|
+
// rather than assuming Object.keys(row) is index-aligned with meta —
|
|
87
|
+
// duplicate JOIN column names (and nested rows) break that alignment.
|
|
88
|
+
// Array rows (sequentially's legacy boolean form) are keyed by index.
|
|
89
|
+
const isArrayRow = Array.isArray(row);
|
|
90
|
+
const keys = isArrayRow ? null : (0, xsqlvar_1.computeColumnKeys)(meta, nestTables, lowercaseKeys, transform);
|
|
91
|
+
const blobCells = [];
|
|
85
92
|
for (let i = 0; i < meta.length; i++) {
|
|
86
|
-
if (meta[i]
|
|
87
|
-
|
|
93
|
+
if (!meta[i] || meta[i].type !== const_1.default.SQL_BLOB) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const target = keys ? (0, xsqlvar_1.nestCell)(row, keys[i].table) : row;
|
|
97
|
+
const key = keys ? keys[i].key : i;
|
|
98
|
+
// duplicate aliases collapse onto one cell — read it only once
|
|
99
|
+
if (typeof target[key] === 'function' &&
|
|
100
|
+
!blobCells.some((cell) => cell.target === target && cell.key === key)) {
|
|
101
|
+
blobCells.push({ target, key });
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
|
-
if (!
|
|
104
|
+
if (!blobCells.length) {
|
|
91
105
|
callback(null, row);
|
|
92
106
|
return;
|
|
93
107
|
}
|
|
94
|
-
let pending =
|
|
108
|
+
let pending = blobCells.length;
|
|
95
109
|
let blobErr;
|
|
96
|
-
|
|
97
|
-
readblob(
|
|
110
|
+
blobCells.forEach(function (cell) {
|
|
111
|
+
readblob(cell.target[cell.key], function (err, data) {
|
|
98
112
|
if (err && !blobErr) {
|
|
99
113
|
blobErr = err;
|
|
100
114
|
}
|
|
101
|
-
|
|
115
|
+
cell.target[cell.key] = data;
|
|
102
116
|
pending--;
|
|
103
117
|
if (pending === 0) {
|
|
104
118
|
callback(blobErr, row);
|
|
@@ -113,6 +127,15 @@ class Database extends events_1.default.EventEmitter {
|
|
|
113
127
|
connection.db = this;
|
|
114
128
|
this.eventid = 1;
|
|
115
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Tagged-template query API: db.sql`SELECT ... ${value}` (see README).
|
|
132
|
+
* Built lazily on first access; the compiled text is positional-only,
|
|
133
|
+
* so the namedPlaceholders rewriter is disabled — any `:token` in the
|
|
134
|
+
* template is PSQL (EXECUTE BLOCK), not a placeholder.
|
|
135
|
+
*/
|
|
136
|
+
get sql() {
|
|
137
|
+
return this._sql || (this._sql = (0, sql_template_1.makeSqlTag)((text, params, options) => this.queryAsync(text, params, { ...options, namedPlaceholders: false })));
|
|
138
|
+
}
|
|
116
139
|
escape(value) {
|
|
117
140
|
return (0, utils_1.escape)(value, this.connection.accept.protocolVersion);
|
|
118
141
|
}
|
|
@@ -250,6 +273,9 @@ class Database extends events_1.default.EventEmitter {
|
|
|
250
273
|
callback = undefined;
|
|
251
274
|
}
|
|
252
275
|
var self = this;
|
|
276
|
+
var keyResolutionDone = false;
|
|
277
|
+
var resolvedNest;
|
|
278
|
+
var resolvedTransform;
|
|
253
279
|
var _on = function (row, i, meta, next) {
|
|
254
280
|
var done = false;
|
|
255
281
|
var finish = function (err) {
|
|
@@ -259,7 +285,15 @@ class Database extends events_1.default.EventEmitter {
|
|
|
259
285
|
done = true;
|
|
260
286
|
next(err);
|
|
261
287
|
};
|
|
262
|
-
|
|
288
|
+
// options is read at call time, after the normalization below;
|
|
289
|
+
// both values are query-invariant, so resolve them once on the
|
|
290
|
+
// first row instead of allocating per row
|
|
291
|
+
if (!keyResolutionDone) {
|
|
292
|
+
resolvedNest = (0, xsqlvar_1.resolveNestTables)(options, self.connection.options);
|
|
293
|
+
resolvedTransform = (0, xsqlvar_1.resolveKeyTransform)(options, self.connection.options);
|
|
294
|
+
keyResolutionDone = true;
|
|
295
|
+
}
|
|
296
|
+
fetchBlobSyncRow(row, meta, resolvedNest, self.connection._lowercase_keys, resolvedTransform, function (blobErr) {
|
|
263
297
|
if (blobErr) {
|
|
264
298
|
finish(blobErr);
|
|
265
299
|
return;
|
|
@@ -453,8 +487,9 @@ class Database extends events_1.default.EventEmitter {
|
|
|
453
487
|
/*
|
|
454
488
|
* Promise / async-await API.
|
|
455
489
|
* Each *Async method wraps its callback counterpart; the callback API
|
|
456
|
-
* stays untouched.
|
|
457
|
-
*
|
|
490
|
+
* stays untouched. The promises resolve with the rows alone unless
|
|
491
|
+
* { withMeta: true } is passed, which resolves the full
|
|
492
|
+
* { rows, fields, affectedRows, recordCounts, warnings } result.
|
|
458
493
|
*/
|
|
459
494
|
queryAsync(query, params, options) {
|
|
460
495
|
var self = this;
|
package/lib/wire/serialize.d.ts
CHANGED
|
@@ -83,8 +83,10 @@ export declare class XdrReader {
|
|
|
83
83
|
frow?: any;
|
|
84
84
|
/** rows decoded so far in this call */
|
|
85
85
|
frows?: any[];
|
|
86
|
-
/** cached object-row keys (column aliases) */
|
|
86
|
+
/** cached object-row keys (column aliases, qualified when nestTables is set) */
|
|
87
87
|
fcols?: string[];
|
|
88
|
+
/** cached per-column table keys when nestTables === true */
|
|
89
|
+
ftables?: string[];
|
|
88
90
|
constructor(buffer: Buffer);
|
|
89
91
|
readInt(): number;
|
|
90
92
|
readUInt(): number;
|
package/lib/wire/serialize.js
CHANGED
|
@@ -160,6 +160,10 @@ class BlrReader {
|
|
|
160
160
|
break;
|
|
161
161
|
case 4:
|
|
162
162
|
value = this.buffer.readInt32LE(this.pos);
|
|
163
|
+
break;
|
|
164
|
+
case 8:
|
|
165
|
+
// e.g. record counts above 2^31 (isc_info_sql_records)
|
|
166
|
+
value = Number(this.buffer.readBigInt64LE(this.pos));
|
|
163
167
|
}
|
|
164
168
|
this.pos += len;
|
|
165
169
|
return value;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Callback, type SimpleCallback } from '../callback';
|
|
2
|
+
import { type SqlTag } from '../sql-template';
|
|
2
3
|
import type Connection from './connection';
|
|
3
4
|
import type Database from './database';
|
|
4
5
|
import type Statement from './statement';
|
|
@@ -8,7 +9,33 @@ declare class Transaction {
|
|
|
8
9
|
connection: Connection;
|
|
9
10
|
db: Database;
|
|
10
11
|
handle: number;
|
|
12
|
+
private _sql?;
|
|
11
13
|
constructor(connection: Connection);
|
|
14
|
+
/**
|
|
15
|
+
* Tagged-template query API: tx.sql`SELECT ... ${value}` (see README).
|
|
16
|
+
* Built lazily — transactions are created per-query internally, and
|
|
17
|
+
* those throwaway instances must not pay for the tag. The compiled text
|
|
18
|
+
* is positional-only, so the namedPlaceholders rewriter is disabled:
|
|
19
|
+
* any `:token` in the template is PSQL (EXECUTE BLOCK), not a
|
|
20
|
+
* placeholder.
|
|
21
|
+
*/
|
|
22
|
+
get sql(): SqlTag;
|
|
23
|
+
/** Current savepoint nesting depth (names savepoints, see savepoint()). */
|
|
24
|
+
private _savepointDepth;
|
|
25
|
+
/**
|
|
26
|
+
* Run `work` inside a savepoint (Firebird 1.5+): on resolve the
|
|
27
|
+
* savepoint is released, on reject the transaction rolls back TO the
|
|
28
|
+
* savepoint — undoing only work's changes — and the error is rethrown,
|
|
29
|
+
* leaving the transaction itself usable. Nestable (each call generates
|
|
30
|
+
* a fresh NF_SP_n name), mirroring db.withTransaction's style and
|
|
31
|
+
* Postgres.js's sql.savepoint().
|
|
32
|
+
*
|
|
33
|
+
* Do NOT run sibling savepoints concurrently on one transaction
|
|
34
|
+
* (Promise.all): Firebird's RELEASE SAVEPOINT also releases every
|
|
35
|
+
* savepoint created after it, so interleaved siblings release each
|
|
36
|
+
* other. Nested (awaited) savepoints are fine.
|
|
37
|
+
*/
|
|
38
|
+
savepoint<T>(work: (transaction: this) => Promise<T> | T): Promise<T>;
|
|
12
39
|
/** Per-call options.namedPlaceholders overrides the connection option. */
|
|
13
40
|
private namedPlaceholdersEnabled;
|
|
14
41
|
newStatement(query: string, callback: StatementCb, options?: InternalQueryOptions): void;
|
package/lib/wire/transaction.js
CHANGED
|
@@ -6,6 +6,8 @@ const callback_1 = require("../callback");
|
|
|
6
6
|
const named_params_1 = require("../named-params");
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const const_1 = __importDefault(require("./const"));
|
|
9
|
+
const sql_template_1 = require("../sql-template");
|
|
10
|
+
const xsqlvar_1 = require("./xsqlvar");
|
|
9
11
|
const query_stream_1 = __importDefault(require("./query-stream"));
|
|
10
12
|
/***************************************
|
|
11
13
|
*
|
|
@@ -44,9 +46,73 @@ function hookAbortSignal(connection, signal, callback) {
|
|
|
44
46
|
}
|
|
45
47
|
class Transaction {
|
|
46
48
|
constructor(connection) {
|
|
49
|
+
/** Current savepoint nesting depth (names savepoints, see savepoint()). */
|
|
50
|
+
this._savepointDepth = 0;
|
|
47
51
|
this.connection = connection;
|
|
48
52
|
this.db = connection.db;
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Tagged-template query API: tx.sql`SELECT ... ${value}` (see README).
|
|
56
|
+
* Built lazily — transactions are created per-query internally, and
|
|
57
|
+
* those throwaway instances must not pay for the tag. The compiled text
|
|
58
|
+
* is positional-only, so the namedPlaceholders rewriter is disabled:
|
|
59
|
+
* any `:token` in the template is PSQL (EXECUTE BLOCK), not a
|
|
60
|
+
* placeholder.
|
|
61
|
+
*/
|
|
62
|
+
get sql() {
|
|
63
|
+
return this._sql || (this._sql = (0, sql_template_1.makeSqlTag)((text, params, options) => this.queryAsync(text, params, { ...options, namedPlaceholders: false })));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run `work` inside a savepoint (Firebird 1.5+): on resolve the
|
|
67
|
+
* savepoint is released, on reject the transaction rolls back TO the
|
|
68
|
+
* savepoint — undoing only work's changes — and the error is rethrown,
|
|
69
|
+
* leaving the transaction itself usable. Nestable (each call generates
|
|
70
|
+
* a fresh NF_SP_n name), mirroring db.withTransaction's style and
|
|
71
|
+
* Postgres.js's sql.savepoint().
|
|
72
|
+
*
|
|
73
|
+
* Do NOT run sibling savepoints concurrently on one transaction
|
|
74
|
+
* (Promise.all): Firebird's RELEASE SAVEPOINT also releases every
|
|
75
|
+
* savepoint created after it, so interleaved siblings release each
|
|
76
|
+
* other. Nested (awaited) savepoints are fine.
|
|
77
|
+
*/
|
|
78
|
+
async savepoint(work) {
|
|
79
|
+
if (typeof work !== 'function') {
|
|
80
|
+
throw new Error('savepoint(work) expects a function');
|
|
81
|
+
}
|
|
82
|
+
// named by nesting depth, not a global counter: sequential
|
|
83
|
+
// savepoints at the same depth reuse the same three SQL strings, so
|
|
84
|
+
// the statement cache serves them instead of accumulating
|
|
85
|
+
// single-use entries (redefining a released savepoint name is legal)
|
|
86
|
+
const name = 'NF_SP_' + (++this._savepointDepth);
|
|
87
|
+
try {
|
|
88
|
+
await this.queryAsync('SAVEPOINT ' + name);
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = await work(this);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// only a work() failure rolls back to the savepoint — a
|
|
95
|
+
// RELEASE failure below must NOT undo work's successful
|
|
96
|
+
// changes
|
|
97
|
+
try {
|
|
98
|
+
await this.queryAsync('ROLLBACK TO SAVEPOINT ' + name);
|
|
99
|
+
}
|
|
100
|
+
catch (rollbackErr) {
|
|
101
|
+
// the original failure matters more; keep the rollback
|
|
102
|
+
// failure attached for diagnosis
|
|
103
|
+
if (err && typeof err === 'object') {
|
|
104
|
+
err.savepointRollbackError = rollbackErr;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
await this.queryAsync('RELEASE SAVEPOINT ' + name);
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
this._savepointDepth--;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
50
116
|
/** Per-call options.namedPlaceholders overrides the connection option. */
|
|
51
117
|
namedPlaceholdersEnabled(options) {
|
|
52
118
|
if (options && options.namedPlaceholders !== undefined)
|
|
@@ -116,6 +182,66 @@ class Transaction {
|
|
|
116
182
|
dropError(err);
|
|
117
183
|
return;
|
|
118
184
|
}
|
|
185
|
+
// withMeta applies to query/execute only: in streaming mode
|
|
186
|
+
// (sequentially/queryStream, which spread user options) rows
|
|
187
|
+
// bypass fetchAll's array, so a result object here would
|
|
188
|
+
// carry rows: [] and a meaningless affectedRows
|
|
189
|
+
var withMeta = Boolean(options && typeof options === 'object' &&
|
|
190
|
+
options.withMeta && !options.asStream);
|
|
191
|
+
// Deliver the historic result shape, or — when options.withMeta
|
|
192
|
+
// is set — request the per-verb DML row counts while the
|
|
193
|
+
// statement handle is still open and wrap everything in a
|
|
194
|
+
// { rows, fields, affectedRows, recordCounts, warnings } object.
|
|
195
|
+
function deliver(rows, isSelect, plainDml) {
|
|
196
|
+
if (!withMeta) {
|
|
197
|
+
statement.release();
|
|
198
|
+
if (callback) {
|
|
199
|
+
if (plainDml) {
|
|
200
|
+
// plain DML historically calls back with no args
|
|
201
|
+
callback();
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
callback(undefined, rows, statement.output, isSelect);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
var execWarnings = (ret && ret.warnings) || [];
|
|
210
|
+
var finalize = function (counts) {
|
|
211
|
+
statement.release();
|
|
212
|
+
if (!callback) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// DML: what the server actually changed; SELECT: rows
|
|
216
|
+
// returned (pg's rowCount convention)
|
|
217
|
+
var affectedRows = counts
|
|
218
|
+
? counts.insertCount + counts.updateCount + counts.deleteCount
|
|
219
|
+
: (Array.isArray(rows) ? rows.length : (rows !== undefined ? 1 : 0));
|
|
220
|
+
callback(undefined, {
|
|
221
|
+
rows: rows,
|
|
222
|
+
fields: (0, xsqlvar_1.describeFields)(statement.output),
|
|
223
|
+
affectedRows: affectedRows,
|
|
224
|
+
recordCounts: counts,
|
|
225
|
+
warnings: execWarnings,
|
|
226
|
+
}, statement.output, isSelect);
|
|
227
|
+
};
|
|
228
|
+
var t = statement.type;
|
|
229
|
+
var isDml = t === const_1.default.isc_info_sql_stmt_insert ||
|
|
230
|
+
t === const_1.default.isc_info_sql_stmt_update ||
|
|
231
|
+
t === const_1.default.isc_info_sql_stmt_delete ||
|
|
232
|
+
t === const_1.default.isc_info_sql_stmt_exec_procedure;
|
|
233
|
+
if (!isDml) {
|
|
234
|
+
finalize();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
self.connection.statementInfo(statement, const_1.default.RECORDS_INFO, function (err, info) {
|
|
238
|
+
if (err) {
|
|
239
|
+
dropError(err);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
finalize((0, xsqlvar_1.parseRecordCounts)(info && info.buffer));
|
|
243
|
+
});
|
|
244
|
+
}
|
|
119
245
|
switch (statement.type) {
|
|
120
246
|
case const_1.default.isc_info_sql_stmt_select:
|
|
121
247
|
statement.fetchAll(self, function (err, r) {
|
|
@@ -123,35 +249,27 @@ class Transaction {
|
|
|
123
249
|
dropError(err);
|
|
124
250
|
return;
|
|
125
251
|
}
|
|
126
|
-
|
|
127
|
-
if (callback)
|
|
128
|
-
callback(undefined, r, statement.output, true);
|
|
252
|
+
deliver(r, true);
|
|
129
253
|
});
|
|
130
254
|
break;
|
|
131
255
|
case const_1.default.isc_info_sql_stmt_exec_procedure:
|
|
132
256
|
if (ret && ret.data && ret.data.length > 0) {
|
|
133
|
-
|
|
134
|
-
if (callback)
|
|
135
|
-
callback(undefined, ret.data[0], statement.output, true);
|
|
257
|
+
deliver(ret.data[0], true);
|
|
136
258
|
break;
|
|
137
259
|
}
|
|
138
260
|
else if (statement.output.length) {
|
|
139
|
-
statement.fetch(self, 1, function (err,
|
|
261
|
+
statement.fetch(self, 1, function (err, fret) {
|
|
140
262
|
if (err) {
|
|
141
263
|
dropError(err);
|
|
142
264
|
return;
|
|
143
265
|
}
|
|
144
|
-
|
|
145
|
-
if (callback)
|
|
146
|
-
callback(undefined, ret.data[0], statement.output, false);
|
|
266
|
+
deliver(fret.data[0], false);
|
|
147
267
|
});
|
|
148
268
|
break;
|
|
149
269
|
}
|
|
150
270
|
// Fall through is normal
|
|
151
271
|
default:
|
|
152
|
-
|
|
153
|
-
if (callback)
|
|
154
|
-
callback();
|
|
272
|
+
deliver(undefined, false, true);
|
|
155
273
|
break;
|
|
156
274
|
}
|
|
157
275
|
}, options);
|
package/lib/wire/xsqlvar.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { XdrReader, XdrWriter, BlrWriter } from './serialize';
|
|
2
|
+
import type { RecordCounts } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Common shape of all SQLVar descriptor objects. The metadata properties
|
|
4
5
|
* are populated externally (in connection.ts) from the op_prepare_statement
|
|
@@ -21,6 +22,105 @@ export declare abstract class SQLVarBase {
|
|
|
21
22
|
abstract decode(data: XdrReader, lowerV13: boolean, options?: any): any;
|
|
22
23
|
abstract calcBlr(blr: BlrWriter): void;
|
|
23
24
|
}
|
|
25
|
+
/** Effective object-row key(s) of one output column (see computeColumnKeys). */
|
|
26
|
+
export interface ColumnKey {
|
|
27
|
+
/** Top-level table key when nestTables === true; undefined otherwise. */
|
|
28
|
+
table?: string;
|
|
29
|
+
/** Property key: the column alias, or 'table<sep>alias' in separator mode. */
|
|
30
|
+
key: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compute the object-row property keys for a statement's output columns,
|
|
34
|
+
* honouring the nestTables and lowercase_keys options. The table qualifier
|
|
35
|
+
* is the query's relation alias when one is used (relationAlias, requested
|
|
36
|
+
* via isc_info_sql_relation_alias), the relation name otherwise, so
|
|
37
|
+
* self-joins nest under their query aliases. Expression columns (no source
|
|
38
|
+
* relation) qualify as '' exactly like mysql2: they nest under the '' key,
|
|
39
|
+
* and in separator mode become '<sep>alias' — always prefixing keeps
|
|
40
|
+
* qualified keys collision-free (a bare expression alias could otherwise
|
|
41
|
+
* collide with a real column's 'table<sep>column' key). Used by the fetch
|
|
42
|
+
* decoder and by fetchBlobSyncRow, which must agree on where each column
|
|
43
|
+
* landed in the row.
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeColumnKeys(output: SQLVarBase[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined, transform?: (key: string) => string): ColumnKey[];
|
|
46
|
+
/** transformKeys option value: the built-in 'camel', or a custom mapper. */
|
|
47
|
+
export type KeyTransform = 'camel' | ((key: string) => string);
|
|
48
|
+
/** FIRST_NAME → firstName (the transformKeys: 'camel' built-in). */
|
|
49
|
+
export declare function camelizeKey(key: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the effective transformKeys value (per-query wins over the
|
|
52
|
+
* connection option) into a callable mapper, or undefined when off.
|
|
53
|
+
* A custom mapper is guarded like the typeCast hook: a throw inside the
|
|
54
|
+
* row-decode loop would be mistaken for an incomplete packet and desync
|
|
55
|
+
* the response queue, so failures fall back to the untransformed key.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveKeyTransform(queryOptions: {
|
|
58
|
+
transformKeys?: KeyTransform;
|
|
59
|
+
} | undefined, connectionOptions: {
|
|
60
|
+
transformKeys?: KeyTransform;
|
|
61
|
+
} | undefined): ((key: string) => string) | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the effective nestTables value: the per-query option wins over
|
|
64
|
+
* the connection option. The decoder and fetchBlobSyncRow both use this —
|
|
65
|
+
* they must agree on whether nesting is active or blob cells are looked
|
|
66
|
+
* up in the wrong place.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveNestTables(queryOptions: {
|
|
69
|
+
nestTables?: boolean | string;
|
|
70
|
+
} | undefined, connectionOptions: {
|
|
71
|
+
nestTables?: boolean | string;
|
|
72
|
+
} | undefined): boolean | string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* The object a column's value lives in: the row itself, or — when the
|
|
75
|
+
* column carries a nestTables table qualifier — the row's per-table
|
|
76
|
+
* sub-object, created on first use. Every site that reads or writes a
|
|
77
|
+
* cell by ColumnKey must resolve it through here.
|
|
78
|
+
*/
|
|
79
|
+
export declare function nestCell(row: any, table: string | undefined): any;
|
|
80
|
+
/** Human-readable names for the SQL_* wire type codes. */
|
|
81
|
+
export declare const SQL_TYPE_NAMES: Record<number, string>;
|
|
82
|
+
/**
|
|
83
|
+
* Public column-metadata shape for one output descriptor: the vocabulary
|
|
84
|
+
* both the typeCast hook and withMeta `fields` deliver. Keep the two in
|
|
85
|
+
* lockstep by building both through here.
|
|
86
|
+
*/
|
|
87
|
+
export declare function describeField(meta: Partial<SQLVarBase>): {
|
|
88
|
+
type: number;
|
|
89
|
+
typeName: string;
|
|
90
|
+
subType: number | undefined;
|
|
91
|
+
scale: number | undefined;
|
|
92
|
+
length: number | undefined;
|
|
93
|
+
nullable: boolean | undefined;
|
|
94
|
+
field: string | undefined;
|
|
95
|
+
relation: string | undefined;
|
|
96
|
+
relationAlias: string | undefined;
|
|
97
|
+
relationSchema: string | undefined;
|
|
98
|
+
alias: string | undefined;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Map a statement's output descriptors to the column-metadata array
|
|
102
|
+
* delivered in withMeta results ({ rows, fields, ... }).
|
|
103
|
+
*/
|
|
104
|
+
export declare function describeFields(output: SQLVarBase[]): {
|
|
105
|
+
type: number;
|
|
106
|
+
typeName: string;
|
|
107
|
+
subType: number | undefined;
|
|
108
|
+
scale: number | undefined;
|
|
109
|
+
length: number | undefined;
|
|
110
|
+
nullable: boolean | undefined;
|
|
111
|
+
field: string | undefined;
|
|
112
|
+
relation: string | undefined;
|
|
113
|
+
relationAlias: string | undefined;
|
|
114
|
+
relationSchema: string | undefined;
|
|
115
|
+
alias: string | undefined;
|
|
116
|
+
}[];
|
|
117
|
+
/**
|
|
118
|
+
* Parse the op_info_sql response buffer of a Const.RECORDS_INFO request
|
|
119
|
+
* into per-verb row counts. The buffer holds an isc_info_sql_records
|
|
120
|
+
* cluster (2-byte total length, then nested isc_info_req_*_count items,
|
|
121
|
+
* each 2-byte length + little-endian integer) terminated by isc_info_end.
|
|
122
|
+
*/
|
|
123
|
+
export declare function parseRecordCounts(buffer: Buffer | undefined): RecordCounts;
|
|
24
124
|
export declare class SQLVarText extends SQLVarBase {
|
|
25
125
|
decode(data: XdrReader, lowerV13: boolean, options?: any): string | Buffer<ArrayBufferLike> | null | undefined;
|
|
26
126
|
calcBlr(blr: BlrWriter): void;
|