node-firebird 2.3.2 → 2.3.4
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 +163 -31
- package/lib/index.d.ts +12 -0
- package/lib/pool.js +10 -1
- package/lib/srp.js +12 -1
- package/lib/wire/connection.js +39 -7
- package/lib/wire/socket.js +13 -1
- package/lib/wire/xsqlvar.js +69 -6
- package/package.json +1 -1
- package/poc/node_modules/.package-lock.json +14 -0
- package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
- package/poc/node_modules/node-firebird/LICENSE +373 -0
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
- package/poc/node_modules/node-firebird/README.md +794 -0
- package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
- package/poc/node_modules/node-firebird/lib/callback.js +38 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
- package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
- package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
- package/poc/node_modules/node-firebird/lib/index.js +128 -0
- package/poc/node_modules/node-firebird/lib/messages.js +162 -0
- package/poc/node_modules/node-firebird/lib/pool.js +108 -0
- package/poc/node_modules/node-firebird/lib/srp.js +299 -0
- package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
- package/poc/node_modules/node-firebird/lib/utils.js +164 -0
- package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
- package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
- package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
- package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
- package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
- package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
- package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
- package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
- package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
- package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
- package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
- package/poc/node_modules/node-firebird/package.json +38 -0
- package/poc/node_modules/node-firebird/vitest.config.js +24 -0
- package/vitest.config.js +3 -1
package/README.md
CHANGED
|
@@ -67,6 +67,8 @@ options.role = null; // default
|
|
|
67
67
|
options.pageSize = 4096; // default when creating database
|
|
68
68
|
options.retryConnectionInterval = 1000; // reconnect interval in case of connection drop
|
|
69
69
|
options.blobAsText = false; // set to true to get blob as text, only affects blob subtype 1
|
|
70
|
+
options.blobChunkSize = 1024; // segment size in bytes used when WRITING blobs (default 1024, max 65535)
|
|
71
|
+
options.blobReadChunkSize = 1024; // buffer size in bytes requested per op_get_segment when READING blobs (default 1024, max 65535)
|
|
70
72
|
options.encoding = 'UTF8'; // default encoding for connection is UTF-8
|
|
71
73
|
options.wireCompression = false; // set to true to enable firebird compression on the wire (works only on FB >= 3 and compression is enabled on server (WireCompression = true in firebird.conf))
|
|
72
74
|
options.wireCrypt = Firebird.WIRE_CRYPT_ENABLE; // default; set to Firebird.WIRE_CRYPT_DISABLE to disable wire encryption (FB >= 3)
|
|
@@ -372,6 +374,34 @@ Firebird.attach(options, (err, db) => {
|
|
|
372
374
|
});
|
|
373
375
|
```
|
|
374
376
|
|
|
377
|
+
### Optimizing BLOB Read/Write Chunk Sizes
|
|
378
|
+
|
|
379
|
+
When working with large blobs (especially over remote or high-latency connections), you can configure the chunk/segment sizes to minimize the number of network round-trips:
|
|
380
|
+
|
|
381
|
+
* **blobChunkSize**: The segment size in bytes used when writing blobs (default: `1024`, maximum: `65535`).
|
|
382
|
+
* **blobReadChunkSize**: The buffer size in bytes requested per segment read operation when reading blobs (default: `1024`, maximum: `65535`).
|
|
383
|
+
|
|
384
|
+
For example, setting `blobReadChunkSize: 65535` requests 64KB segments at a time, resulting in up to 64x fewer network packets/round-trips when reading large blobs.
|
|
385
|
+
|
|
386
|
+
```js
|
|
387
|
+
var options = {
|
|
388
|
+
host: '127.0.0.1',
|
|
389
|
+
port: 3050,
|
|
390
|
+
database: 'database.fdb',
|
|
391
|
+
user: 'SYSDBA',
|
|
392
|
+
password: 'masterkey',
|
|
393
|
+
blobChunkSize: 65535, // Minimize write round-trips
|
|
394
|
+
blobReadChunkSize: 65535 // Minimize read round-trips
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
Firebird.attach(options, function (err, db) {
|
|
398
|
+
if (err) throw err;
|
|
399
|
+
|
|
400
|
+
// Insert/Read operations will use the configured 64KB chunk sizes
|
|
401
|
+
db.detach();
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
375
405
|
### Streaming a big data
|
|
376
406
|
|
|
377
407
|
```js
|
|
@@ -697,15 +727,47 @@ fb.attach(_connection, function (err, svc) {
|
|
|
697
727
|
});
|
|
698
728
|
```
|
|
699
729
|
|
|
700
|
-
###
|
|
730
|
+
### Character Set & Encoding Support
|
|
731
|
+
|
|
732
|
+
Node-Firebird defaults to `UTF-8` for database connections, but fully supports custom client character sets. You can set the connection encoding by specifying `options.encoding` (e.g. `'UTF8'`, `'WIN1252'`, `'ISO8859_1'`, `'LATIN1'`, `'ASCII'`, or `'NONE'`).
|
|
701
733
|
|
|
702
|
-
|
|
734
|
+
Commonly used Firebird character sets are automatically mapped to their corresponding Node.js Buffer encodings:
|
|
703
735
|
|
|
736
|
+
| Firebird Character Set | Node.js Buffer Encoding | Description / Notes |
|
|
737
|
+
| ---------------------- | ----------------------- | ------------------- |
|
|
738
|
+
| `UTF8`, `UNICODE_FSS` | `utf8` | Unicode. Handles character-level truncation automatically based on charset width. |
|
|
739
|
+
| `WIN1252`, `ISO8859_1`, `LATIN1` | `latin1` | 8-bit European encodings. Safely decodes special accented characters. |
|
|
740
|
+
| `ASCII` | `ascii` | 7-bit ASCII. |
|
|
741
|
+
| `NONE` | `latin1` | Raw/unspecified character set. Treated as binary-safe 8-bit characters. |
|
|
742
|
+
|
|
743
|
+
Accented characters and fixed-length `CHAR(N)` column whitespace/truncation are handled automatically matching the connection character set width definitions.
|
|
744
|
+
|
|
745
|
+
#### Custom Charset Connection Example
|
|
704
746
|
```js
|
|
705
|
-
|
|
706
|
-
|
|
747
|
+
var options = {
|
|
748
|
+
host: '127.0.0.1',
|
|
749
|
+
port: 3050,
|
|
750
|
+
database: 'win1252_db.fdb',
|
|
751
|
+
user: 'SYSDBA',
|
|
752
|
+
password: 'masterkey',
|
|
753
|
+
encoding: 'WIN1252' // Automatically maps to 'latin1' under the hood
|
|
754
|
+
};
|
|
707
755
|
|
|
708
|
-
|
|
756
|
+
Firebird.attach(options, function (err, db) {
|
|
757
|
+
if (err) throw err;
|
|
758
|
+
|
|
759
|
+
// Writes 'Ç Ã É Ú Ñ' correctly using Windows-1252 encoding
|
|
760
|
+
db.query('INSERT INTO ACCENTED_TEST (ID, NAME) VALUES (?, ?)', [1, 'Ç Ã É Ú Ñ'], function (err) {
|
|
761
|
+
if (err) throw err;
|
|
762
|
+
|
|
763
|
+
db.query('SELECT NAME FROM ACCENTED_TEST WHERE ID = 1', function (err, rows) {
|
|
764
|
+
if (err) throw err;
|
|
765
|
+
console.log(rows[0].name); // 'Ç Ã É Ú Ñ' (perfectly decoded)
|
|
766
|
+
db.detach();
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
```
|
|
709
771
|
|
|
710
772
|
### Firebird 3.0+ Support
|
|
711
773
|
|
|
@@ -759,17 +821,18 @@ Firebird.attach({
|
|
|
759
821
|
- Empty or undefined values send an empty response to the callback
|
|
760
822
|
- This feature requires Firebird 3.0.1+ (protocol 14/15) for encrypted databases
|
|
761
823
|
|
|
762
|
-
### Firebird 4.0 and 5.0
|
|
824
|
+
### Firebird 4.0 and 5.0 Support
|
|
763
825
|
|
|
764
|
-
Firebird 4 wire protocol (versions 16 and 17) is
|
|
826
|
+
Firebird 4.0+ wire protocol (versions 16 and 17) is fully supported, including:
|
|
765
827
|
|
|
766
|
-
- **Protocol versions 16 and 17** — Full support for Firebird 4.0+ wire
|
|
767
|
-
- **DECFLOAT data types** —
|
|
768
|
-
- **INT128 data type** — Native support for 128-bit integers
|
|
769
|
-
- **
|
|
770
|
-
- **
|
|
828
|
+
- **Protocol versions 16 and 17** — Full support for Firebird 4.0+ and 5.0+ wire protocols (automatic fallback/negotiation).
|
|
829
|
+
- **DECFLOAT data types** — Production-ready support for `DECFLOAT(16)` (Decimal64, 8 bytes) and `DECFLOAT(34)` (Decimal128, 16 bytes) complying with the full IEEE 754-2008 standard using BID (Binary Integer Decimal) encoding. Supports special values such as `NaN`, `+Infinity`, `-Infinity`, `+0`, and `-0`.
|
|
830
|
+
- **INT128 data type** — Native support for 128-bit integers using Node.js `BigInt`.
|
|
831
|
+
- **Statement Timeout** — Support for query and statement-level execution timeouts (Protocol 16+).
|
|
832
|
+
- **Time Zone Support** — Native support for `TIME WITH TIME ZONE` and `TIMESTAMP WITH TIME ZONE` (represented as JavaScript `Date` objects).
|
|
833
|
+
- **Extended metadata identifiers** — Support for identifiers up to 63 characters.
|
|
771
834
|
|
|
772
|
-
The driver will automatically negotiate the best protocol version supported by both the client and server.
|
|
835
|
+
No configuration changes are required for Firebird 4.0 or 5.0 servers. The driver will automatically negotiate the best protocol version supported by both the client and server.
|
|
773
836
|
|
|
774
837
|
```js
|
|
775
838
|
Firebird.attach({
|
|
@@ -783,27 +846,12 @@ Firebird.attach({
|
|
|
783
846
|
|
|
784
847
|
// DECFLOAT and INT128 types are automatically supported
|
|
785
848
|
db.query('SELECT CAST(123.456 AS DECFLOAT(16)) AS df16, CAST(9876543210 AS INT128) AS i128 FROM RDB$DATABASE', function(err, result) {
|
|
786
|
-
console.log(result); // { df16: 123.456, i128:
|
|
849
|
+
console.log(result); // { df16: 123.456, i128: 9876543210n }
|
|
787
850
|
db.detach();
|
|
788
851
|
});
|
|
789
852
|
});
|
|
790
853
|
```
|
|
791
854
|
|
|
792
|
-
**Important Notes:**
|
|
793
|
-
|
|
794
|
-
1. **Statement Timeouts:** Statement timeouts (a Protocol 16 feature) are not yet implemented. This will be added in a future release.
|
|
795
|
-
|
|
796
|
-
2. **DECFLOAT Encoding:** The current DECFLOAT implementation uses a simplified encoding/decoding mechanism and does NOT implement proper IEEE 754 Decimal64/Decimal128 formats. For production use with DECFLOAT types requiring full precision and standards compliance, consider:
|
|
797
|
-
- Using a proper IEEE 754 Decimal library (e.g., `decimal128` npm package)
|
|
798
|
-
- Contributing a full IEEE 754 implementation to this driver
|
|
799
|
-
- Working with DECFLOAT values as strings or Buffers to preserve exact precision
|
|
800
|
-
|
|
801
|
-
For legacy Firebird 4 servers with SRP authentication only, use the following configuration in `firebird.conf`:
|
|
802
|
-
Firebird 4 wire protocol (versions 16 and 17) is partially supported, including:
|
|
803
|
-
- **Time Zone Support**: Native support for `TIME WITH TIME ZONE` and `TIMESTAMP WITH TIME ZONE` (Protocol 16+).
|
|
804
|
-
- **INT128 support**: Native support for 128-bit integers.
|
|
805
|
-
- **Statement Timeout**: Support for statement-level timeouts.
|
|
806
|
-
|
|
807
855
|
#### Using Timezones (FB 4.0+)
|
|
808
856
|
|
|
809
857
|
Columns of type `TIMESTAMP WITH TIME ZONE` and `TIME WITH TIME ZONE` are automatically mapped to JavaScript `Date` objects. Values are read as UTC and represented in the local timezone of the Node.js process.
|
|
@@ -820,8 +868,7 @@ db.query('INSERT INTO FB4_TABLE (TS_TZ_COL) VALUES (?)', [new Date()], function(
|
|
|
820
868
|
});
|
|
821
869
|
```
|
|
822
870
|
|
|
823
|
-
|
|
824
|
-
so you only need the following minimal configuration in `firebird.conf`:
|
|
871
|
+
For legacy Firebird 4 servers with SRP authentication only, use the following configuration in `firebird.conf`:
|
|
825
872
|
|
|
826
873
|
```bash
|
|
827
874
|
AuthServer = Srp256, Srp
|
|
@@ -836,6 +883,91 @@ For more details see:
|
|
|
836
883
|
- [Firebird 5 migration guide — authorization](https://ib-aid.com/download/docs/fb5migrationguide.html#_authorization_from_firebird_2_5_client_libraries)
|
|
837
884
|
|
|
838
885
|
|
|
886
|
+
## Extensive Examples
|
|
887
|
+
|
|
888
|
+
### Firebird 4.0+ DECFLOAT & INT128 Usage
|
|
889
|
+
```js
|
|
890
|
+
Firebird.attach({
|
|
891
|
+
host: '127.0.0.1',
|
|
892
|
+
database: '/path/to/fb4.fdb',
|
|
893
|
+
user: 'SYSDBA',
|
|
894
|
+
password: 'masterkey',
|
|
895
|
+
}, function(err, db) {
|
|
896
|
+
if (err) throw err;
|
|
897
|
+
|
|
898
|
+
// Insert DECFLOAT and INT128 types
|
|
899
|
+
db.query(
|
|
900
|
+
'INSERT INTO INVENTORY (ID, PRICE, SERIAL_NUMBER) VALUES (?, ?, ?)',
|
|
901
|
+
[1n, '12.34567890123456', 987654321098765432109876543210n],
|
|
902
|
+
function(err) {
|
|
903
|
+
if (err) throw err;
|
|
904
|
+
|
|
905
|
+
// Select them back
|
|
906
|
+
db.query('SELECT PRICE, SERIAL_NUMBER FROM INVENTORY WHERE ID = 1', function(err, result) {
|
|
907
|
+
if (err) throw err;
|
|
908
|
+
console.log(typeof result[0].price); // 'string' (e.g. '12.34567890123456')
|
|
909
|
+
console.log(typeof result[0].serial_number); // 'bigint' (e.g. 987654321098765432109876543210n)
|
|
910
|
+
db.detach();
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
);
|
|
914
|
+
});
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
### Statement Timeouts (Firebird 4.0+)
|
|
918
|
+
Setting a statement timeout allows the client to automatically abort queries that take too long on the server.
|
|
919
|
+
```js
|
|
920
|
+
Firebird.attach(options, function(err, db) {
|
|
921
|
+
if (err) throw err;
|
|
922
|
+
|
|
923
|
+
// Specify a statement-level execution timeout of 1000ms
|
|
924
|
+
db.query(
|
|
925
|
+
'SELECT * FROM MY_LARGE_TABLE',
|
|
926
|
+
[],
|
|
927
|
+
function(err, result) {
|
|
928
|
+
if (err) {
|
|
929
|
+
if (err.message.includes('timeout')) {
|
|
930
|
+
console.error('Query timed out!');
|
|
931
|
+
} else {
|
|
932
|
+
console.error('Error:', err);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
db.detach();
|
|
936
|
+
},
|
|
937
|
+
{ timeout: 1000 } // timeout option passed to query/execute
|
|
938
|
+
);
|
|
939
|
+
});
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### Advanced Connection Pooling & Life-cycle
|
|
943
|
+
```js
|
|
944
|
+
var pool = Firebird.pool(10, {
|
|
945
|
+
host: '127.0.0.1',
|
|
946
|
+
database: 'db.fdb',
|
|
947
|
+
user: 'SYSDBA',
|
|
948
|
+
password: 'masterkey',
|
|
949
|
+
connectTimeout: 5000 // 5 seconds connect timeout for pool.get()
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// Retrieve a connection
|
|
953
|
+
pool.get(function(err, db) {
|
|
954
|
+
if (err) {
|
|
955
|
+
console.error('Could not get connection from pool:', err);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
db.query('SELECT * FROM TABLE', function(err, result) {
|
|
960
|
+
// Return connection back to the pool
|
|
961
|
+
db.detach();
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// Close all pool connections and reject pending requests
|
|
966
|
+
process.on('SIGTERM', function() {
|
|
967
|
+
pool.destroy();
|
|
968
|
+
});
|
|
969
|
+
```
|
|
970
|
+
|
|
839
971
|
## Contributors
|
|
840
972
|
|
|
841
973
|
- Henri Gourvest, <https://github.com/hgourvest>
|
package/lib/index.d.ts
CHANGED
|
@@ -114,6 +114,18 @@ declare module 'node-firebird' {
|
|
|
114
114
|
retryConnectionInterval?: number;
|
|
115
115
|
encoding?: SupportedCharacterSet;
|
|
116
116
|
blobAsText?: boolean; // only affects for blob subtype 1
|
|
117
|
+
/**
|
|
118
|
+
* Segment size in bytes used when WRITING blobs (op_batch_segments).
|
|
119
|
+
* Default 1024, max 65535. Use 65535 to minimize round-trips on
|
|
120
|
+
* remote/high-latency connections.
|
|
121
|
+
*/
|
|
122
|
+
blobChunkSize?: number;
|
|
123
|
+
/**
|
|
124
|
+
* Buffer size in bytes requested per op_get_segment when READING
|
|
125
|
+
* blobs. Default 1024, max 65535. Use 65535 to minimize round-trips
|
|
126
|
+
* on remote/high-latency connections (~64x fewer round-trips).
|
|
127
|
+
*/
|
|
128
|
+
blobReadChunkSize?: number;
|
|
117
129
|
wireCrypt?: number; // WIRE_CRYPT_DISABLE or WIRE_CRYPT_ENABLE
|
|
118
130
|
wireCompression?: boolean;
|
|
119
131
|
pluginName?: string;
|
package/lib/pool.js
CHANGED
|
@@ -42,9 +42,18 @@ class Pool {
|
|
|
42
42
|
if (!cb)
|
|
43
43
|
return self;
|
|
44
44
|
if (self.pooldb.length) {
|
|
45
|
+
var db = self.pooldb.shift();
|
|
46
|
+
// Discard connections that have been closed or destroyed while idle
|
|
47
|
+
if (db.connection && (db.connection._isClosed || db.connection._isDetach || !db.connection._socket || db.connection._socket.destroyed)) {
|
|
48
|
+
var idx = self.internaldb.indexOf(db);
|
|
49
|
+
if (idx !== -1) self.internaldb.splice(idx, 1);
|
|
50
|
+
self.pending.unshift(cb);
|
|
51
|
+
setImmediate(function () { self.check(); });
|
|
52
|
+
return self;
|
|
53
|
+
}
|
|
45
54
|
// Idle connection available — hand it out immediately.
|
|
46
55
|
self.dbinuse++;
|
|
47
|
-
cb(null,
|
|
56
|
+
cb(null, db);
|
|
48
57
|
} else {
|
|
49
58
|
// No idle connection — create a new one via attach().
|
|
50
59
|
self._creating++;
|
package/lib/srp.js
CHANGED
|
@@ -24,7 +24,11 @@ const PRIME = {
|
|
|
24
24
|
* @param a BigInt Client private key.
|
|
25
25
|
* @returns {{private: BigInt, public: BigInt}}
|
|
26
26
|
*/
|
|
27
|
-
exports.clientSeed = function(a = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) {
|
|
27
|
+
exports.clientSeed = function(a = toBigInt(crypto.randomBytes(SRP_KEY_SIZE)) % PRIME.N) {
|
|
28
|
+
// a must be in [0, N): clientSession() reduces the exponent (a + ux) mod N
|
|
29
|
+
// to match the Firebird engine, but A = g^a is computed from the raw a.
|
|
30
|
+
// When random a >= N (~10% of 1024-bit values), the proof diverges from
|
|
31
|
+
// the server's session key, causing sporadic auth failures.
|
|
28
32
|
var A = modPow(PRIME.g, a, PRIME.N);
|
|
29
33
|
|
|
30
34
|
dump('a', a);
|
|
@@ -127,6 +131,13 @@ function hexPad(hex) {
|
|
|
127
131
|
}
|
|
128
132
|
exports.hexPad = hexPad;
|
|
129
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The SRP prime modulus N (1024-bit, same value used by Firebird).
|
|
136
|
+
* Exported so tests and tooling can assert key-size invariants without
|
|
137
|
+
* duplicating the constant.
|
|
138
|
+
*/
|
|
139
|
+
exports.PRIME_N = PRIME.N;
|
|
140
|
+
|
|
130
141
|
/**
|
|
131
142
|
* Pad key with SRP_KEY_SIZE.
|
|
132
143
|
*
|
package/lib/wire/connection.js
CHANGED
|
@@ -40,6 +40,7 @@ class Connection {
|
|
|
40
40
|
this._isUsed = false;
|
|
41
41
|
this._pooled = options.isPool||false;
|
|
42
42
|
if (options && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
|
|
43
|
+
if (options && options.blobReadChunkSize > 65535) options.blobReadChunkSize = 65535;
|
|
43
44
|
this.options = options;
|
|
44
45
|
this._bind_events(host, port, callback);
|
|
45
46
|
this.error;
|
|
@@ -155,6 +156,7 @@ class Connection {
|
|
|
155
156
|
|
|
156
157
|
while (xdr.pos < xdr.buffer.length) {
|
|
157
158
|
var cb = self._queue[0], pos = xdr.pos;
|
|
159
|
+
var lazySnapshot = cb ? cb.lazy_count : undefined;
|
|
158
160
|
|
|
159
161
|
decodeResponse(xdr, cb, self, self._lowercase_keys, function (err, obj) {
|
|
160
162
|
|
|
@@ -171,8 +173,17 @@ class Connection {
|
|
|
171
173
|
xdr.buffer.length, pos, self._queue.length);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
// Restore lazy_count to its value before this (failed)
|
|
177
|
+
// decode attempt. Forcing lazy_count = 2 here made the
|
|
178
|
+
// decoder expect a second op_response that never arrives
|
|
179
|
+
// whenever a single large response (e.g. op_get_segment
|
|
180
|
+
// with a big buffer) spans multiple TCP packets.
|
|
181
|
+
if (cb) {
|
|
182
|
+
if (lazySnapshot === undefined) {
|
|
183
|
+
delete cb.lazy_count;
|
|
184
|
+
} else {
|
|
185
|
+
cb.lazy_count = lazySnapshot;
|
|
186
|
+
}
|
|
176
187
|
}
|
|
177
188
|
} else {
|
|
178
189
|
// Any other error (truly unknown opcode not handled above).
|
|
@@ -1287,9 +1298,22 @@ class Connection {
|
|
|
1287
1298
|
}
|
|
1288
1299
|
|
|
1289
1300
|
if (ret && ret.data && ret.data.length) {
|
|
1290
|
-
|
|
1301
|
+
// Read blobs sequentially instead of in parallel to avoid
|
|
1302
|
+
// exceeding Firebird's per-connection open-blob-handle limit,
|
|
1303
|
+
// which causes a server-side deadlock when many rows contain
|
|
1304
|
+
// BLOBs and blobAsText is true. See issue #387.
|
|
1305
|
+
const arrBlobFns = ret.arrBlob || [];
|
|
1306
|
+
const readBlobsSequentially = (index, results) => {
|
|
1307
|
+
if (index >= arrBlobFns.length) {
|
|
1308
|
+
return Promise.resolve(results);
|
|
1309
|
+
}
|
|
1310
|
+
return arrBlobFns[index](transaction).then((v) => {
|
|
1311
|
+
results.push(v);
|
|
1312
|
+
return readBlobsSequentially(index + 1, results);
|
|
1313
|
+
});
|
|
1314
|
+
};
|
|
1291
1315
|
|
|
1292
|
-
|
|
1316
|
+
readBlobsSequentially(0, []).then((arrBlob) => {
|
|
1293
1317
|
for (let i = 0; i < arrBlob.length; i++) {
|
|
1294
1318
|
const blob = arrBlob[i];
|
|
1295
1319
|
ret.data[blob.row][blob.column] = blob.value;
|
|
@@ -1354,7 +1378,7 @@ class Connection {
|
|
|
1354
1378
|
msg.pos = 0;
|
|
1355
1379
|
msg.addInt(Const.op_get_segment);
|
|
1356
1380
|
msg.addInt(blob.handle);
|
|
1357
|
-
msg.addInt(1024); // buffer length
|
|
1381
|
+
msg.addInt(this.options.blobReadChunkSize || 1024); // buffer length (max 65535)
|
|
1358
1382
|
msg.addInt(0); // ???
|
|
1359
1383
|
this._queueEvent(callback);
|
|
1360
1384
|
}
|
|
@@ -1718,7 +1742,7 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1718
1742
|
_xdrpos = data.pos;
|
|
1719
1743
|
const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
1720
1744
|
const row = data.frows.length;
|
|
1721
|
-
let value = item.decode(data, lowerV13);
|
|
1745
|
+
let value = item.decode(data, lowerV13, cnx.options);
|
|
1722
1746
|
|
|
1723
1747
|
if (item.type === Const.SQL_BLOB && value !== null) {
|
|
1724
1748
|
if (item.subType === Const.isc_blob_text && cnx.options.blobAsText) {
|
|
@@ -1808,7 +1832,15 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1808
1832
|
|
|
1809
1833
|
if (is_authenticated === 0) {
|
|
1810
1834
|
if (cnx.options.pluginName && cnx.options.pluginName !== accept.pluginName) {
|
|
1811
|
-
|
|
1835
|
+
var errPlugin = new Error('Server don\'t accept plugin : ' + cnx.options.pluginName + ', but support : ' + accept.pluginName);
|
|
1836
|
+
doError(errPlugin, callback);
|
|
1837
|
+
return cb(errPlugin);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1 && !cnx.clientKeys) {
|
|
1841
|
+
var errPlugin = new Error('Server accepted plugin : ' + accept.pluginName + ', but client did not initialize SRP keys');
|
|
1842
|
+
doError(errPlugin, callback);
|
|
1843
|
+
return cb(errPlugin);
|
|
1812
1844
|
}
|
|
1813
1845
|
|
|
1814
1846
|
if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
|
package/lib/wire/socket.js
CHANGED
|
@@ -55,6 +55,7 @@ class Socket {
|
|
|
55
55
|
constructor(port, host) {
|
|
56
56
|
this._socket = net.createConnection(port, host);
|
|
57
57
|
this._socket.setNoDelay(true);
|
|
58
|
+
this._socket.setKeepAlive(true, 60000); // 1 minute delay to detect dead/stale connections
|
|
58
59
|
this.compressor = null;
|
|
59
60
|
this.compressorBuffer = [];
|
|
60
61
|
this.decompressor = null;
|
|
@@ -99,7 +100,14 @@ class Socket {
|
|
|
99
100
|
*/
|
|
100
101
|
write(data, defer = false) {
|
|
101
102
|
if (defer) {
|
|
102
|
-
|
|
103
|
+
// Accumulate deferred packets instead of overwriting. Multiple
|
|
104
|
+
// deferred ops (e.g. op_close_blob followed immediately by
|
|
105
|
+
// op_free_statement) must all be kept until the next flush;
|
|
106
|
+
// overwriting the buffer silently drops packets and desynchronises
|
|
107
|
+
// the request/response queue, causing the connection to hang.
|
|
108
|
+
this.buffer = this.buffer
|
|
109
|
+
? Buffer.concat([this.buffer, Buffer.from(data)])
|
|
110
|
+
: Buffer.from(data);
|
|
103
111
|
return;
|
|
104
112
|
}
|
|
105
113
|
|
|
@@ -167,6 +175,10 @@ class Socket {
|
|
|
167
175
|
return this[field].bind(this);
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
if (typeof target[field] === 'function') {
|
|
179
|
+
return target[field].bind(target);
|
|
180
|
+
}
|
|
181
|
+
|
|
170
182
|
return target[field];
|
|
171
183
|
}
|
|
172
184
|
}
|
package/lib/wire/xsqlvar.js
CHANGED
|
@@ -13,17 +13,79 @@ const
|
|
|
13
13
|
TimeCoeff = 86400000,
|
|
14
14
|
MsPerMinute = 60000;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Maps Firebird character-set names (upper-case) to the Node.js Buffer
|
|
18
|
+
* encoding string used by Buffer.toString() / Buffer.from().
|
|
19
|
+
*
|
|
20
|
+
* Firebird stores CHAR/VARCHAR data in the on-wire character set of the
|
|
21
|
+
* column (or the connection character set for NONE/unspecified columns).
|
|
22
|
+
* We must decode raw bytes with the matching Node.js encoding so that
|
|
23
|
+
* characters outside ASCII are reproduced correctly.
|
|
24
|
+
*
|
|
25
|
+
* Commonly used Firebird charsets not listed here fall back to the
|
|
26
|
+
* connection-level DEFAULT_ENCODING (typically 'utf8').
|
|
27
|
+
*/
|
|
28
|
+
const FirebirdToNodeEncoding = Object.freeze({
|
|
29
|
+
UTF8: 'utf8',
|
|
30
|
+
UNICODE_FSS: 'utf8',
|
|
31
|
+
WIN1252: 'latin1',
|
|
32
|
+
ISO8859_1: 'latin1',
|
|
33
|
+
LATIN1: 'latin1',
|
|
34
|
+
ASCII: 'ascii',
|
|
35
|
+
NONE: 'latin1', // unspecified charset – treat as binary-safe latin1
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const FirebirdCharsetWidths = {
|
|
39
|
+
'UTF8': 4,
|
|
40
|
+
'UNICODE_FSS': 3,
|
|
41
|
+
'SJIS': 2,
|
|
42
|
+
'EUCJ': 2
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function getFirebirdCharsetWidth(charset) {
|
|
46
|
+
if (!charset) return 4;
|
|
47
|
+
const upper = charset.toUpperCase();
|
|
48
|
+
return FirebirdCharsetWidths[upper] || 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the Node.js Buffer encoding to use when decoding text from a
|
|
53
|
+
* Firebird response buffer.
|
|
54
|
+
*
|
|
55
|
+
* @param {object|null} options Connection options object (may be falsy).
|
|
56
|
+
* @returns {string} A Node.js-compatible encoding string.
|
|
57
|
+
*/
|
|
58
|
+
function resolveTextEncoding(options) {
|
|
59
|
+
const encoding = (options && options.encoding)
|
|
60
|
+
? options.encoding.toUpperCase()
|
|
61
|
+
: Const.DEFAULT_ENCODING;
|
|
62
|
+
return FirebirdToNodeEncoding[encoding] || Const.DEFAULT_ENCODING.toLowerCase();
|
|
63
|
+
}
|
|
64
|
+
|
|
16
65
|
//------------------------------------------------------
|
|
17
66
|
|
|
18
67
|
class SQLVarText {
|
|
19
|
-
decode(data, lowerV13) {
|
|
68
|
+
decode(data, lowerV13, options) {
|
|
20
69
|
let ret;
|
|
70
|
+
const textEncoding = resolveTextEncoding(options);
|
|
21
71
|
if (this.subType > 1) {
|
|
22
72
|
// ToDo: with column charset
|
|
23
|
-
ret = data.readText(this.length,
|
|
73
|
+
ret = data.readText(this.length, textEncoding);
|
|
74
|
+
const encoding = options && options.encoding ? options.encoding : 'UTF8';
|
|
75
|
+
const width = getFirebirdCharsetWidth(encoding);
|
|
76
|
+
const charLength = Math.floor(this.length / width);
|
|
77
|
+
if (ret.length > charLength) {
|
|
78
|
+
ret = ret.substring(0, charLength);
|
|
79
|
+
}
|
|
24
80
|
} else if (this.subType === 0) {
|
|
25
81
|
// without charset definition
|
|
26
|
-
ret = data.readText(this.length,
|
|
82
|
+
ret = data.readText(this.length, textEncoding);
|
|
83
|
+
const encoding = options && options.encoding ? options.encoding : 'UTF8';
|
|
84
|
+
const width = getFirebirdCharsetWidth(encoding);
|
|
85
|
+
const charLength = Math.floor(this.length / width);
|
|
86
|
+
if (ret.length > charLength) {
|
|
87
|
+
ret = ret.substring(0, charLength);
|
|
88
|
+
}
|
|
27
89
|
} else {
|
|
28
90
|
ret = data.readBuffer(this.length);
|
|
29
91
|
}
|
|
@@ -49,14 +111,15 @@ class SQLVarNull extends SQLVarText {
|
|
|
49
111
|
//------------------------------------------------------
|
|
50
112
|
|
|
51
113
|
class SQLVarString {
|
|
52
|
-
decode(data, lowerV13) {
|
|
114
|
+
decode(data, lowerV13, options) {
|
|
53
115
|
let ret;
|
|
116
|
+
const textEncoding = resolveTextEncoding(options);
|
|
54
117
|
if (this.subType > 1) {
|
|
55
118
|
// ToDo: with column charset
|
|
56
|
-
ret = data.readString(
|
|
119
|
+
ret = data.readString(textEncoding);
|
|
57
120
|
} else if (this.subType === 0) {
|
|
58
121
|
// without charset definition
|
|
59
|
-
ret = data.readString(
|
|
122
|
+
ret = data.readString(textEncoding);
|
|
60
123
|
} else {
|
|
61
124
|
ret = data.readBuffer();
|
|
62
125
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-firebird-pool-poc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"node_modules/node-firebird": {
|
|
8
|
+
"version": "2.3.1",
|
|
9
|
+
"resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-2.3.1.tgz",
|
|
10
|
+
"integrity": "sha512-3yTwiLHwgtLYRv+aS4frzsQEwHMDXkP5Z1SPM/W103TvMemVRoCIaxPYO7axnUrKhgTfPHGXzqC5umYlQail/w==",
|
|
11
|
+
"license": "MPL-2.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# For most projects, this workflow file will not need changing; you simply need
|
|
2
|
+
# to commit it to your repository.
|
|
3
|
+
#
|
|
4
|
+
# You may wish to alter this file to override the set of languages analyzed,
|
|
5
|
+
# or to provide custom queries or build logic.
|
|
6
|
+
#
|
|
7
|
+
# ******** NOTE ********
|
|
8
|
+
# We have attempted to detect the languages in your repository. Please check
|
|
9
|
+
# the `language` matrix defined below to confirm you have the correct set of
|
|
10
|
+
# supported CodeQL languages.
|
|
11
|
+
#
|
|
12
|
+
name: "CodeQL"
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
branches: [ "master" ]
|
|
17
|
+
pull_request:
|
|
18
|
+
# The branches below must be a subset of the branches above
|
|
19
|
+
branches: [ "master" ]
|
|
20
|
+
schedule:
|
|
21
|
+
- cron: '26 2 * * 3'
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
analyze:
|
|
25
|
+
name: Analyze
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
permissions:
|
|
28
|
+
actions: read
|
|
29
|
+
contents: read
|
|
30
|
+
security-events: write
|
|
31
|
+
|
|
32
|
+
strategy:
|
|
33
|
+
fail-fast: false
|
|
34
|
+
matrix:
|
|
35
|
+
language: [ 'javascript' ]
|
|
36
|
+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
37
|
+
# Use only 'java' to analyze code written in Java, Kotlin or both
|
|
38
|
+
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
|
39
|
+
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
|
40
|
+
|
|
41
|
+
steps:
|
|
42
|
+
- name: Checkout repository
|
|
43
|
+
uses: actions/checkout@v3
|
|
44
|
+
|
|
45
|
+
# Initializes the CodeQL tools for scanning.
|
|
46
|
+
- name: Initialize CodeQL
|
|
47
|
+
uses: github/codeql-action/init@v2
|
|
48
|
+
with:
|
|
49
|
+
languages: ${{ matrix.language }}
|
|
50
|
+
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
51
|
+
# By default, queries listed here will override any specified in a config file.
|
|
52
|
+
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
53
|
+
|
|
54
|
+
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
55
|
+
# queries: security-extended,security-and-quality
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
|
59
|
+
# If this step fails, then you should remove it and run the build manually (see below)
|
|
60
|
+
- name: Autobuild
|
|
61
|
+
uses: github/codeql-action/autobuild@v2
|
|
62
|
+
|
|
63
|
+
# ℹ️ Command-line programs to run using the OS shell.
|
|
64
|
+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
65
|
+
|
|
66
|
+
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
|
67
|
+
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
|
68
|
+
|
|
69
|
+
# - run: |
|
|
70
|
+
# echo "Run, Build Application using script"
|
|
71
|
+
# ./location_of_script_within_repo/buildscript.sh
|
|
72
|
+
|
|
73
|
+
- name: Perform CodeQL Analysis
|
|
74
|
+
uses: github/codeql-action/analyze@v2
|
|
75
|
+
with:
|
|
76
|
+
category: "/language:${{matrix.language}}"
|