node-firebird 2.8.1 → 2.9.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 +103 -2
- package/lib/index.d.ts +12 -9
- package/lib/index.js +11 -3
- package/lib/named-params.d.ts +42 -0
- package/lib/named-params.js +133 -0
- package/lib/types.d.ts +42 -20
- package/lib/uri.d.ts +57 -0
- package/lib/uri.js +193 -0
- package/lib/wire/connection.js +19 -2
- package/lib/wire/statement.d.ts +9 -1
- package/lib/wire/statement.js +25 -1
- package/lib/wire/transaction.d.ts +3 -1
- package/lib/wire/transaction.js +28 -5
- package/package.json +1 -1
- package/src/index.ts +18 -11
- package/src/named-params.ts +145 -0
- package/src/types.ts +43 -20
- package/src/uri.ts +204 -0
- package/src/wire/connection.ts +17 -2
- package/src/wire/statement.ts +31 -2
- package/src/wire/transaction.ts +32 -5
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
- [Installation](#installation)
|
|
12
12
|
- [Usage](#usage) — including [developing the driver](#developing-the-driver)
|
|
13
13
|
- [Promises and async/await](#promises-and-asyncawait) — the `*Async` API plus `withConnection` / `withTransaction` helpers
|
|
14
|
-
- [Connection types](#connection-types) — connection options, classic connections, pooling
|
|
14
|
+
- [Connection types](#connection-types) — connection options, `firebird://` URIs and traditional connection strings, classic connections, pooling
|
|
15
15
|
- [Database object (db)](#database-object-db) — database, transaction and statement methods/options
|
|
16
|
-
- [Examples](#examples) — parametrized queries, BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
|
|
16
|
+
- [Examples](#examples) — parametrized queries, named placeholders, BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
|
|
17
17
|
- [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, query cancellation (AbortSignal), batch execution (bulk inserts), statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
|
|
18
18
|
- [Using node-firebird with Express.js](#using-node-firebird-with-expressjs)
|
|
19
19
|
- [FAQ](#faq)
|
|
@@ -180,8 +180,69 @@ options.maxNegotiatedProtocols = 10; // optional; limit maximum protocol version
|
|
|
180
180
|
options.defaultSchema = undefined; // optional; sets session CURRENT_SCHEMA at connect time (FB >= 6.0)
|
|
181
181
|
options.searchPath = undefined; // optional; ordered list/array of schemas to resolve unqualified object references (FB >= 6.0)
|
|
182
182
|
options.jsonAsObject = false; // optional; automatically stringify parameters and parse query results that contain JSON (FB >= 6.0)
|
|
183
|
+
options.namedPlaceholders = false; // set to true to allow :name placeholders in SQL with a { name: value } params object (see Named placeholders)
|
|
183
184
|
```
|
|
184
185
|
|
|
186
|
+
### Connection URI strings
|
|
187
|
+
|
|
188
|
+
Everywhere an options object is accepted — `attach`, `create`,
|
|
189
|
+
`attachOrCreate`, `drop`, `Firebird.pool()` and their `*Async`
|
|
190
|
+
counterparts — a `firebird://` URI string works too, which is handy for
|
|
191
|
+
12-factor apps and containers that configure the database via a single
|
|
192
|
+
environment variable:
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
const db = await Firebird.attachAsync(process.env.DATABASE_URL);
|
|
196
|
+
// e.g. DATABASE_URL=firebird://SYSDBA:masterkey@db.example.com:3050//var/fb/prod.fdb?encoding=UTF8
|
|
197
|
+
|
|
198
|
+
const pool = Firebird.pool(10,
|
|
199
|
+
'firebird://app:secret@localhost/appdb?lowercase_keys=true&idleTimeoutMillis=30000');
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The database part after `host[:port]/` can be:
|
|
203
|
+
|
|
204
|
+
| URI | database |
|
|
205
|
+
| :--- | :--- |
|
|
206
|
+
| `firebird://host/employee` | the alias `employee` |
|
|
207
|
+
| `firebird://host//var/fb/prod.fdb` | `/var/fb/prod.fdb` (explicit absolute path) |
|
|
208
|
+
| `firebird://host/var/fb/prod.fdb` | `/var/fb/prod.fdb` (a database part with `/` is a path — aliases cannot contain slashes) |
|
|
209
|
+
| `firebird://host/C:/fbdata/prod.fdb` | the Windows path `C:/fbdata/prod.fdb` |
|
|
210
|
+
|
|
211
|
+
Query parameters map 1:1 onto the connection options above and are coerced
|
|
212
|
+
to the right type (`?pageSize=8192&lowercase_keys=true&wireCompression=1`).
|
|
213
|
+
Credentials and paths are URL-decoded, so reserved characters can be
|
|
214
|
+
percent-encoded (`p%40ss` for `p@ss`); `user`/`password` may alternatively
|
|
215
|
+
be passed as query parameters. IPv6 hosts use brackets:
|
|
216
|
+
`firebird://[::1]:3050/employee`. The parser is exported as
|
|
217
|
+
`Firebird.parseConnectionUri(uri)` if you need the resulting options object.
|
|
218
|
+
|
|
219
|
+
### Traditional connection strings (old style)
|
|
220
|
+
|
|
221
|
+
The classic Firebird connection string format — the same
|
|
222
|
+
`[host[/port]:]{path | alias}` strings isql and the other Firebird tools
|
|
223
|
+
use — is accepted everywhere too:
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
const db = await Firebird.attachAsync('db.example.com/3051:/var/fb/prod.fdb');
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
| Connection string | meaning |
|
|
230
|
+
| :--- | :--- |
|
|
231
|
+
| `employee` | the alias `employee` on `127.0.0.1:3050` |
|
|
232
|
+
| `/var/fb/prod.fdb` | a path on `127.0.0.1:3050` |
|
|
233
|
+
| `db.example.com:employee` | the alias `employee` on `db.example.com:3050` |
|
|
234
|
+
| `db.example.com/3051:/var/fb/prod.fdb` | host and explicit port |
|
|
235
|
+
| `myserver:C:\fbdata\prod.fdb` | a Windows path behind a host |
|
|
236
|
+
| `C:\fbdata\prod.fdb` | a single character before `:` is a drive letter, not a host (same rule as Firebird) |
|
|
237
|
+
| `[::1]/3050:employee` | IPv6 hosts use brackets |
|
|
238
|
+
|
|
239
|
+
Unlike `firebird://` URIs, traditional strings carry no credentials or
|
|
240
|
+
options — the driver defaults apply (`SYSDBA`/`masterkey`, port 3050), and
|
|
241
|
+
the port must be numeric (`/etc/services` names are not resolved). Use the
|
|
242
|
+
URI form or an options object when you need to set anything else.
|
|
243
|
+
`Firebird.parseConnectionString(str)` parses both forms and is what
|
|
244
|
+
`attach`/`create`/`pool` use internally for string arguments.
|
|
245
|
+
|
|
185
246
|
### Classic
|
|
186
247
|
|
|
187
248
|
```js
|
|
@@ -385,6 +446,46 @@ Firebird.attach(options, function (err, db) {
|
|
|
385
446
|
});
|
|
386
447
|
```
|
|
387
448
|
|
|
449
|
+
### Named placeholders
|
|
450
|
+
|
|
451
|
+
With the `namedPlaceholders: true` connection option, SQL may use `:name`
|
|
452
|
+
markers and parameters may be passed as a values-by-name object instead of
|
|
453
|
+
a positional array. The rewrite happens client-side before the statement is
|
|
454
|
+
prepared, so it works on every Firebird version; positional `?` arrays keep
|
|
455
|
+
working unchanged on the same connection.
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
const db = await Firebird.attachAsync({ ...options, namedPlaceholders: true });
|
|
459
|
+
// or: firebird://user:pass@host/db?namedPlaceholders=true
|
|
460
|
+
|
|
461
|
+
const rows = await db.queryAsync(
|
|
462
|
+
'SELECT * FROM USERS WHERE ALIAS = :alias AND CREATED > :since',
|
|
463
|
+
{ alias: 'Peter', since: new Date(2026, 0, 1) });
|
|
464
|
+
|
|
465
|
+
// A name may repeat — it binds once per occurrence:
|
|
466
|
+
await db.queryAsync(
|
|
467
|
+
'SELECT * FROM T WHERE A = :v OR B = :v', { v: 42 });
|
|
468
|
+
|
|
469
|
+
// Batch rows can be objects too (Firebird 4.0+):
|
|
470
|
+
await db.executeBatchAsync(
|
|
471
|
+
'INSERT INTO USERS (ID, ALIAS) VALUES (:id, :alias)',
|
|
472
|
+
[{ id: 1, alias: 'a' }, { id: 2, alias: 'b' }]);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Placeholders inside string literals (`'...'`), quoted identifiers (`"..."`),
|
|
476
|
+
comments and `q'{...}'` alternative literals are left untouched. A key
|
|
477
|
+
present with value `null` binds SQL `NULL`; a *missing* key raises
|
|
478
|
+
`Missing value for named placeholder(s): ...`.
|
|
479
|
+
|
|
480
|
+
The scanner has no SQL grammar, so inside an `EXECUTE BLOCK` body every
|
|
481
|
+
PSQL `:variable` reference looks like a placeholder too. The option is
|
|
482
|
+
therefore off by default — and can be disabled for a single statement with
|
|
483
|
+
the per-query option:
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
await db.queryAsync(execBlockSql, [], { namedPlaceholders: false });
|
|
487
|
+
```
|
|
488
|
+
|
|
388
489
|
### Tablespaces and Schema Partitioning (Firebird 6.0+)
|
|
389
490
|
|
|
390
491
|
For Firebird 6.0+ (Protocol 20+), you can create and manage physical tablespace locations and logical schema namespaces, optionally partitioning schemas into specific physical tablespaces.
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Connection from './wire/connection';
|
|
2
2
|
import { escape as escapeValue } from './utils';
|
|
3
|
+
import { parseConnectionUri, parseConnectionString } from './uri';
|
|
3
4
|
import type { Options, SvcMgrOptions, DatabaseCallback, ServiceManagerCallback, SimpleCallback, ConnectionPool, Database, ServiceManager } from './types';
|
|
4
5
|
export * from './types';
|
|
5
6
|
export { GDSCode } from './gdscodes';
|
|
@@ -29,14 +30,16 @@ export declare const escape: typeof escapeValue;
|
|
|
29
30
|
* the connection was stored on the module object itself.
|
|
30
31
|
*/
|
|
31
32
|
export declare let connection: Connection | undefined;
|
|
32
|
-
export declare function attach(options: Options, callback: DatabaseCallback): void;
|
|
33
|
+
export declare function attach(options: Options | string, callback: DatabaseCallback): void;
|
|
33
34
|
export declare function attach(options: SvcMgrOptions, callback: ServiceManagerCallback): void;
|
|
34
|
-
export declare function drop(options: Options, callback: SimpleCallback): void;
|
|
35
|
-
export declare function create(options: Options, callback: DatabaseCallback): void;
|
|
36
|
-
export declare function attachOrCreate(options: Options, callback: DatabaseCallback): void;
|
|
37
|
-
export declare function pool(max: number, options: Options): ConnectionPool;
|
|
35
|
+
export declare function drop(options: Options | string, callback: SimpleCallback): void;
|
|
36
|
+
export declare function create(options: Options | string, callback: DatabaseCallback): void;
|
|
37
|
+
export declare function attachOrCreate(options: Options | string, callback: DatabaseCallback): void;
|
|
38
|
+
export declare function pool(max: number, options: Options | string): ConnectionPool;
|
|
39
|
+
export { parseConnectionUri, parseConnectionString };
|
|
40
|
+
export { parseNamedPlaceholders } from './named-params';
|
|
38
41
|
export declare function attachAsync(options: SvcMgrOptions): Promise<ServiceManager>;
|
|
39
|
-
export declare function attachAsync(options: Options): Promise<Database>;
|
|
40
|
-
export declare function createAsync(options: Options): Promise<Database>;
|
|
41
|
-
export declare function attachOrCreateAsync(options: Options): Promise<Database>;
|
|
42
|
-
export declare function dropAsync(options: Options): Promise<void>;
|
|
42
|
+
export declare function attachAsync(options: Options | string): Promise<Database>;
|
|
43
|
+
export declare function createAsync(options: Options | string): Promise<Database>;
|
|
44
|
+
export declare function attachOrCreateAsync(options: Options | string): Promise<Database>;
|
|
45
|
+
export declare function dropAsync(options: Options | string): Promise<void>;
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.connection = exports.escape = exports.ISOLATION_READ_COMMITTED_READ_ONLY = exports.ISOLATION_SERIALIZABLE = exports.ISOLATION_REPEATABLE_READ = exports.ISOLATION_READ_COMMITTED = exports.ISOLATION_READ_UNCOMMITTED = exports.WIRE_CRYPT_ENABLE = exports.WIRE_CRYPT_DISABLE = exports.AUTH_PLUGIN_SRP512 = exports.AUTH_PLUGIN_SRP384 = exports.AUTH_PLUGIN_SRP256 = exports.AUTH_PLUGIN_SRP = exports.AUTH_PLUGIN_LEGACY = exports.GDSCode = void 0;
|
|
20
|
+
exports.parseNamedPlaceholders = exports.parseConnectionString = exports.parseConnectionUri = exports.connection = exports.escape = exports.ISOLATION_READ_COMMITTED_READ_ONLY = exports.ISOLATION_SERIALIZABLE = exports.ISOLATION_REPEATABLE_READ = exports.ISOLATION_READ_COMMITTED = exports.ISOLATION_READ_UNCOMMITTED = exports.WIRE_CRYPT_ENABLE = exports.WIRE_CRYPT_DISABLE = exports.AUTH_PLUGIN_SRP512 = exports.AUTH_PLUGIN_SRP384 = exports.AUTH_PLUGIN_SRP256 = exports.AUTH_PLUGIN_SRP = exports.AUTH_PLUGIN_LEGACY = exports.GDSCode = void 0;
|
|
21
21
|
exports.attach = attach;
|
|
22
22
|
exports.drop = drop;
|
|
23
23
|
exports.create = create;
|
|
@@ -32,6 +32,9 @@ const callback_1 = require("./callback");
|
|
|
32
32
|
const connection_1 = __importDefault(require("./wire/connection"));
|
|
33
33
|
const pool_1 = __importDefault(require("./pool"));
|
|
34
34
|
const utils_1 = require("./utils");
|
|
35
|
+
const uri_1 = require("./uri");
|
|
36
|
+
Object.defineProperty(exports, "parseConnectionUri", { enumerable: true, get: function () { return uri_1.parseConnectionUri; } });
|
|
37
|
+
Object.defineProperty(exports, "parseConnectionString", { enumerable: true, get: function () { return uri_1.parseConnectionString; } });
|
|
35
38
|
__exportStar(require("./types"), exports);
|
|
36
39
|
var gdscodes_1 = require("./gdscodes");
|
|
37
40
|
Object.defineProperty(exports, "GDSCode", { enumerable: true, get: function () { return gdscodes_1.GDSCode; } });
|
|
@@ -61,6 +64,7 @@ exports.ISOLATION_SERIALIZABLE = const_1.default.ISOLATION_SERIALIZABLE;
|
|
|
61
64
|
exports.ISOLATION_READ_COMMITTED_READ_ONLY = const_1.default.ISOLATION_READ_COMMITTED_READ_ONLY;
|
|
62
65
|
exports.escape = utils_1.escape;
|
|
63
66
|
function attach(options, callback) {
|
|
67
|
+
options = (0, uri_1.normalizeOptions)(options);
|
|
64
68
|
var host = options.host || const_1.default.DEFAULT_HOST;
|
|
65
69
|
var port = options.port || const_1.default.DEFAULT_PORT;
|
|
66
70
|
var manager = options.manager || false;
|
|
@@ -83,7 +87,7 @@ function attach(options, callback) {
|
|
|
83
87
|
}, options);
|
|
84
88
|
}
|
|
85
89
|
function drop(options, callback) {
|
|
86
|
-
attach(options, function (err, db) {
|
|
90
|
+
attach((0, uri_1.normalizeOptions)(options), function (err, db) {
|
|
87
91
|
if (err) {
|
|
88
92
|
callback({ error: err, message: "Drop error" });
|
|
89
93
|
return;
|
|
@@ -92,6 +96,7 @@ function drop(options, callback) {
|
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
function create(options, callback) {
|
|
99
|
+
options = (0, uri_1.normalizeOptions)(options);
|
|
95
100
|
var host = options.host || const_1.default.DEFAULT_HOST;
|
|
96
101
|
var port = options.port || const_1.default.DEFAULT_PORT;
|
|
97
102
|
var cnx = exports.connection = new connection_1.default(host, port, function (err) {
|
|
@@ -112,6 +117,7 @@ function create(options, callback) {
|
|
|
112
117
|
}, options);
|
|
113
118
|
}
|
|
114
119
|
function attachOrCreate(options, callback) {
|
|
120
|
+
options = (0, uri_1.normalizeOptions)(options);
|
|
115
121
|
var host = options.host || const_1.default.DEFAULT_HOST;
|
|
116
122
|
var port = options.port || const_1.default.DEFAULT_PORT;
|
|
117
123
|
var cnx = exports.connection = new connection_1.default(host, port, function (err) {
|
|
@@ -139,8 +145,10 @@ function attachOrCreate(options, callback) {
|
|
|
139
145
|
}
|
|
140
146
|
// Pooling
|
|
141
147
|
function pool(max, options) {
|
|
142
|
-
return new pool_1.default(attach, max, Object.assign({}, options, { isPool: true }));
|
|
148
|
+
return new pool_1.default(attach, max, Object.assign({}, (0, uri_1.normalizeOptions)(options), { isPool: true }));
|
|
143
149
|
}
|
|
150
|
+
var named_params_1 = require("./named-params");
|
|
151
|
+
Object.defineProperty(exports, "parseNamedPlaceholders", { enumerable: true, get: function () { return named_params_1.parseNamedPlaceholders; } });
|
|
144
152
|
function attachAsync(options) {
|
|
145
153
|
return (0, callback_1.fromCallback)(function (cb) { attach(options, cb); });
|
|
146
154
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/***************************************
|
|
2
|
+
*
|
|
3
|
+
* Named placeholders (:name → ?)
|
|
4
|
+
*
|
|
5
|
+
***************************************/
|
|
6
|
+
/**
|
|
7
|
+
* Result of scanning a SQL string for named placeholders.
|
|
8
|
+
*/
|
|
9
|
+
export interface ParsedNamedPlaceholders {
|
|
10
|
+
/** SQL with every named placeholder replaced by a positional "?". */
|
|
11
|
+
sql: string;
|
|
12
|
+
/**
|
|
13
|
+
* Placeholder names in positional order (a repeated name appears once
|
|
14
|
+
* per occurrence), or null when the SQL contains none.
|
|
15
|
+
*/
|
|
16
|
+
names: string[] | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Scan `sql` for named placeholders (`:name`) and rewrite them to positional
|
|
20
|
+
* `?` markers, returning the rewritten SQL and the names in positional
|
|
21
|
+
* order. Placeholders inside string literals ('...'), quoted identifiers
|
|
22
|
+
* ("..."), line comments (--), block comments and Firebird alternative
|
|
23
|
+
* string literals (q'{...}') are left untouched.
|
|
24
|
+
*
|
|
25
|
+
* Note: the scanner has no SQL grammar — inside an EXECUTE BLOCK body every
|
|
26
|
+
* `:variable` reference looks like a placeholder too. Use positional params
|
|
27
|
+
* (or per-call `namedPlaceholders: false`) for EXECUTE BLOCK.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseNamedPlaceholders(sql: string): ParsedNamedPlaceholders;
|
|
30
|
+
/**
|
|
31
|
+
* True when `params` is a plain values-by-name object (and not one of the
|
|
32
|
+
* values the driver accepts as a single positional parameter, like Date or
|
|
33
|
+
* Buffer).
|
|
34
|
+
*/
|
|
35
|
+
export declare function isNamedParamsObject(params: any): params is Record<string, any>;
|
|
36
|
+
/**
|
|
37
|
+
* Map a values-by-name object onto the positional order collected by
|
|
38
|
+
* parseNamedPlaceholders. A name may be bound multiple times; every name
|
|
39
|
+
* must be an own property of `params` (a present key holding null is a
|
|
40
|
+
* NULL parameter, a missing key is an error).
|
|
41
|
+
*/
|
|
42
|
+
export declare function bindNamedParams(names: string[], params: Record<string, any>): any[];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/***************************************
|
|
3
|
+
*
|
|
4
|
+
* Named placeholders (:name → ?)
|
|
5
|
+
*
|
|
6
|
+
***************************************/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.parseNamedPlaceholders = parseNamedPlaceholders;
|
|
9
|
+
exports.isNamedParamsObject = isNamedParamsObject;
|
|
10
|
+
exports.bindNamedParams = bindNamedParams;
|
|
11
|
+
const IDENT_START = /[A-Za-z_]/;
|
|
12
|
+
const IDENT_PART = /[A-Za-z0-9_$]/;
|
|
13
|
+
// Parsing is pure string work, so identical SQL (the common case with
|
|
14
|
+
// query builders and hot paths) is scanned only once.
|
|
15
|
+
const CACHE_MAX = 100;
|
|
16
|
+
const cache = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Scan `sql` for named placeholders (`:name`) and rewrite them to positional
|
|
19
|
+
* `?` markers, returning the rewritten SQL and the names in positional
|
|
20
|
+
* order. Placeholders inside string literals ('...'), quoted identifiers
|
|
21
|
+
* ("..."), line comments (--), block comments and Firebird alternative
|
|
22
|
+
* string literals (q'{...}') are left untouched.
|
|
23
|
+
*
|
|
24
|
+
* Note: the scanner has no SQL grammar — inside an EXECUTE BLOCK body every
|
|
25
|
+
* `:variable` reference looks like a placeholder too. Use positional params
|
|
26
|
+
* (or per-call `namedPlaceholders: false`) for EXECUTE BLOCK.
|
|
27
|
+
*/
|
|
28
|
+
function parseNamedPlaceholders(sql) {
|
|
29
|
+
var cached = cache.get(sql);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
32
|
+
var out = '';
|
|
33
|
+
var names = [];
|
|
34
|
+
var i = 0;
|
|
35
|
+
var n = sql.length;
|
|
36
|
+
while (i < n) {
|
|
37
|
+
var c = sql[i];
|
|
38
|
+
if (c === "'" || c === '"') {
|
|
39
|
+
// string literal or quoted identifier; doubled quotes escape
|
|
40
|
+
var quote = c;
|
|
41
|
+
var end = i + 1;
|
|
42
|
+
while (end < n) {
|
|
43
|
+
if (sql[end] === quote) {
|
|
44
|
+
if (sql[end + 1] === quote) {
|
|
45
|
+
end += 2;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
end++;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
end++;
|
|
52
|
+
}
|
|
53
|
+
out += sql.slice(i, end);
|
|
54
|
+
i = end;
|
|
55
|
+
}
|
|
56
|
+
else if (c === '-' && sql[i + 1] === '-') {
|
|
57
|
+
var eol = sql.indexOf('\n', i);
|
|
58
|
+
if (eol === -1)
|
|
59
|
+
eol = n;
|
|
60
|
+
out += sql.slice(i, eol);
|
|
61
|
+
i = eol;
|
|
62
|
+
}
|
|
63
|
+
else if (c === '/' && sql[i + 1] === '*') {
|
|
64
|
+
var close = sql.indexOf('*/', i + 2);
|
|
65
|
+
close = close === -1 ? n : close + 2;
|
|
66
|
+
out += sql.slice(i, close);
|
|
67
|
+
i = close;
|
|
68
|
+
}
|
|
69
|
+
else if ((c === 'q' || c === 'Q') && sql[i + 1] === "'" && i + 2 < n &&
|
|
70
|
+
(i === 0 || !IDENT_PART.test(sql[i - 1]))) {
|
|
71
|
+
// Firebird 3+ alternative string literal: q'{...}' / q'!...!'
|
|
72
|
+
var open = sql[i + 2];
|
|
73
|
+
var closer = open === '(' ? ')'
|
|
74
|
+
: open === '[' ? ']'
|
|
75
|
+
: open === '{' ? '}'
|
|
76
|
+
: open === '<' ? '>'
|
|
77
|
+
: open;
|
|
78
|
+
var stop = sql.indexOf(closer + "'", i + 3);
|
|
79
|
+
stop = stop === -1 ? n : stop + 2;
|
|
80
|
+
out += sql.slice(i, stop);
|
|
81
|
+
i = stop;
|
|
82
|
+
}
|
|
83
|
+
else if (c === ':' && i + 1 < n && IDENT_START.test(sql[i + 1])) {
|
|
84
|
+
var end2 = i + 2;
|
|
85
|
+
while (end2 < n && IDENT_PART.test(sql[end2]))
|
|
86
|
+
end2++;
|
|
87
|
+
names.push(sql.slice(i + 1, end2));
|
|
88
|
+
out += '?';
|
|
89
|
+
i = end2;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
out += c;
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
var result = names.length
|
|
97
|
+
? { sql: out, names: names }
|
|
98
|
+
: { sql: sql, names: null };
|
|
99
|
+
if (cache.size >= CACHE_MAX) {
|
|
100
|
+
cache.delete(cache.keys().next().value);
|
|
101
|
+
}
|
|
102
|
+
cache.set(sql, result);
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* True when `params` is a plain values-by-name object (and not one of the
|
|
107
|
+
* values the driver accepts as a single positional parameter, like Date or
|
|
108
|
+
* Buffer).
|
|
109
|
+
*/
|
|
110
|
+
function isNamedParamsObject(params) {
|
|
111
|
+
return params !== null &&
|
|
112
|
+
typeof params === 'object' &&
|
|
113
|
+
!Array.isArray(params) &&
|
|
114
|
+
!Buffer.isBuffer(params) &&
|
|
115
|
+
!(params instanceof Date);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Map a values-by-name object onto the positional order collected by
|
|
119
|
+
* parseNamedPlaceholders. A name may be bound multiple times; every name
|
|
120
|
+
* must be an own property of `params` (a present key holding null is a
|
|
121
|
+
* NULL parameter, a missing key is an error).
|
|
122
|
+
*/
|
|
123
|
+
function bindNamedParams(names, params) {
|
|
124
|
+
var missing = [];
|
|
125
|
+
var values = names.map(function (name) {
|
|
126
|
+
if (!Object.prototype.hasOwnProperty.call(params, name))
|
|
127
|
+
missing.push(name);
|
|
128
|
+
return params[name];
|
|
129
|
+
});
|
|
130
|
+
if (missing.length)
|
|
131
|
+
throw new Error('Missing value for named placeholder(s): ' + missing.join(', '));
|
|
132
|
+
return values;
|
|
133
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -90,9 +90,21 @@ export type BatchOptions = {
|
|
|
90
90
|
/** Rows per op_batch_msg packet (default 500). */
|
|
91
91
|
chunkSize?: number;
|
|
92
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Positional query parameters (array), or — when named placeholders are
|
|
95
|
+
* enabled via the `namedPlaceholders` connection/query option — values by
|
|
96
|
+
* placeholder name.
|
|
97
|
+
*/
|
|
98
|
+
export type QueryParams = any[] | Record<string, any>;
|
|
93
99
|
export type QueryOptions = {
|
|
94
100
|
timeout?: number;
|
|
95
101
|
scrollable?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Per-query override of the `namedPlaceholders` connection option
|
|
104
|
+
* (e.g. disable it for one EXECUTE BLOCK statement whose body uses
|
|
105
|
+
* `:variable` PSQL references).
|
|
106
|
+
*/
|
|
107
|
+
namedPlaceholders?: boolean;
|
|
96
108
|
/**
|
|
97
109
|
* Abort the query when the signal fires (Firebird 2.5+ / protocol 12+).
|
|
98
110
|
* If the signal is already aborted the query is not sent at all and the
|
|
@@ -107,11 +119,11 @@ export interface Database {
|
|
|
107
119
|
detach(callback?: SimpleCallback): Database;
|
|
108
120
|
transaction(options: TransactionOptions | Isolation | TransactionCallback, callback?: TransactionCallback): Database;
|
|
109
121
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
|
|
110
|
-
query(query: string, params:
|
|
111
|
-
execute(query: string, params:
|
|
122
|
+
query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): Database;
|
|
123
|
+
execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): Database;
|
|
112
124
|
/** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
|
|
113
|
-
executeBatch(query: string, rows:
|
|
114
|
-
sequentially(query: string, params:
|
|
125
|
+
executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
|
|
126
|
+
sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
|
|
115
127
|
drop(callback: SimpleCallback): void;
|
|
116
128
|
escape(value: any): string;
|
|
117
129
|
attachEvent(callback: any): this;
|
|
@@ -119,10 +131,10 @@ export interface Database {
|
|
|
119
131
|
alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
|
|
120
132
|
dropTablespace(name: string, callback?: QueryCallback): Database;
|
|
121
133
|
createSchema(schemaName: string, tablespaceName?: string | QueryCallback, callback?: QueryCallback): Database;
|
|
122
|
-
queryAsync<T = any>(query: string, params?:
|
|
123
|
-
executeAsync<T = any>(query: string, params?:
|
|
124
|
-
executeBatchAsync(query: string, rows:
|
|
125
|
-
sequentiallyAsync(query: string, params:
|
|
134
|
+
queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
135
|
+
executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
136
|
+
executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
|
|
137
|
+
sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
126
138
|
sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
127
139
|
transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
|
|
128
140
|
startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
|
|
@@ -143,19 +155,19 @@ export interface Database {
|
|
|
143
155
|
}
|
|
144
156
|
export interface Transaction {
|
|
145
157
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
|
|
146
|
-
query(query: string, params:
|
|
147
|
-
execute(query: string, params:
|
|
158
|
+
query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
|
|
159
|
+
execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
|
|
148
160
|
/** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
|
|
149
|
-
executeBatch(query: string, rows:
|
|
150
|
-
sequentially(query: string, params:
|
|
161
|
+
executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
|
|
162
|
+
sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
|
|
151
163
|
commit(callback?: SimpleCallback): void;
|
|
152
164
|
commitRetaining(callback?: SimpleCallback): void;
|
|
153
165
|
rollback(callback?: SimpleCallback): void;
|
|
154
166
|
rollbackRetaining(callback?: SimpleCallback): void;
|
|
155
|
-
queryAsync<T = any>(query: string, params?:
|
|
156
|
-
executeAsync<T = any>(query: string, params?:
|
|
157
|
-
executeBatchAsync(query: string, rows:
|
|
158
|
-
sequentiallyAsync(query: string, params:
|
|
167
|
+
queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
168
|
+
executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
|
|
169
|
+
executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
|
|
170
|
+
sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
159
171
|
sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
160
172
|
newStatementAsync(query: string): Promise<Statement>;
|
|
161
173
|
commitAsync(): Promise<void>;
|
|
@@ -167,14 +179,14 @@ export interface Statement {
|
|
|
167
179
|
close(callback?: SimpleCallback): void;
|
|
168
180
|
drop(callback?: SimpleCallback): void;
|
|
169
181
|
release(callback?: SimpleCallback): void;
|
|
170
|
-
execute(transaction: Transaction, params:
|
|
182
|
+
execute(transaction: Transaction, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
|
|
171
183
|
fetch(transaction: Transaction, count: number, callback: QueryCallback): void;
|
|
172
184
|
fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
|
|
173
185
|
fetchAll(transaction: Transaction, callback: QueryCallback): void;
|
|
174
186
|
/** Execute this prepared statement once per row (Firebird 4.0+ batch API). */
|
|
175
|
-
executeBatch(transaction: Transaction, rows:
|
|
176
|
-
executeAsync(transaction: Transaction, params?:
|
|
177
|
-
executeBatchAsync(transaction: Transaction, rows:
|
|
187
|
+
executeBatch(transaction: Transaction, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
|
|
188
|
+
executeAsync(transaction: Transaction, params?: QueryParams, options?: QueryOptions): Promise<any>;
|
|
189
|
+
executeBatchAsync(transaction: Transaction, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
|
|
178
190
|
fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
|
|
179
191
|
fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
|
|
180
192
|
fetchAllAsync(transaction: Transaction): Promise<any>;
|
|
@@ -209,6 +221,16 @@ export interface Options {
|
|
|
209
221
|
blobReadChunkSize?: number;
|
|
210
222
|
wireCrypt?: number;
|
|
211
223
|
wireCompression?: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Enable named placeholders: SQL may use `:name` markers and params may
|
|
226
|
+
* be a values-by-name object (`db.query('... WHERE id = :id', { id: 1 })`).
|
|
227
|
+
* Placeholders are rewritten client-side to positional `?` before
|
|
228
|
+
* preparing; positional arrays keep working unchanged. Off by default
|
|
229
|
+
* because `EXECUTE BLOCK` bodies use `:variable` for PSQL references —
|
|
230
|
+
* with this option on, run such statements with positional params or a
|
|
231
|
+
* per-query `namedPlaceholders: false` override.
|
|
232
|
+
*/
|
|
233
|
+
namedPlaceholders?: boolean;
|
|
212
234
|
pluginName?: string;
|
|
213
235
|
parallelWorkers?: number;
|
|
214
236
|
maxInlineBlobSize?: number;
|
package/lib/uri.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/***************************************
|
|
2
|
+
*
|
|
3
|
+
* Connection URI strings
|
|
4
|
+
*
|
|
5
|
+
***************************************/
|
|
6
|
+
import type { Options } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Parse a firebird:// connection URI into an options object.
|
|
9
|
+
*
|
|
10
|
+
* firebird://user:password@host:port/database?option=value&...
|
|
11
|
+
*
|
|
12
|
+
* The database part:
|
|
13
|
+
* firebird://host/employee → alias "employee"
|
|
14
|
+
* firebird://host//var/db/prod.fdb → absolute path "/var/db/prod.fdb"
|
|
15
|
+
* firebird://host/var/db/prod.fdb → "/var/db/prod.fdb" (a database
|
|
16
|
+
* part with slashes is a path —
|
|
17
|
+
* aliases cannot contain "/")
|
|
18
|
+
* firebird://host/C:/db/prod.fdb → Windows path "C:/db/prod.fdb"
|
|
19
|
+
*
|
|
20
|
+
* Credentials and the database path are URL-decoded, so reserved characters
|
|
21
|
+
* can be percent-encoded (e.g. p%40ss for "p@ss"). Query parameters map
|
|
22
|
+
* 1:1 to option keys and are coerced to the option's type (booleans accept
|
|
23
|
+
* 1/true/yes/on). `user` and `password` may be given as query parameters
|
|
24
|
+
* instead of in the authority.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseConnectionUri(uri: string): Options;
|
|
27
|
+
/**
|
|
28
|
+
* Parse a traditional ("old style") Firebird connection string:
|
|
29
|
+
*
|
|
30
|
+
* [host[/port]:]{path | alias}
|
|
31
|
+
*
|
|
32
|
+
* employee → alias "employee" (default host)
|
|
33
|
+
* /var/fb/prod.fdb → local path (default host)
|
|
34
|
+
* C:\fbdata\prod.fdb → Windows path — a single character
|
|
35
|
+
* before ":" is a drive letter, not
|
|
36
|
+
* a host (same rule as Firebird)
|
|
37
|
+
* db.example.com:employee → host + alias
|
|
38
|
+
* db.example.com/3051:/var/fb/prod.fdb → host + port + path
|
|
39
|
+
* myserver:C:\fbdata\prod.fdb → host + Windows path
|
|
40
|
+
* [::1]/3050:employee → IPv6 host + port + alias
|
|
41
|
+
*
|
|
42
|
+
* Unlike firebird:// URIs, traditional strings carry no credentials or
|
|
43
|
+
* options — the driver defaults apply (SYSDBA/masterkey, port 3050).
|
|
44
|
+
* The port must be numeric; /etc/services names are not resolved.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseOldStyleConnectionString(str: string): Options;
|
|
47
|
+
/**
|
|
48
|
+
* Parse any connection string the driver accepts: a firebird:// URI, or a
|
|
49
|
+
* traditional [host[/port]:]database string when there is no scheme.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseConnectionString(str: string): Options;
|
|
52
|
+
/**
|
|
53
|
+
* Accept either an options object or a connection string (firebird:// URI
|
|
54
|
+
* or traditional host[/port]:database) everywhere options are taken.
|
|
55
|
+
* Strings are parsed; objects pass through unchanged.
|
|
56
|
+
*/
|
|
57
|
+
export declare function normalizeOptions<T>(options: T | string): T;
|