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.
Files changed (49) hide show
  1. package/README.md +163 -31
  2. package/lib/index.d.ts +12 -0
  3. package/lib/pool.js +10 -1
  4. package/lib/srp.js +12 -1
  5. package/lib/wire/connection.js +39 -7
  6. package/lib/wire/socket.js +13 -1
  7. package/lib/wire/xsqlvar.js +69 -6
  8. package/package.json +1 -1
  9. package/poc/node_modules/.package-lock.json +14 -0
  10. package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
  11. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
  12. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
  13. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
  14. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
  15. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
  16. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
  17. package/poc/node_modules/node-firebird/LICENSE +373 -0
  18. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
  19. package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
  20. package/poc/node_modules/node-firebird/README.md +794 -0
  21. package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
  22. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
  23. package/poc/node_modules/node-firebird/lib/callback.js +38 -0
  24. package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
  25. package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
  26. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
  27. package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
  28. package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
  29. package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
  30. package/poc/node_modules/node-firebird/lib/index.js +128 -0
  31. package/poc/node_modules/node-firebird/lib/messages.js +162 -0
  32. package/poc/node_modules/node-firebird/lib/pool.js +108 -0
  33. package/poc/node_modules/node-firebird/lib/srp.js +299 -0
  34. package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
  35. package/poc/node_modules/node-firebird/lib/utils.js +164 -0
  36. package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
  37. package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
  38. package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
  39. package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
  40. package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
  41. package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
  42. package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
  43. package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
  44. package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
  45. package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
  46. package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
  47. package/poc/node_modules/node-firebird/package.json +38 -0
  48. package/poc/node_modules/node-firebird/vitest.config.js +24 -0
  49. 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
- ### Charset for database connection is always UTF-8
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
- Node Firebird uses UTF-8 as the default charset. If you want a different one, such as Latin1, you will need to go into the library and modify the default_encoding in the index.js file
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
- const default_encoding = 'latin1';
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
- This is why you should use **Firebird 2.5** server at least.
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 now supported with the following features:
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 protocol
767
- - **DECFLOAT data types** — Support for DECFLOAT(16) and DECFLOAT(34) using IEEE 754 Decimal64 and Decimal128 formats
768
- - **INT128 data type** — Native support for 128-bit integers
769
- - **Extended metadata identifiers** — Support for identifiers up to 63 characters
770
- - **Backward compatibility** — Automatically negotiates the best protocol version with the server
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. No configuration changes are required for Firebird 4.0 servers.
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: '9876543210' }
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
- Srp256 authentication and wire encryption are now supported natively,
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, self.pooldb.shift());
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
  *
@@ -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
- if (self.accept && self.accept.protocolMinimumType === Const.ptype_lazy_send && self._queue.length > 0) {
175
- self._queue[0].lazy_count = 2;
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
- const arrPromise = (ret.arrBlob || []).map((value) => value(transaction));
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
- Promise.all(arrPromise).then((arrBlob) => {
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
- doError(new Error('Server don\'t accept plugin : ' + cnx.options.pluginName + ', but support : ' + accept.pluginName), callback);
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) {
@@ -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
- this.buffer = Buffer.from(data);
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
  }
@@ -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, Const.DEFAULT_ENCODING);
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, Const.DEFAULT_ENCODING);
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(Const.DEFAULT_ENCODING);
119
+ ret = data.readString(textEncoding);
57
120
  } else if (this.subType === 0) {
58
121
  // without charset definition
59
- ret = data.readString(Const.DEFAULT_ENCODING);
122
+ ret = data.readString(textEncoding);
60
123
  } else {
61
124
  ret = data.readBuffer();
62
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
@@ -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,12 @@
1
+ {
2
+ "parserOptions": {
3
+ "ecmaVersion": 6,
4
+ "sourceType": "module",
5
+ "ecmaFeatures": {
6
+ "jsx": true
7
+ }
8
+ },
9
+ "rules": {
10
+ "semi": "error"
11
+ }
12
+ }
@@ -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}}"