node-firebird 2.3.4 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +574 -20
- package/lib/callback.d.ts +20 -0
- package/lib/callback.js +6 -16
- package/lib/firebird.msg.json +1370 -1370
- package/lib/gdscodes.d.ts +1519 -1520
- package/lib/gdscodes.js +5 -5
- package/lib/ieee754-decimal.d.ts +38 -0
- package/lib/ieee754-decimal.js +71 -118
- package/lib/index.d.ts +37 -338
- package/lib/index.js +97 -86
- package/lib/messages.d.ts +6 -0
- package/lib/messages.js +135 -162
- package/lib/pool.d.ts +23 -0
- package/lib/pool.js +34 -47
- package/lib/srp.d.ts +51 -0
- package/lib/srp.js +62 -72
- package/lib/types.d.ts +375 -0
- package/lib/types.js +22 -0
- package/lib/unix-crypt.d.ts +1 -0
- package/lib/unix-crypt.js +126 -152
- package/lib/utils.d.ts +16 -0
- package/lib/utils.js +24 -48
- package/lib/wire/connection.d.ts +100 -0
- package/lib/wire/connection.js +1029 -1009
- package/lib/wire/const.d.ts +567 -0
- package/lib/wire/const.js +620 -599
- package/lib/wire/database.d.ts +57 -0
- package/lib/wire/database.js +111 -108
- package/lib/wire/eventConnection.d.ts +16 -0
- package/lib/wire/eventConnection.js +29 -31
- package/lib/wire/fbEventManager.d.ts +45 -0
- package/lib/wire/fbEventManager.js +38 -57
- package/lib/wire/serialize.d.ts +97 -0
- package/lib/wire/serialize.js +42 -141
- package/lib/wire/service.d.ts +66 -0
- package/lib/wire/service.js +374 -401
- package/lib/wire/socket.d.ts +59 -0
- package/lib/wire/socket.js +79 -40
- package/lib/wire/statement.d.ts +25 -0
- package/lib/wire/statement.js +13 -9
- package/lib/wire/transaction.d.ts +21 -0
- package/lib/wire/transaction.js +33 -61
- package/lib/wire/xsqlvar.d.ts +175 -0
- package/lib/wire/xsqlvar.js +169 -269
- package/package.json +12 -4
- package/src/callback.ts +55 -0
- package/{poc/node_modules/node-firebird/lib/gdscodes.js → src/gdscodes.ts} +3 -3
- package/{poc/node_modules/node-firebird/lib/ieee754-decimal.js → src/ieee754-decimal.ts} +7 -14
- package/src/index.ts +157 -0
- package/{poc/node_modules/node-firebird/lib/messages.js → src/messages.ts} +163 -162
- package/src/pool.ts +209 -0
- package/{poc/node_modules/node-firebird/lib/srp.js → src/srp.ts} +74 -33
- package/src/types.ts +419 -0
- package/{poc/node_modules/node-firebird/lib/unix-crypt.js → src/unix-crypt.ts} +6 -12
- package/{poc/node_modules/node-firebird/lib/utils.js → src/utils.ts} +11 -20
- package/{poc/node_modules/node-firebird/lib/wire/connection.js → src/wire/connection.ts} +611 -129
- package/{poc/node_modules/node-firebird/lib/wire/const.js → src/wire/const.ts} +96 -9
- package/{poc/node_modules/node-firebird/lib/wire/database.js → src/wire/database.ts} +110 -44
- package/{poc/node_modules/node-firebird/lib/wire/eventConnection.js → src/wire/eventConnection.ts} +29 -14
- package/{poc/node_modules/node-firebird/lib/wire/fbEventManager.js → src/wire/fbEventManager.ts} +39 -30
- package/{poc/node_modules/node-firebird/lib/wire/serialize.js → src/wire/serialize.ts} +78 -59
- package/{poc/node_modules/node-firebird/lib/wire/service.js → src/wire/service.ts} +90 -88
- package/src/wire/socket.ts +262 -0
- package/src/wire/statement.ts +71 -0
- package/{poc/node_modules/node-firebird/lib/wire/transaction.js → src/wire/transaction.ts} +25 -19
- package/{poc/node_modules/node-firebird/lib/wire/xsqlvar.js → src/wire/xsqlvar.ts} +244 -143
- package/.eslintrc.json +0 -12
- package/.github/workflows/codeql.yml +0 -76
- package/.github/workflows/node.js.yml +0 -95
- package/BIGINT_MIGRATION.md +0 -374
- package/CI_DEBUGGING_GUIDE.md +0 -148
- package/ENCRYPTION_CALLBACK.md +0 -152
- package/FIREBIRD_LOG_FEATURE.md +0 -145
- package/MINIMAL_CHANGES_SUMMARY.md +0 -136
- package/PR_SUMMARY.md +0 -96
- package/ROADMAP.md +0 -223
- package/SRP_PROTOCOL.md +0 -482
- package/poc/README.md +0 -160
- package/poc/helpers.js +0 -59
- package/poc/node_modules/.package-lock.json +0 -14
- package/poc/node_modules/node-firebird/.eslintrc.json +0 -12
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +0 -76
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +0 -95
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +0 -374
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +0 -148
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +0 -152
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +0 -145
- package/poc/node_modules/node-firebird/LICENSE +0 -373
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +0 -136
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +0 -96
- package/poc/node_modules/node-firebird/README.md +0 -794
- package/poc/node_modules/node-firebird/ROADMAP.md +0 -223
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +0 -482
- package/poc/node_modules/node-firebird/lib/callback.js +0 -38
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +0 -1524
- package/poc/node_modules/node-firebird/lib/index.d.ts +0 -316
- package/poc/node_modules/node-firebird/lib/index.js +0 -128
- package/poc/node_modules/node-firebird/lib/pool.js +0 -108
- package/poc/node_modules/node-firebird/lib/wire/socket.js +0 -175
- package/poc/node_modules/node-firebird/lib/wire/statement.js +0 -48
- package/poc/node_modules/node-firebird/package.json +0 -38
- package/poc/node_modules/node-firebird/vitest.config.js +0 -24
- package/poc/package-lock.json +0 -21
- package/poc/package.json +0 -12
- package/poc/reproduce-fixed.js +0 -150
- package/poc/reproduce.js +0 -133
- package/vitest.config.js +0 -27
- /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg +0 -0
- /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg.json +0 -0
package/README.md
CHANGED
|
@@ -13,20 +13,6 @@
|
|
|
13
13
|
- [Firebird on Twitter](https://twitter.com/firebirdsql/)
|
|
14
14
|
- [Firebird on Facebook](https://www.facebook.com/FirebirdSQL)
|
|
15
15
|
|
|
16
|
-
## Changelog for version v0.2.x
|
|
17
|
-
|
|
18
|
-
- added auto-reconnect
|
|
19
|
-
- added [sequentially selects](https://github.com/hgourvest/node-firebird/wiki/What-is-sequentially-selects)
|
|
20
|
-
- events for connection (attach, detach, row, result, transaction, commit, rollback, error, etc.)
|
|
21
|
-
- performance improvements
|
|
22
|
-
- supports inserting/updating buffers and streams
|
|
23
|
-
- reading blobs (sequentially)
|
|
24
|
-
- pooling
|
|
25
|
-
- `database.detach()` waits for last command
|
|
26
|
-
- better unit-test
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
16
|
- [Firebird documentation](https://firebirdsql.org/en/documentation/)
|
|
31
17
|
- [Firebird limits and data types](https://firebirdsql.org/en/firebird-technical-specifications/)
|
|
32
18
|
|
|
@@ -36,12 +22,65 @@
|
|
|
36
22
|
npm install node-firebird
|
|
37
23
|
```
|
|
38
24
|
|
|
25
|
+
The driver is pure JavaScript at runtime — no native addons and no runtime
|
|
26
|
+
dependencies. Node.js 20 or newer is supported (CI runs on Node 20, 22, 24
|
|
27
|
+
and 26 against Firebird 3, 4, 5 and 6).
|
|
28
|
+
|
|
39
29
|
## Usage
|
|
40
30
|
|
|
41
31
|
```js
|
|
42
32
|
var Firebird = require('node-firebird');
|
|
43
33
|
```
|
|
44
34
|
|
|
35
|
+
TypeScript is fully supported — the driver itself is written in TypeScript and
|
|
36
|
+
ships its own type declarations:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import * as Firebird from 'node-firebird';
|
|
40
|
+
import type { Options, Database } from 'node-firebird';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Developing the driver
|
|
44
|
+
|
|
45
|
+
Since v2.4.0 the driver is written in TypeScript and compiled with the native
|
|
46
|
+
TypeScript 7 compiler (`tsc`). The published package ships both the compiled
|
|
47
|
+
output and the sources.
|
|
48
|
+
|
|
49
|
+
**Requirements**
|
|
50
|
+
|
|
51
|
+
- Node.js >= 20 (CI matrix: 20, 22, 24, 26)
|
|
52
|
+
- npm (TypeScript 7 and all tooling are installed as devDependencies — no
|
|
53
|
+
global installs needed)
|
|
54
|
+
- a Firebird server on `127.0.0.1:3050` with `SYSDBA`/`masterkey` for the
|
|
55
|
+
integration tests (CI tests against Firebird 3, 4, 5 and 6-snapshot); the
|
|
56
|
+
unit tests under `test/unit/` run without a server
|
|
57
|
+
|
|
58
|
+
The quickest way to get a test server is Docker:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
docker run -d --name firebird -p 3050:3050 \
|
|
62
|
+
-e FIREBIRD_ROOT_PASSWORD=masterkey \
|
|
63
|
+
firebirdsql/firebird:5
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Layout**
|
|
67
|
+
|
|
68
|
+
- `src/` — the TypeScript sources; this is what you edit
|
|
69
|
+
- `lib/` — compiled CommonJS + generated `.d.ts` declarations; build output,
|
|
70
|
+
gitignored — never edit it by hand
|
|
71
|
+
- `test/` — vitest suite (integration tests at the top level, server-free
|
|
72
|
+
tests in `test/unit/`)
|
|
73
|
+
|
|
74
|
+
**Workflow**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install # installs deps and builds lib/ via the prepare script
|
|
78
|
+
npm run build # compile src/ -> lib/
|
|
79
|
+
npm run typecheck # type-check sources and tests without emitting
|
|
80
|
+
npm run lint # oxlint
|
|
81
|
+
npm test # build + run the vitest suite (unit + integration)
|
|
82
|
+
```
|
|
83
|
+
|
|
45
84
|
### Methods
|
|
46
85
|
|
|
47
86
|
- `Firebird.escape(value) -> return {String}` - prevent for SQL Injections
|
|
@@ -75,6 +114,12 @@ options.wireCrypt = Firebird.WIRE_CRYPT_ENABLE; // default; set to Firebird.WIRE
|
|
|
75
114
|
options.pluginName = undefined; // optional, auto-negotiated; can be set to Firebird.AUTH_PLUGIN_SRP256, Firebird.AUTH_PLUGIN_SRP, or Firebird.AUTH_PLUGIN_LEGACY
|
|
76
115
|
options.dbCryptConfig = undefined; // optional; database encryption key for encrypted databases. Use 'base64:<value>' for base64-encoded keys or plain text
|
|
77
116
|
options.connectTimeout = 10000; // optional; timeout in ms for a single pool.get() attach operation (default: no timeout)
|
|
117
|
+
options.parallelWorkers = undefined; // optional; request multiple thread workers for maintenance/index tasks (FB >= 5)
|
|
118
|
+
options.maxInlineBlobSize = undefined; // optional; threshold size in bytes for inline blob transmission (default 65535, FB >= 5.0.3)
|
|
119
|
+
options.maxNegotiatedProtocols = 10; // optional; limit maximum protocol versions negotiated (default 10 for compatibility, set to 11 for FB >= 6.0)
|
|
120
|
+
options.defaultSchema = undefined; // optional; sets session CURRENT_SCHEMA at connect time (FB >= 6.0)
|
|
121
|
+
options.searchPath = undefined; // optional; ordered list/array of schemas to resolve unqualified object references (FB >= 6.0)
|
|
122
|
+
options.jsonAsObject = false; // optional; automatically stringify parameters and parse query results that contain JSON (FB >= 6.0)
|
|
78
123
|
```
|
|
79
124
|
|
|
80
125
|
### Classic
|
|
@@ -175,6 +220,10 @@ sequenceDiagram
|
|
|
175
220
|
- `db.sequentially(query, [params], function(row, index), function(err), options)` - sequentially query
|
|
176
221
|
- `db.detach(function(err))` detach a database
|
|
177
222
|
- `db.transaction(options, function(err, transaction))` create transaction
|
|
223
|
+
- `db.createTablespace(name, filePath, function(err, result))` - Create a physical tablespace (FB >= 6.0)
|
|
224
|
+
- `db.alterTablespace(name, filePath, function(err, result))` - Alter an existing tablespace physical location (FB >= 6.0)
|
|
225
|
+
- `db.dropTablespace(name, function(err, result))` - Drop a tablespace (FB >= 6.0)
|
|
226
|
+
- `db.createSchema(schemaName, [tablespaceName], function(err, result))` - Create a schema/namespace, optionally binding it to a tablespace (FB >= 6.0)
|
|
178
227
|
|
|
179
228
|
### Transaction options
|
|
180
229
|
|
|
@@ -235,6 +284,93 @@ Firebird.attach(options, function (err, db) {
|
|
|
235
284
|
});
|
|
236
285
|
```
|
|
237
286
|
|
|
287
|
+
### Tablespaces and Schema Partitioning (Firebird 6.0+)
|
|
288
|
+
|
|
289
|
+
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.
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
Firebird.attach(options, function (err, db) {
|
|
293
|
+
if (err) throw err;
|
|
294
|
+
|
|
295
|
+
// 1. Create a physical tablespace mapping to a physical storage location
|
|
296
|
+
db.createTablespace('FAST_TS', '/ssd/fast_data.ts', function (err, result) {
|
|
297
|
+
if (err) throw err;
|
|
298
|
+
console.log('Tablespace FAST_TS created successfully');
|
|
299
|
+
|
|
300
|
+
// 2. Create a schema namespace and partition it into the FAST_TS tablespace
|
|
301
|
+
db.createSchema('MYSCHEMA', 'FAST_TS', function (err, result) {
|
|
302
|
+
if (err) throw err;
|
|
303
|
+
console.log('Schema MYSCHEMA partitioned to FAST_TS');
|
|
304
|
+
|
|
305
|
+
// 3. Drop tablespace when no longer needed
|
|
306
|
+
// db.dropTablespace('FAST_TS', function (err, result) { ... });
|
|
307
|
+
|
|
308
|
+
db.detach();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Native JSON Data Type Support (Firebird 6.0+)
|
|
315
|
+
|
|
316
|
+
By enabling the `jsonAsObject` connection parameter, the driver will automatically serialize JavaScript objects/arrays passed as query parameters to JSON strings, and automatically parse returned JSON text/BLOB columns back into JavaScript objects/arrays.
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
const options = {
|
|
320
|
+
// ...other connection options
|
|
321
|
+
jsonAsObject: true,
|
|
322
|
+
blobAsText: true // recommended to read text BLOBs as strings
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
Firebird.attach(options, function (err, db) {
|
|
326
|
+
if (err) throw err;
|
|
327
|
+
|
|
328
|
+
const data = { name: 'Alice', age: 30, roles: ['admin', 'user'] };
|
|
329
|
+
|
|
330
|
+
db.query(
|
|
331
|
+
'INSERT INTO USERS (ID, PROFILE_JSON) VALUES (?, ?)',
|
|
332
|
+
[1, data],
|
|
333
|
+
function (err, result) {
|
|
334
|
+
if (err) throw err;
|
|
335
|
+
|
|
336
|
+
db.query(
|
|
337
|
+
'SELECT PROFILE_JSON FROM USERS WHERE ID = ?',
|
|
338
|
+
[1],
|
|
339
|
+
function (err, result) {
|
|
340
|
+
if (err) throw err;
|
|
341
|
+
// PROFILE_JSON is automatically parsed back to a JavaScript object
|
|
342
|
+
console.log(result[0].profile_json); // { name: 'Alice', age: 30, roles: ['admin', 'user'] }
|
|
343
|
+
db.detach();
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### SQL-Standard ROW Type (Firebird 6.0+)
|
|
352
|
+
|
|
353
|
+
Firebird 6.0+ supports the SQL-standard `ROW` type representing composite records / tuples (e.g. `ROW(id INT, name VARCHAR(20))`). Since the database server compiles row value expressions into individual scalar columns/parameters at the wire interface, you can pass individual parameters or tuple arrays natively:
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
Firebird.attach(options, function (err, db) {
|
|
357
|
+
if (err) throw err;
|
|
358
|
+
|
|
359
|
+
// Use a row value expression / tuple comparison
|
|
360
|
+
db.query(
|
|
361
|
+
'SELECT * FROM USERS WHERE (ID, NAME) = (ROW(?, ?))',
|
|
362
|
+
[1, 'Alice'],
|
|
363
|
+
function (err, rows) {
|
|
364
|
+
if (err) throw err;
|
|
365
|
+
console.log(rows);
|
|
366
|
+
db.detach();
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
For PSQL block declarations (triggers, procedures), you can declare and use `ROW`/`RECORD` variables (such as `DECLARE VARIABLE myrow ROW(id INT, name VARCHAR(20))`) directly within the compiled SQL strings executed via `db.query` or `db.execute`.
|
|
373
|
+
|
|
238
374
|
### BLOB (stream)
|
|
239
375
|
|
|
240
376
|
```js
|
|
@@ -404,6 +540,8 @@ Firebird.attach(options, function (err, db) {
|
|
|
404
540
|
|
|
405
541
|
### Streaming a big data
|
|
406
542
|
|
|
543
|
+
`db.query` / `db.execute` buffer the **entire** result set into memory as an array before your callback runs — fine for small/medium results, but a poor fit for big tables or unbounded exports (this is what causes "Sequential heap limit / allocation failed"-style errors on very large result sets). `db.sequentially` / `transaction.sequentially` stream rows one at a time to an `on(row, index)` callback instead: node-firebird never accumulates the rows itself, so memory use stays flat regardless of table size (the `rows` argument passed to the completion callback is always `[]`).
|
|
544
|
+
|
|
407
545
|
```js
|
|
408
546
|
Firebird.attach(options, function (err, db) {
|
|
409
547
|
if (err) throw err;
|
|
@@ -424,6 +562,34 @@ Firebird.attach(options, function (err, db) {
|
|
|
424
562
|
});
|
|
425
563
|
```
|
|
426
564
|
|
|
565
|
+
#### Backpressure
|
|
566
|
+
|
|
567
|
+
Rows are still fetched from the server in batches (200 rows per round-trip) as fast as your `on` callback returns. If you're forwarding each row to something that can fall behind (an HTTP response, a file write stream, a rate-limited API), declare `on` with a third `next` parameter — or return a `Promise` — and node-firebird will wait for you to call it (or for the promise to resolve) before fetching or processing the next row:
|
|
568
|
+
|
|
569
|
+
```js
|
|
570
|
+
db.sequentially(
|
|
571
|
+
'SELECT * FROM BIGTABLE',
|
|
572
|
+
function (row, index, next) {
|
|
573
|
+
// Only ask for the next row once the downstream write has drained.
|
|
574
|
+
if (outputStream.write(JSON.stringify(row) + '\n')) {
|
|
575
|
+
next();
|
|
576
|
+
} else {
|
|
577
|
+
outputStream.once('drain', next);
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
function (err) {
|
|
581
|
+
db.detach();
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
#### Do / Don't
|
|
587
|
+
|
|
588
|
+
- **Do** use `sequentially` for large tables, full-table exports, or any query whose row count you can't bound in advance.
|
|
589
|
+
- **Do** use the 3-arg `on(row, index, next)` form (or return a `Promise` from `on`) when writing rows to something that applies its own backpressure, so unprocessed rows can't pile up faster than they're consumed.
|
|
590
|
+
- **Don't** use `db.query` / `db.execute` for big or unbounded result sets — both build the full array in memory before your callback ever runs.
|
|
591
|
+
- **Don't** assume the 2-arg `on(row, index)` form throttles you — it only guarantees node-firebird itself won't buffer rows; if your handler does async work without waiting on it (e.g. fire-and-forget writes), buffering can still build up on the *consumer* side.
|
|
592
|
+
|
|
427
593
|
### Transactions
|
|
428
594
|
|
|
429
595
|
**Transaction types:**
|
|
@@ -519,9 +685,9 @@ Firebird.attach(options, function (err, db) {
|
|
|
519
685
|
|
|
520
686
|
### Firebird Database Events (POST_EVENT)
|
|
521
687
|
|
|
522
|
-
Firebird database events are **asynchronous** notifications triggered by `POST_EVENT` inside PSQL
|
|
523
|
-
|
|
524
|
-
|
|
688
|
+
Firebird database events are **asynchronous** notifications triggered by `POST_EVENT` inside PSQL
|
|
689
|
+
triggers or stored procedures. They travel over a separate "aux" connection (opened via
|
|
690
|
+
`db.attachEvent()`) and are managed through a `FbEventManager` instance.
|
|
525
691
|
|
|
526
692
|
```js
|
|
527
693
|
Firebird.attach(options, function (err, db) {
|
|
@@ -531,7 +697,8 @@ Firebird.attach(options, function (err, db) {
|
|
|
531
697
|
db.attachEvent(function (err, evtmgr) {
|
|
532
698
|
if (err) throw err;
|
|
533
699
|
|
|
534
|
-
// 2. Subscribe to one or more named events
|
|
700
|
+
// 2. Subscribe to one or more named events (names must match POST_EVENT('name') in your
|
|
701
|
+
// PSQL triggers/procedures). Resolves once op_que_events is acknowledged by the server.
|
|
535
702
|
evtmgr.registerEvent(['MY_EVENT'], function (err) {
|
|
536
703
|
if (err) throw err;
|
|
537
704
|
|
|
@@ -542,10 +709,20 @@ Firebird.attach(options, function (err, db) {
|
|
|
542
709
|
});
|
|
543
710
|
});
|
|
544
711
|
|
|
545
|
-
// 4. Unsubscribe from events
|
|
712
|
+
// 4. Unsubscribe from one or more events. Passing all currently registered names cancels
|
|
713
|
+
// the subscription (sends op_cancel_events); the manager re-subscribes automatically if
|
|
714
|
+
// other event names remain registered.
|
|
546
715
|
// evtmgr.unregisterEvent(['MY_EVENT'], function (err) { ... });
|
|
547
716
|
|
|
548
|
-
// 5.
|
|
717
|
+
// 5. Inspect the current subscription state for debugging: returns
|
|
718
|
+
// { state, hasActiveSubscription, registeredEvents, eventId,
|
|
719
|
+
// isEventConnectionOpen, isDatabaseConnectionClosed }.
|
|
720
|
+
// state is one of 'IDLE' (aux connection open, no active subscription),
|
|
721
|
+
// 'SUBSCRIBED' (op_que_events acknowledged) or 'CLOSED'.
|
|
722
|
+
// const state = evtmgr.getState();
|
|
723
|
+
|
|
724
|
+
// 6. Release the aux connection when done. Cancels any active subscription first, then
|
|
725
|
+
// gracefully closes the aux socket.
|
|
549
726
|
// evtmgr.close(function (err) { ... });
|
|
550
727
|
});
|
|
551
728
|
});
|
|
@@ -939,6 +1116,77 @@ Firebird.attach(options, function(err, db) {
|
|
|
939
1116
|
});
|
|
940
1117
|
```
|
|
941
1118
|
|
|
1119
|
+
### Bidirectional Scrollable Cursors (Firebird 5.0+)
|
|
1120
|
+
Firebird 5.0 introduced native support for scrollable cursors, enabling bi-directional result set traversal on the server side. You can execute a statement with `{ scrollable: true }` and navigate with `statement.fetchScroll()`.
|
|
1121
|
+
|
|
1122
|
+
```js
|
|
1123
|
+
db.transaction(function(err, tx) {
|
|
1124
|
+
tx.newStatement('SELECT ID, VAL FROM MY_TABLE ORDER BY ID', function(err, statement) {
|
|
1125
|
+
if (err) throw err;
|
|
1126
|
+
|
|
1127
|
+
// Execute the statement and request a scrollable cursor on the server
|
|
1128
|
+
statement.execute(tx, [], function(err) {
|
|
1129
|
+
if (err) throw err;
|
|
1130
|
+
|
|
1131
|
+
// 1. Fetch the first row
|
|
1132
|
+
statement.fetchScroll(tx, 'FIRST', 0, 1, function(err, res) {
|
|
1133
|
+
console.log('First:', res.data); // Row 1
|
|
1134
|
+
|
|
1135
|
+
// 2. Fetch the next row
|
|
1136
|
+
statement.fetchScroll(tx, 'NEXT', 0, 1, function(err, res) {
|
|
1137
|
+
console.log('Next:', res.data); // Row 2
|
|
1138
|
+
|
|
1139
|
+
// 3. Fetch the prior row
|
|
1140
|
+
statement.fetchScroll(tx, 'PRIOR', 0, 1, function(err, res) {
|
|
1141
|
+
console.log('Prior:', res.data); // Row 1 again
|
|
1142
|
+
|
|
1143
|
+
// 4. Fetch the absolute 3rd row
|
|
1144
|
+
statement.fetchScroll(tx, 'ABSOLUTE', 3, 1, function(err, res) {
|
|
1145
|
+
console.log('Absolute 3rd:', res.data); // Row 3
|
|
1146
|
+
|
|
1147
|
+
statement.release();
|
|
1148
|
+
tx.commit();
|
|
1149
|
+
db.detach();
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
}, { scrollable: true });
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
Supported directions are: `'NEXT'` (0), `'PRIOR'` (1), `'FIRST'` (2), `'LAST'` (3), `'ABSOLUTE'` (4), and `'RELATIVE'` (5).
|
|
1160
|
+
|
|
1161
|
+
### DML RETURNING Multiple Rows (Firebird 5.0+)
|
|
1162
|
+
In Firebird 5.0, DML statements like `UPDATE`, `DELETE`, and `INSERT ... SELECT` with a `RETURNING` clause can return multiple rows. When executing these statements, the driver receives an array of objects representing all the affected rows:
|
|
1163
|
+
|
|
1164
|
+
```js
|
|
1165
|
+
db.query(
|
|
1166
|
+
'UPDATE MY_TABLE SET VAL = VAL || \'!\' WHERE ID > 1 RETURNING ID, VAL',
|
|
1167
|
+
[],
|
|
1168
|
+
function(err, rows) {
|
|
1169
|
+
if (err) throw err;
|
|
1170
|
+
console.log(rows); // Array of updated rows: [{ id: 2, val: 'two!' }, { id: 3, val: 'three!' }]
|
|
1171
|
+
}
|
|
1172
|
+
);
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
### SKIP LOCKED (Firebird 5.0+)
|
|
1176
|
+
Firebird 5.0 supports the `SKIP LOCKED` clause with `SELECT ... WITH LOCK`, `UPDATE`, and `DELETE` statements. This allows transactions to skip rows currently locked by other transactions instead of waiting or raising lock conflict errors, making it ideal for concurrency queues:
|
|
1177
|
+
|
|
1178
|
+
```js
|
|
1179
|
+
// Selects unlocked rows, skipping any locked by concurrent processes
|
|
1180
|
+
db.query(
|
|
1181
|
+
'SELECT * FROM QUEUE_TASK WHERE STATUS = \'PENDING\' WITH LOCK SKIP LOCKED',
|
|
1182
|
+
[],
|
|
1183
|
+
function(err, result) {
|
|
1184
|
+
if (err) throw err;
|
|
1185
|
+
console.log(result);
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1188
|
+
```
|
|
1189
|
+
|
|
942
1190
|
### Advanced Connection Pooling & Life-cycle
|
|
943
1191
|
```js
|
|
944
1192
|
var pool = Firebird.pool(10, {
|
|
@@ -968,6 +1216,312 @@ process.on('SIGTERM', function() {
|
|
|
968
1216
|
});
|
|
969
1217
|
```
|
|
970
1218
|
|
|
1219
|
+
## Using node-firebird with Express.js
|
|
1220
|
+
|
|
1221
|
+
node-firebird works well with Express, but because the driver is connection/pool based rather than an ORM with automatic connection management, a few request-lifecycle patterns keep connections from leaking under load. This section documents the recommended architecture, referenced from the [roadmap](ROADMAP.md#2-expressjs-support-first-class-integration).
|
|
1222
|
+
|
|
1223
|
+
### Recommended architecture: one pool per app, created at startup
|
|
1224
|
+
|
|
1225
|
+
Create a single `Firebird.pool(...)` when the app boots and reuse it for the lifetime of the process. Do **not** call `Firebird.pool()` or `Firebird.attach()` inside a request handler — that opens a new socket (or an entire new pool) on every request and will exhaust server and database resources under load.
|
|
1226
|
+
|
|
1227
|
+
```js
|
|
1228
|
+
// db.js
|
|
1229
|
+
const Firebird = require('node-firebird');
|
|
1230
|
+
|
|
1231
|
+
const options = {
|
|
1232
|
+
host: '127.0.0.1',
|
|
1233
|
+
database: '/var/lib/firebird/data/app.fdb',
|
|
1234
|
+
user: 'SYSDBA',
|
|
1235
|
+
password: 'masterkey',
|
|
1236
|
+
connectTimeout: 5000,
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
// Size the pool to your expected concurrency, not to the number of requests.
|
|
1240
|
+
const pool = Firebird.pool(10, options);
|
|
1241
|
+
|
|
1242
|
+
module.exports = pool;
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
```js
|
|
1246
|
+
// app.js
|
|
1247
|
+
const express = require('express');
|
|
1248
|
+
const pool = require('./db');
|
|
1249
|
+
|
|
1250
|
+
const app = express();
|
|
1251
|
+
|
|
1252
|
+
// ...routes...
|
|
1253
|
+
|
|
1254
|
+
const server = app.listen(3000);
|
|
1255
|
+
|
|
1256
|
+
// Close the pool (and reject any in-flight pool.get() calls) on shutdown.
|
|
1257
|
+
process.on('SIGTERM', function () {
|
|
1258
|
+
server.close(function () {
|
|
1259
|
+
pool.destroy();
|
|
1260
|
+
});
|
|
1261
|
+
});
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
### Request lifecycle: acquire → query → always release
|
|
1265
|
+
|
|
1266
|
+
Every request that touches the database must release its connection back to the pool exactly once, on every code path: success, query error, or a synchronous throw. There is no native `finally` across async callbacks, so a small helper plays that role:
|
|
1267
|
+
|
|
1268
|
+
```js
|
|
1269
|
+
// withConnection.js
|
|
1270
|
+
function withConnection(pool, handler) {
|
|
1271
|
+
return function (req, res, next) {
|
|
1272
|
+
pool.get(function (err, db) {
|
|
1273
|
+
if (err) return next(err);
|
|
1274
|
+
|
|
1275
|
+
var finished = false;
|
|
1276
|
+
|
|
1277
|
+
// Safe to call more than once; only the first call has any effect.
|
|
1278
|
+
function done(err) {
|
|
1279
|
+
if (finished) return;
|
|
1280
|
+
finished = true;
|
|
1281
|
+
db.detach();
|
|
1282
|
+
if (err) next(err);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
try {
|
|
1286
|
+
handler(db, req, res, done);
|
|
1287
|
+
} catch (err) {
|
|
1288
|
+
done(err);
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
module.exports = withConnection;
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
```js
|
|
1298
|
+
app.get('/users', withConnection(pool, function (db, req, res, done) {
|
|
1299
|
+
db.query('SELECT ID, ALIAS FROM USERS', function (err, rows) {
|
|
1300
|
+
if (err) return done(err);
|
|
1301
|
+
res.json(rows);
|
|
1302
|
+
done();
|
|
1303
|
+
});
|
|
1304
|
+
}));
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
### Transaction middleware: commit on success, rollback on error
|
|
1308
|
+
|
|
1309
|
+
For write endpoints, acquire a connection and start a transaction in middleware, expose it as `req.tx`, and let the route handler decide whether to commit or roll back:
|
|
1310
|
+
|
|
1311
|
+
```js
|
|
1312
|
+
function transactional(pool) {
|
|
1313
|
+
return function (req, res, next) {
|
|
1314
|
+
pool.get(function (err, db) {
|
|
1315
|
+
if (err) return next(err);
|
|
1316
|
+
|
|
1317
|
+
db.transaction(function (err, tx) {
|
|
1318
|
+
if (err) {
|
|
1319
|
+
db.detach();
|
|
1320
|
+
return next(err);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
req.tx = tx;
|
|
1324
|
+
|
|
1325
|
+
// Call on success — commits, releases the connection, then responds.
|
|
1326
|
+
req.commit = function (payload) {
|
|
1327
|
+
tx.commit(function (err) {
|
|
1328
|
+
db.detach();
|
|
1329
|
+
if (err) return next(err);
|
|
1330
|
+
res.json(payload);
|
|
1331
|
+
});
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// Call on failure — rolls back, releases the connection, then
|
|
1335
|
+
// forwards the error to Express's error-handling middleware.
|
|
1336
|
+
req.rollbackOnError = function (err) {
|
|
1337
|
+
tx.rollback(function () {
|
|
1338
|
+
db.detach();
|
|
1339
|
+
next(err);
|
|
1340
|
+
});
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
next();
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
app.post('/orders', transactional(pool), function (req, res, next) {
|
|
1350
|
+
req.tx.query(
|
|
1351
|
+
'INSERT INTO ORDERS (CUSTOMER, TOTAL) VALUES (?, ?) RETURNING ID',
|
|
1352
|
+
[req.body.customer, req.body.total],
|
|
1353
|
+
function (err, result) {
|
|
1354
|
+
if (err) return req.rollbackOnError(err);
|
|
1355
|
+
req.commit(result);
|
|
1356
|
+
}
|
|
1357
|
+
);
|
|
1358
|
+
});
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
### Error handling: map Firebird errors to HTTP status codes
|
|
1362
|
+
|
|
1363
|
+
Firebird errors surface as a plain `Error` with `.message`, `.gdscode`, and `.gdsparams` (see `lib/callback.js`). Map the codes you care about — exported as named constants from `node-firebird/lib/gdscodes` — to HTTP status codes in your error-handling middleware, and log the original error server-side only. Don't forward `err.message` as-is to clients: it can contain SQL text, table/column names, or file paths.
|
|
1364
|
+
|
|
1365
|
+
```js
|
|
1366
|
+
const { GDSCode } = require('node-firebird/lib/gdscodes');
|
|
1367
|
+
|
|
1368
|
+
function mapFirebirdErrorToStatus(err) {
|
|
1369
|
+
switch (err.gdscode) {
|
|
1370
|
+
case GDSCode.UNIQUE_KEY_VIOLATION:
|
|
1371
|
+
return 409; // Conflict
|
|
1372
|
+
case GDSCode.LOCK_CONFLICT:
|
|
1373
|
+
return 423; // Locked
|
|
1374
|
+
default:
|
|
1375
|
+
return 500;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Express error-handling middleware (register last, with 4 arguments).
|
|
1380
|
+
app.use(function (err, req, res, next) {
|
|
1381
|
+
console.error(err); // full detail goes to server logs only
|
|
1382
|
+
|
|
1383
|
+
res.status(mapFirebirdErrorToStatus(err)).json({
|
|
1384
|
+
error: err.gdscode === GDSCode.UNIQUE_KEY_VIOLATION ? 'Duplicate record' : 'Request failed',
|
|
1385
|
+
});
|
|
1386
|
+
});
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
### BLOB streaming: pipe a BLOB column straight to the response
|
|
1390
|
+
|
|
1391
|
+
Blob columns come back from `db.query`/`transaction.query` as functions (see [Reading Blobs](#reading-blobs-asynchronous)). Call the column function to get an `EventEmitter` you can `.pipe()` straight into `res`, and release the connection once streaming ends, errors, *or* the client disconnects early.
|
|
1392
|
+
|
|
1393
|
+
```js
|
|
1394
|
+
app.get('/users/:id/picture', withConnection(pool, function (db, req, res, done) {
|
|
1395
|
+
db.query(
|
|
1396
|
+
'SELECT USERPICTURE FROM USERS WHERE ID = ?',
|
|
1397
|
+
[req.params.id],
|
|
1398
|
+
function (err, rows) {
|
|
1399
|
+
if (err) return done(err);
|
|
1400
|
+
if (!rows.length) {
|
|
1401
|
+
res.sendStatus(404);
|
|
1402
|
+
return done();
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
rows[0].userpicture(function (err, name, blobStream) {
|
|
1406
|
+
if (err) return done(err);
|
|
1407
|
+
|
|
1408
|
+
res.type('application/octet-stream');
|
|
1409
|
+
|
|
1410
|
+
// done() is idempotent — whichever of these fires first releases
|
|
1411
|
+
// the connection; the rest are no-ops.
|
|
1412
|
+
blobStream.once('end', function () { done(); });
|
|
1413
|
+
blobStream.once('error', done);
|
|
1414
|
+
res.once('close', function () { done(); });
|
|
1415
|
+
|
|
1416
|
+
blobStream.pipe(res);
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
);
|
|
1420
|
+
}));
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
## FAQ
|
|
1424
|
+
|
|
1425
|
+
Answers to recurring questions from the [issue tracker](https://github.com/hgourvest/node-firebird/issues).
|
|
1426
|
+
|
|
1427
|
+
#### Can I use aggregate functions like `LIST()`? I get "no database to handle" when I call the result.
|
|
1428
|
+
|
|
1429
|
+
Yes — `LIST()` is plain SQL and needs no special driver support. The error happens because `LIST()` returns a text blob (subtype 1), and blob columns come back from `db.query`/`transaction.query` as **async reader functions** bound to the transaction the query ran in (see [Reading Blobs](#reading-blobs-asynchronous)). Calling that function without a transaction — or with a different one — is what throws "no database to handle".
|
|
1430
|
+
|
|
1431
|
+
The simplest fix is `blobAsText: true`, which returns subtype-1 blobs as plain strings automatically, no callback needed:
|
|
1432
|
+
|
|
1433
|
+
```js
|
|
1434
|
+
const options = { /* ...other options... */ blobAsText: true };
|
|
1435
|
+
|
|
1436
|
+
Firebird.attach(options, function (err, db) {
|
|
1437
|
+
if (err) throw err;
|
|
1438
|
+
db.query('SELECT LIST(RDB$FIELD_NAME) AS COLUMNS FROM RDB$RELATION_FIELDS', function (err, rows) {
|
|
1439
|
+
if (err) throw err;
|
|
1440
|
+
console.log(rows[0].columns); // plain string, not a function
|
|
1441
|
+
db.detach();
|
|
1442
|
+
});
|
|
1443
|
+
});
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
If you need to read it manually instead, pass the **same** transaction the query used:
|
|
1447
|
+
|
|
1448
|
+
```js
|
|
1449
|
+
db.transaction(function (err, tx) {
|
|
1450
|
+
tx.query('SELECT LIST(RDB$FIELD_NAME) AS COLUMNS FROM RDB$RELATION_FIELDS', function (err, rows) {
|
|
1451
|
+
rows[0].columns(tx, function (err, name, e) {
|
|
1452
|
+
let collected = '';
|
|
1453
|
+
e.on('data', (chunk) => (collected += chunk));
|
|
1454
|
+
e.on('end', () => console.log(collected));
|
|
1455
|
+
});
|
|
1456
|
+
});
|
|
1457
|
+
});
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
#### Is the wire protocol version hard-coded?
|
|
1461
|
+
|
|
1462
|
+
No. node-firebird negotiates the highest protocol version both the client and server support, up to `options.maxNegotiatedProtocols` (default `10`, i.e. Protocol 19 — see [Protocol Implementation Status](ROADMAP.md#4-protocol-implementation-status) in the roadmap for the full version table). Raise it if you're on Firebird 6.0 and want to attempt Protocol 20:
|
|
1463
|
+
|
|
1464
|
+
```js
|
|
1465
|
+
options.maxNegotiatedProtocols = 11; // offers Protocol 20 as well as 19
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
The default is capped at 10 (Protocol 19) rather than the full list because Protocol 20 has a known query-preparation hang on some Firebird 6.0 builds — see the "Firebird 6 and Beyond" note in [ROADMAP.md](ROADMAP.md#4-protocol-implementation-status).
|
|
1469
|
+
|
|
1470
|
+
#### BLOB reads/writes are very slow, especially for large files over a remote connection
|
|
1471
|
+
|
|
1472
|
+
This is almost always network round-trips, not the database. By default, blobs are streamed in small (1024-byte) segments; for a multi-megabyte file over a non-local connection that's thousands of round-trips. Raise `blobChunkSize` / `blobReadChunkSize` (up to the protocol max of `65535`) to fetch/send far larger segments per round-trip — see [Optimizing BLOB Read/Write Chunk Sizes](#optimizing-blob-readwrite-chunk-sizes):
|
|
1473
|
+
|
|
1474
|
+
```js
|
|
1475
|
+
options.blobChunkSize = 65535;
|
|
1476
|
+
options.blobReadChunkSize = 65535;
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
If your server and client are on the same host, this won't matter much — the slowdown is latency-bound, not throughput-bound.
|
|
1480
|
+
|
|
1481
|
+
#### How do I use an encoding other than UTF-8 (e.g. WIN1252/Latin1)?
|
|
1482
|
+
|
|
1483
|
+
Set `options.encoding` — no source changes required (see [Character Set & Encoding Support](#character-set--encoding-support) for the full mapping table):
|
|
1484
|
+
|
|
1485
|
+
```js
|
|
1486
|
+
options.encoding = 'WIN1252'; // or 'ISO8859_1', 'LATIN1', 'ASCII', 'NONE'
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
`options.encoding` must match the character set the *database itself* was created with. If you set an encoding that doesn't match the database's charset, Firebird will reject transliteration of characters that don't exist in both charsets with an error like `Cannot transliterate character between character sets` — that's the server protecting you from silent data corruption, not a driver bug. When in doubt, check the database's `RDB$CHARACTER_SETS` / connection charset rather than guessing.
|
|
1490
|
+
|
|
1491
|
+
If your database uses `charset NONE` (no transliteration at all — the server stores whatever bytes it's given) and you need full control over the byte encoding yourself, pass a `Buffer` instead of a string for text-column parameters. node-firebird writes `Buffer` values through unchanged, bypassing Node's UTF-8 string handling entirely — combine with a transliteration library like [`iconv-lite`](https://www.npmjs.com/package/iconv-lite) to produce the bytes:
|
|
1492
|
+
|
|
1493
|
+
```js
|
|
1494
|
+
const iconv = require('iconv-lite');
|
|
1495
|
+
options.encoding = 'NONE';
|
|
1496
|
+
|
|
1497
|
+
db.query(
|
|
1498
|
+
'INSERT INTO NOTES (ID, BODY) VALUES (?, ?)',
|
|
1499
|
+
[1, iconv.encode('Café', 'win1252')], // pre-encoded bytes, written as-is
|
|
1500
|
+
function (err) { /* ... */ }
|
|
1501
|
+
);
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
#### I get "Dynamic SQL Error" (string truncation) using `LIKE` with a pattern longer than the column
|
|
1505
|
+
|
|
1506
|
+
```
|
|
1507
|
+
SELECT * FROM ACTORS WHERE NAME LIKE 'James Wick%' -- NAME is VARCHAR(10), literal is 11 chars
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
On some Firebird server versions, the optimizer infers a literal's type from the column it's compared against and can raise a truncation error at compile time if an inline string literal is longer than the column's declared length — even though the same query works fine as a parameterized query. This is server-side SQL compilation behavior, not something node-firebird controls; it did not reproduce against the Firebird 6.0 server used by this project's own test suite, so it appears to be limited to certain older engine versions/optimizer paths.
|
|
1511
|
+
|
|
1512
|
+
The reliable fix, which also happens to be the general best practice (see [Parametrized Queries](#parametrized-queries)), is to pass the pattern as a parameter instead of inlining it:
|
|
1513
|
+
|
|
1514
|
+
```js
|
|
1515
|
+
// Instead of embedding the literal in the SQL text:
|
|
1516
|
+
db.query("SELECT * FROM ACTORS WHERE NAME LIKE 'James Wick%'", ...);
|
|
1517
|
+
|
|
1518
|
+
// Bind it as a parameter:
|
|
1519
|
+
db.query('SELECT * FROM ACTORS WHERE NAME LIKE ?', ['James Wick%'], function (err, rows) {
|
|
1520
|
+
if (err) throw err;
|
|
1521
|
+
console.log(rows);
|
|
1522
|
+
});
|
|
1523
|
+
```
|
|
1524
|
+
|
|
971
1525
|
## Contributors
|
|
972
1526
|
|
|
973
1527
|
- Henri Gourvest, <https://github.com/hgourvest>
|