node-firebird 2.11.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 +191 -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 +114 -0
- package/lib/uri.js +53 -2
- package/lib/wire/connection.d.ts +6 -0
- package/lib/wire/connection.js +50 -36
- package/lib/wire/const.d.ts +5 -0
- package/lib/wire/const.js +12 -0
- package/lib/wire/database.d.ts +9 -0
- package/lib/wire/database.js +27 -7
- 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 +62 -1
- package/lib/wire/xsqlvar.js +165 -6
- package/package.json +1 -1
- package/src/pool.ts +57 -14
- package/src/sql-template.ts +196 -0
- package/src/types.ts +113 -1
- package/src/uri.ts +54 -2
- package/src/wire/connection.ts +59 -37
- package/src/wire/const.ts +13 -0
- package/src/wire/database.ts +31 -8
- package/src/wire/serialize.ts +5 -1
- package/src/wire/transaction.ts +140 -19
- package/src/wire/xsqlvar.ts +170 -5
package/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Readable } from 'stream';
|
|
2
|
+
import type { SqlTag } from './sql-template';
|
|
3
|
+
export type { SqlTag, SqlQuery, SqlIdentifier, CompiledQuery } from './sql-template';
|
|
2
4
|
export type DatabaseCallback = (err: any, db: Database) => void;
|
|
3
5
|
export type TransactionCallback = (err: any, transaction: Transaction) => void;
|
|
4
6
|
export type QueryCallback = (err: any, result: any[]) => void;
|
|
@@ -127,7 +129,70 @@ export type QueryOptions = {
|
|
|
127
129
|
* unaffected.
|
|
128
130
|
*/
|
|
129
131
|
nestTables?: boolean | string;
|
|
132
|
+
/**
|
|
133
|
+
* Per-query override of the `transformKeys` connection option: rewrite
|
|
134
|
+
* object-row keys — `'camel'` turns `FIRST_NAME` into `firstName`, or
|
|
135
|
+
* pass a custom `(key) => key` mapper. Applied after `lowercase_keys`
|
|
136
|
+
* and to both parts of `nestTables` keys. Column metadata (`fields`,
|
|
137
|
+
* typeCast) keeps the raw server aliases.
|
|
138
|
+
*/
|
|
139
|
+
transformKeys?: 'camel' | ((key: string) => string);
|
|
140
|
+
/**
|
|
141
|
+
* Deliver a full result object `{ rows, fields, affectedRows,
|
|
142
|
+
* recordCounts, warnings }` instead of the bare rows (callback and
|
|
143
|
+
* promise APIs). For DML, `affectedRows` is what the server actually
|
|
144
|
+
* changed (`isc_info_sql_records`, one extra lightweight info request
|
|
145
|
+
* per statement — hence opt-in) and `recordCounts` breaks it down per
|
|
146
|
+
* verb; for SELECT it is the number of rows returned (pg's `rowCount`
|
|
147
|
+
* convention) with no extra round-trip. `warnings` carries any
|
|
148
|
+
* `isc_arg_warning` entries from the execute response. Honoured by
|
|
149
|
+
* query/execute and their *Async wrappers only — ignored by the
|
|
150
|
+
* streaming APIs (sequentially/queryStream, where rows bypass the
|
|
151
|
+
* result) and executeBatch (which has its own completion shape).
|
|
152
|
+
*/
|
|
153
|
+
withMeta?: boolean;
|
|
130
154
|
};
|
|
155
|
+
/** Column metadata delivered in withMeta results (`fields`) — the same
|
|
156
|
+
* vocabulary the typeCast hook receives, plus nullable and the relation
|
|
157
|
+
* alias/schema. */
|
|
158
|
+
export interface FieldMetadata {
|
|
159
|
+
type: number;
|
|
160
|
+
typeName: string;
|
|
161
|
+
subType?: number;
|
|
162
|
+
scale?: number;
|
|
163
|
+
length?: number;
|
|
164
|
+
nullable?: boolean;
|
|
165
|
+
field?: string;
|
|
166
|
+
relation?: string;
|
|
167
|
+
relationAlias?: string;
|
|
168
|
+
relationSchema?: string;
|
|
169
|
+
alias?: string;
|
|
170
|
+
}
|
|
171
|
+
/** Per-verb server row counts of an executed DML statement. */
|
|
172
|
+
export interface RecordCounts {
|
|
173
|
+
selectCount: number;
|
|
174
|
+
insertCount: number;
|
|
175
|
+
updateCount: number;
|
|
176
|
+
deleteCount: number;
|
|
177
|
+
}
|
|
178
|
+
/** An isc_arg_warning entry from a server response ('warning' driver event
|
|
179
|
+
* and withMeta `warnings`). */
|
|
180
|
+
export interface ServerWarning {
|
|
181
|
+
gdscode: number;
|
|
182
|
+
params?: (string | number)[];
|
|
183
|
+
message: string;
|
|
184
|
+
}
|
|
185
|
+
/** Full result shape delivered when `withMeta: true` is set. */
|
|
186
|
+
export interface QueryResult<T = any> {
|
|
187
|
+
/** Rows array (SELECT), single row object (RETURNING / procedures), or undefined (plain DML). */
|
|
188
|
+
rows: T[] | T | undefined;
|
|
189
|
+
fields: FieldMetadata[];
|
|
190
|
+
/** DML: rows the server changed; SELECT: rows returned. */
|
|
191
|
+
affectedRows: number;
|
|
192
|
+
/** Set for DML statements only. */
|
|
193
|
+
recordCounts?: RecordCounts;
|
|
194
|
+
warnings: ServerWarning[];
|
|
195
|
+
}
|
|
131
196
|
export type QueryStreamOptions = QueryOptions & {
|
|
132
197
|
/**
|
|
133
198
|
* Rows buffered internally before fetching pauses (object-mode
|
|
@@ -138,6 +203,13 @@ export type QueryStreamOptions = QueryOptions & {
|
|
|
138
203
|
asObject?: boolean;
|
|
139
204
|
};
|
|
140
205
|
export interface Database {
|
|
206
|
+
/**
|
|
207
|
+
* Tagged-template query API (Postgres.js-style): interpolated values
|
|
208
|
+
* become positional parameters, `sql('NAME')` quotes an identifier,
|
|
209
|
+
* embedded `sql` fragments compose, arrays expand to `?, ?, ?` lists.
|
|
210
|
+
* The returned query is a lazy thenable — it executes once, on await.
|
|
211
|
+
*/
|
|
212
|
+
sql: SqlTag;
|
|
141
213
|
detach(callback?: SimpleCallback): Database;
|
|
142
214
|
transaction(options: TransactionOptions | Isolation | TransactionCallback, callback?: TransactionCallback): Database;
|
|
143
215
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
|
|
@@ -160,7 +232,13 @@ export interface Database {
|
|
|
160
232
|
alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
|
|
161
233
|
dropTablespace(name: string, callback?: QueryCallback): Database;
|
|
162
234
|
createSchema(schemaName: string, tablespaceName?: string | QueryCallback, callback?: QueryCallback): Database;
|
|
235
|
+
queryAsync<T = any>(query: string, params: QueryParams | undefined, options: QueryOptions & {
|
|
236
|
+
withMeta: true;
|
|
237
|
+
}): Promise<QueryResult<T>>;
|
|
163
238
|
queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
239
|
+
executeAsync<T = any>(query: string, params: QueryParams | undefined, options: QueryOptions & {
|
|
240
|
+
withMeta: true;
|
|
241
|
+
}): Promise<QueryResult<T>>;
|
|
164
242
|
executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
165
243
|
executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
|
|
166
244
|
sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
@@ -183,6 +261,14 @@ export interface Database {
|
|
|
183
261
|
cancelAsync(kind?: number): Promise<void>;
|
|
184
262
|
}
|
|
185
263
|
export interface Transaction {
|
|
264
|
+
/** Tagged-template query API running inside this transaction (see Database.sql). */
|
|
265
|
+
sql: SqlTag;
|
|
266
|
+
/**
|
|
267
|
+
* Run `work` inside a savepoint: released on resolve, rolled back TO
|
|
268
|
+
* (undoing only work's changes) on reject — the transaction stays
|
|
269
|
+
* usable either way. Nestable.
|
|
270
|
+
*/
|
|
271
|
+
savepoint<T>(work: (transaction: Transaction) => Promise<T> | T): Promise<T>;
|
|
186
272
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
|
|
187
273
|
query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
|
|
188
274
|
execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
|
|
@@ -199,7 +285,13 @@ export interface Transaction {
|
|
|
199
285
|
commitRetaining(callback?: SimpleCallback): void;
|
|
200
286
|
rollback(callback?: SimpleCallback): void;
|
|
201
287
|
rollbackRetaining(callback?: SimpleCallback): void;
|
|
288
|
+
queryAsync<T = any>(query: string, params: QueryParams | undefined, options: QueryOptions & {
|
|
289
|
+
withMeta: true;
|
|
290
|
+
}): Promise<QueryResult<T>>;
|
|
202
291
|
queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
292
|
+
executeAsync<T = any>(query: string, params: QueryParams | undefined, options: QueryOptions & {
|
|
293
|
+
withMeta: true;
|
|
294
|
+
}): Promise<QueryResult<T>>;
|
|
203
295
|
executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
204
296
|
executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
|
|
205
297
|
sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
@@ -276,6 +368,14 @@ export interface Options {
|
|
|
276
368
|
* array rows (execute) are unaffected. Overridable per query.
|
|
277
369
|
*/
|
|
278
370
|
nestTables?: boolean | string;
|
|
371
|
+
/**
|
|
372
|
+
* Rewrite object-row keys (Postgres.js `transform` counterpart):
|
|
373
|
+
* `'camel'` turns `FIRST_NAME` into `firstName`, or pass a custom
|
|
374
|
+
* `(key) => key` mapper. Applied after `lowercase_keys` and to both
|
|
375
|
+
* parts of `nestTables` keys; column metadata keeps raw aliases.
|
|
376
|
+
* Overridable per query.
|
|
377
|
+
*/
|
|
378
|
+
transformKeys?: 'camel' | ((key: string) => string);
|
|
279
379
|
/**
|
|
280
380
|
* TCP keepalive probing to detect dead/stale connections (same option
|
|
281
381
|
* names as mysql2). On by default; set false to disable.
|
|
@@ -314,6 +414,20 @@ export interface Options {
|
|
|
314
414
|
* Default 0 (idle connections are kept forever).
|
|
315
415
|
*/
|
|
316
416
|
idleTimeoutMillis?: number;
|
|
417
|
+
/**
|
|
418
|
+
* Pool only: retire a physical connection after this many checkouts
|
|
419
|
+
* (pg's `maxUses`) — it is closed for good when returned to the pool
|
|
420
|
+
* and replaced on demand. Bounds server-side resource drift on
|
|
421
|
+
* long-lived connections. Default 0 (unlimited uses).
|
|
422
|
+
*/
|
|
423
|
+
maxUses?: number;
|
|
424
|
+
/**
|
|
425
|
+
* Pool only: retire a physical connection this many milliseconds after
|
|
426
|
+
* it was created (Postgres.js's `max_lifetime`), on return to the pool
|
|
427
|
+
* or by the idle sweep — even below `min`; replacements are created on
|
|
428
|
+
* demand. Default 0 (unlimited lifetime).
|
|
429
|
+
*/
|
|
430
|
+
maxLifetimeMillis?: number;
|
|
317
431
|
/**
|
|
318
432
|
* **Firebird 6.0+ only (Protocol 20+)**
|
|
319
433
|
*
|
package/lib/uri.js
CHANGED
|
@@ -187,7 +187,58 @@ function parseConnectionString(str) {
|
|
|
187
187
|
*/
|
|
188
188
|
function normalizeOptions(options) {
|
|
189
189
|
if (typeof options === 'string') {
|
|
190
|
-
|
|
190
|
+
options = parseConnectionString(options);
|
|
191
191
|
}
|
|
192
|
-
return options;
|
|
192
|
+
return applyEnvDefaults(options);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Fall back to environment variables for connection settings the caller
|
|
196
|
+
* did not provide — the pg-style convention using Firebird's own names:
|
|
197
|
+
* ISC_USER / ISC_PASSWORD (honoured by isql and every official tool) plus
|
|
198
|
+
* FIREBIRD_HOST / FIREBIRD_PORT / FIREBIRD_DATABASE / FIREBIRD_ROLE.
|
|
199
|
+
* Explicit options always win; the driver's built-in defaults (SYSDBA /
|
|
200
|
+
* masterkey / 127.0.0.1) still apply when neither is set. A fresh object
|
|
201
|
+
* is returned so caller-owned options objects are never mutated.
|
|
202
|
+
*/
|
|
203
|
+
const ENV_FALLBACKS = [
|
|
204
|
+
['user', 'ISC_USER'],
|
|
205
|
+
['password', 'ISC_PASSWORD'],
|
|
206
|
+
['host', 'FIREBIRD_HOST'],
|
|
207
|
+
['port', 'FIREBIRD_PORT'],
|
|
208
|
+
['database', 'FIREBIRD_DATABASE'],
|
|
209
|
+
['role', 'FIREBIRD_ROLE'],
|
|
210
|
+
];
|
|
211
|
+
function applyEnvDefaults(options) {
|
|
212
|
+
let out = options;
|
|
213
|
+
for (const [key, envName] of ENV_FALLBACKS) {
|
|
214
|
+
const value = process.env[envName];
|
|
215
|
+
// empty-string env vars (common in CI: `export ISC_PASSWORD=`)
|
|
216
|
+
// count as unset
|
|
217
|
+
if (value === undefined || value === '') {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// a service-manager connection's `database` selects the TARGET of
|
|
221
|
+
// backup/restore — never let a leftover env var pick that silently
|
|
222
|
+
if (key === 'database' && options.manager) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (out[key] === undefined || out[key] === null || out[key] === '') {
|
|
226
|
+
if (out === options) {
|
|
227
|
+
out = { ...options };
|
|
228
|
+
}
|
|
229
|
+
if (key === 'port') {
|
|
230
|
+
const port = Number(value);
|
|
231
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
232
|
+
// NaN is falsy: it would silently fall back to 3050
|
|
233
|
+
// downstream instead of surfacing the typo
|
|
234
|
+
throw new Error('Invalid FIREBIRD_PORT environment variable: ' + value);
|
|
235
|
+
}
|
|
236
|
+
out[key] = port;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
out[key] = value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
193
244
|
}
|
package/lib/wire/connection.d.ts
CHANGED
|
@@ -132,6 +132,12 @@ declare class Connection {
|
|
|
132
132
|
/** `count` may be the callback itself when no fetch size is given. */
|
|
133
133
|
fetch(statement: Statement, transaction: Transaction, count: any, callback?: QueueCallback): void;
|
|
134
134
|
fetchScroll(statement: Statement, transaction: Transaction, direction: string | number, offset: any, count: any, callback?: QueueCallback): void;
|
|
135
|
+
/**
|
|
136
|
+
* Query runtime information about a prepared statement via op_info_sql
|
|
137
|
+
* (e.g. Const.RECORDS_INFO for the per-verb DML row counts). The
|
|
138
|
+
* response is a plain op_response whose buffer holds the info clusters.
|
|
139
|
+
*/
|
|
140
|
+
statementInfo(statement: Statement, items: number[], callback?: QueueCallback): this | undefined;
|
|
135
141
|
fetchAll(statement: Statement, transaction: Transaction, callback: Callback<any[]>): void;
|
|
136
142
|
openBlob(blob: Quad, transaction: Transaction, callback: QueueCallback): void;
|
|
137
143
|
closeBlob(blob: any, callback?: QueueCallback, defer?: boolean): void;
|
package/lib/wire/connection.js
CHANGED
|
@@ -107,31 +107,8 @@ function statementCacheLimit(options) {
|
|
|
107
107
|
}
|
|
108
108
|
return 0;
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
[const_1.default.SQL_VARYING]: 'VARYING',
|
|
113
|
-
[const_1.default.SQL_SHORT]: 'SHORT',
|
|
114
|
-
[const_1.default.SQL_LONG]: 'LONG',
|
|
115
|
-
[const_1.default.SQL_FLOAT]: 'FLOAT',
|
|
116
|
-
[const_1.default.SQL_DOUBLE]: 'DOUBLE',
|
|
117
|
-
[const_1.default.SQL_D_FLOAT]: 'D_FLOAT',
|
|
118
|
-
[const_1.default.SQL_TIMESTAMP]: 'TIMESTAMP',
|
|
119
|
-
[const_1.default.SQL_BLOB]: 'BLOB',
|
|
120
|
-
[const_1.default.SQL_ARRAY]: 'ARRAY',
|
|
121
|
-
[const_1.default.SQL_QUAD]: 'QUAD',
|
|
122
|
-
[const_1.default.SQL_TYPE_TIME]: 'TIME',
|
|
123
|
-
[const_1.default.SQL_TYPE_DATE]: 'DATE',
|
|
124
|
-
[const_1.default.SQL_INT64]: 'INT64',
|
|
125
|
-
[const_1.default.SQL_INT128]: 'INT128',
|
|
126
|
-
[const_1.default.SQL_TIMESTAMP_TZ]: 'TIMESTAMP_TZ',
|
|
127
|
-
[const_1.default.SQL_TIMESTAMP_TZ_EX]: 'TIMESTAMP_TZ_EX',
|
|
128
|
-
[const_1.default.SQL_TIME_TZ]: 'TIME_TZ',
|
|
129
|
-
[const_1.default.SQL_TIME_TZ_EX]: 'TIME_TZ_EX',
|
|
130
|
-
[const_1.default.SQL_DEC16]: 'DEC16',
|
|
131
|
-
[const_1.default.SQL_DEC34]: 'DEC34',
|
|
132
|
-
[const_1.default.SQL_BOOLEAN]: 'BOOLEAN',
|
|
133
|
-
[const_1.default.SQL_NULL]: 'NULL',
|
|
134
|
-
};
|
|
110
|
+
// SQL type-code names live in xsqlvar.ts alongside the descriptors
|
|
111
|
+
const SQL_TYPE_NAMES = Xsql.SQL_TYPE_NAMES;
|
|
135
112
|
/**
|
|
136
113
|
* Run the user's typeCast hook (options.typeCast) for one column value.
|
|
137
114
|
* The hook receives the column metadata and a next() returning the value
|
|
@@ -145,16 +122,7 @@ function applyTypeCast(options, meta, defaultValue) {
|
|
|
145
122
|
if (typeof typeCast !== 'function') {
|
|
146
123
|
return defaultValue;
|
|
147
124
|
}
|
|
148
|
-
const column =
|
|
149
|
-
type: meta.type,
|
|
150
|
-
typeName: SQL_TYPE_NAMES[meta.type] || 'UNKNOWN',
|
|
151
|
-
subType: meta.subType,
|
|
152
|
-
scale: meta.scale,
|
|
153
|
-
length: meta.length,
|
|
154
|
-
field: meta.field,
|
|
155
|
-
relation: meta.relation,
|
|
156
|
-
alias: meta.alias,
|
|
157
|
-
};
|
|
125
|
+
const column = Xsql.describeField(meta);
|
|
158
126
|
// A hook exception must never escape into the row-decode loop: there it
|
|
159
127
|
// would be mistaken for an incomplete packet and desync the response
|
|
160
128
|
// queue (the same failure mode as issue #341). Fall back to the default
|
|
@@ -396,6 +364,30 @@ class Connection {
|
|
|
396
364
|
if (process.env.FIREBIRD_DEBUG) {
|
|
397
365
|
console.log('[fb-debug] response dispatched: queue remaining=%d pending remaining=%d xdr.pos=%d', self._queue.length, self._pending.length, xdr.pos);
|
|
398
366
|
}
|
|
367
|
+
// Surface isc_arg_warning entries (parsed since 2.10.0 but
|
|
368
|
+
// dropped here): resolve their message text and emit them on
|
|
369
|
+
// the Database on the next tick, so a listener registered
|
|
370
|
+
// inside this very response's callback (e.g. right after
|
|
371
|
+
// attach) still receives them.
|
|
372
|
+
if (obj && obj.warnings && obj.warnings.length && self.db && typeof self.db.emit === 'function') {
|
|
373
|
+
const warnings = obj.warnings;
|
|
374
|
+
for (const w of warnings) {
|
|
375
|
+
if (w.message === undefined) {
|
|
376
|
+
w.message = (0, utils_1.lookupMessages)([w]);
|
|
377
|
+
if (!w.message || w.message === 'Unknow error') {
|
|
378
|
+
// codes newer than the bundled firebird.msg:
|
|
379
|
+
// still say something actionable
|
|
380
|
+
w.message = 'Firebird warning ' + w.gdscode +
|
|
381
|
+
(w.params && w.params.length ? ': ' + w.params.join(', ') : '');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
process.nextTick(function () {
|
|
386
|
+
for (const w of warnings) {
|
|
387
|
+
self.db.emit('warning', w);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
399
391
|
if (obj && obj.status) {
|
|
400
392
|
obj.message = (0, utils_1.lookupMessages)(obj.status);
|
|
401
393
|
(0, callback_1.doCallback)(obj, cb);
|
|
@@ -1670,6 +1662,27 @@ class Connection {
|
|
|
1670
1662
|
callback.statement = statement;
|
|
1671
1663
|
this._queueEvent(callback);
|
|
1672
1664
|
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Query runtime information about a prepared statement via op_info_sql
|
|
1667
|
+
* (e.g. Const.RECORDS_INFO for the per-verb DML row counts). The
|
|
1668
|
+
* response is a plain op_response whose buffer holds the info clusters.
|
|
1669
|
+
*/
|
|
1670
|
+
statementInfo(statement, items, callback) {
|
|
1671
|
+
if (this._isClosed)
|
|
1672
|
+
return this.throwClosed(callback);
|
|
1673
|
+
this._pending.push('statementInfo');
|
|
1674
|
+
var msg = this._msg;
|
|
1675
|
+
var blr = this._blr;
|
|
1676
|
+
msg.pos = 0;
|
|
1677
|
+
blr.pos = 0;
|
|
1678
|
+
blr.addBytes(items);
|
|
1679
|
+
msg.addInt(const_1.default.op_info_sql);
|
|
1680
|
+
msg.addInt(statement.handle);
|
|
1681
|
+
msg.addInt(0); // incarnation
|
|
1682
|
+
msg.addBlr(blr);
|
|
1683
|
+
msg.addInt(65535); // buffer_length
|
|
1684
|
+
this._queueEvent(callback);
|
|
1685
|
+
}
|
|
1673
1686
|
fetchAll(statement, transaction, callback) {
|
|
1674
1687
|
const self = this;
|
|
1675
1688
|
const custom = statement.options || {};
|
|
@@ -2106,7 +2119,8 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2106
2119
|
data.frows = data.frows || [];
|
|
2107
2120
|
if (custom.asObject && !data.fcols) {
|
|
2108
2121
|
const nest = Xsql.resolveNestTables(custom, cnx.options);
|
|
2109
|
-
const
|
|
2122
|
+
const transform = Xsql.resolveKeyTransform(custom, cnx.options);
|
|
2123
|
+
const columnKeys = Xsql.computeColumnKeys(output, nest, lowercase_keys, transform);
|
|
2110
2124
|
data.fcols = columnKeys.map((k) => k.key);
|
|
2111
2125
|
if (nest === true) {
|
|
2112
2126
|
// computeColumnKeys always sets table when nesting
|
package/lib/wire/const.d.ts
CHANGED
|
@@ -404,6 +404,10 @@ declare const Const: Readonly<{
|
|
|
404
404
|
isc_info_sql_stmt_type: number;
|
|
405
405
|
isc_info_sql_get_plan: number;
|
|
406
406
|
isc_info_sql_records: number;
|
|
407
|
+
isc_info_req_select_count: number;
|
|
408
|
+
isc_info_req_insert_count: number;
|
|
409
|
+
isc_info_req_update_count: number;
|
|
410
|
+
isc_info_req_delete_count: number;
|
|
407
411
|
isc_info_sql_batch_fetch: number;
|
|
408
412
|
isc_info_sql_relation_alias: number;
|
|
409
413
|
isc_info_sql_explain_plan: number;
|
|
@@ -589,6 +593,7 @@ declare const Const: Readonly<{
|
|
|
589
593
|
isc_spb_trc_cfg: number;
|
|
590
594
|
DESCRIBE: number[];
|
|
591
595
|
DESCRIBE_WITH_SCHEMA: number[];
|
|
596
|
+
RECORDS_INFO: number[];
|
|
592
597
|
SUPPORTED_PROTOCOL: number[][];
|
|
593
598
|
}>;
|
|
594
599
|
export = Const;
|
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
|
|
@@ -618,6 +624,11 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
618
624
|
sqlInfo.isc_info_sql_length,
|
|
619
625
|
sqlInfo.isc_info_sql_describe_end
|
|
620
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
|
+
];
|
|
621
632
|
/***********************/
|
|
622
633
|
/* ISC Services */
|
|
623
634
|
/***********************/
|
|
@@ -843,6 +854,7 @@ const Const = Object.freeze({
|
|
|
843
854
|
...defaultOptions,
|
|
844
855
|
DESCRIBE,
|
|
845
856
|
DESCRIBE_WITH_SCHEMA,
|
|
857
|
+
RECORDS_INFO,
|
|
846
858
|
...dpb,
|
|
847
859
|
...dsql,
|
|
848
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,7 @@ 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");
|
|
9
10
|
const xsqlvar_1 = require("./xsqlvar");
|
|
10
11
|
const eventConnection_1 = __importDefault(require("./eventConnection"));
|
|
11
12
|
const fbEventManager_1 = __importDefault(require("./fbEventManager"));
|
|
@@ -76,7 +77,7 @@ function readblob(blob, callback) {
|
|
|
76
77
|
});
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
|
-
function fetchBlobSyncRow(row, meta, nestTables, lowercaseKeys, callback) {
|
|
80
|
+
function fetchBlobSyncRow(row, meta, nestTables, lowercaseKeys, transform, callback) {
|
|
80
81
|
if (!row || !meta || !meta.length || !meta.some((m) => m && m.type === const_1.default.SQL_BLOB)) {
|
|
81
82
|
callback(null, row);
|
|
82
83
|
return;
|
|
@@ -86,7 +87,7 @@ function fetchBlobSyncRow(row, meta, nestTables, lowercaseKeys, callback) {
|
|
|
86
87
|
// duplicate JOIN column names (and nested rows) break that alignment.
|
|
87
88
|
// Array rows (sequentially's legacy boolean form) are keyed by index.
|
|
88
89
|
const isArrayRow = Array.isArray(row);
|
|
89
|
-
const keys = isArrayRow ? null : (0, xsqlvar_1.computeColumnKeys)(meta, nestTables, lowercaseKeys);
|
|
90
|
+
const keys = isArrayRow ? null : (0, xsqlvar_1.computeColumnKeys)(meta, nestTables, lowercaseKeys, transform);
|
|
90
91
|
const blobCells = [];
|
|
91
92
|
for (let i = 0; i < meta.length; i++) {
|
|
92
93
|
if (!meta[i] || meta[i].type !== const_1.default.SQL_BLOB) {
|
|
@@ -126,6 +127,15 @@ class Database extends events_1.default.EventEmitter {
|
|
|
126
127
|
connection.db = this;
|
|
127
128
|
this.eventid = 1;
|
|
128
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
|
+
}
|
|
129
139
|
escape(value) {
|
|
130
140
|
return (0, utils_1.escape)(value, this.connection.accept.protocolVersion);
|
|
131
141
|
}
|
|
@@ -263,6 +273,9 @@ class Database extends events_1.default.EventEmitter {
|
|
|
263
273
|
callback = undefined;
|
|
264
274
|
}
|
|
265
275
|
var self = this;
|
|
276
|
+
var keyResolutionDone = false;
|
|
277
|
+
var resolvedNest;
|
|
278
|
+
var resolvedTransform;
|
|
266
279
|
var _on = function (row, i, meta, next) {
|
|
267
280
|
var done = false;
|
|
268
281
|
var finish = function (err) {
|
|
@@ -272,9 +285,15 @@ class Database extends events_1.default.EventEmitter {
|
|
|
272
285
|
done = true;
|
|
273
286
|
next(err);
|
|
274
287
|
};
|
|
275
|
-
// options is read at call time, after the normalization below
|
|
276
|
-
|
|
277
|
-
|
|
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) {
|
|
278
297
|
if (blobErr) {
|
|
279
298
|
finish(blobErr);
|
|
280
299
|
return;
|
|
@@ -468,8 +487,9 @@ class Database extends events_1.default.EventEmitter {
|
|
|
468
487
|
/*
|
|
469
488
|
* Promise / async-await API.
|
|
470
489
|
* Each *Async method wraps its callback counterpart; the callback API
|
|
471
|
-
* stays untouched.
|
|
472
|
-
*
|
|
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.
|
|
473
493
|
*/
|
|
474
494
|
queryAsync(query, params, options) {
|
|
475
495
|
var self = this;
|
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;
|