node-firebird 1.1.9 → 1.1.10
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/.github/workflows/node.js.yml +6 -4
- package/README.md +32 -3
- package/Roadmap.md +80 -0
- package/lib/index.d.ts +11 -1
- package/lib/wire/connection.js +107 -20
- package/lib/wire/const.js +5 -5
- package/lib/wire/database.js +4 -4
- package/lib/wire/serialize.js +1 -1
- package/lib/wire/socket.js +100 -0
- package/package.json +2 -2
- package/.travis.yml +0 -23
|
@@ -8,7 +8,7 @@ jobs:
|
|
|
8
8
|
|
|
9
9
|
strategy:
|
|
10
10
|
matrix:
|
|
11
|
-
node: [
|
|
11
|
+
node: [20, 22]
|
|
12
12
|
firebird-version: ['v3']
|
|
13
13
|
|
|
14
14
|
steps:
|
|
@@ -17,11 +17,13 @@ jobs:
|
|
|
17
17
|
fetch-depth: 10
|
|
18
18
|
|
|
19
19
|
- name: Setup FirebirdSQL container
|
|
20
|
-
uses: juarezr/firebirdsql-github-action@
|
|
20
|
+
uses: juarezr/firebirdsql-github-action@v2.0.1
|
|
21
21
|
with:
|
|
22
22
|
version: ${{ matrix.firebird-version }}
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
firebird_root_password: "masterkey"
|
|
24
|
+
firebird_conf: |
|
|
25
|
+
WireCrypt = Enabled
|
|
26
|
+
AuthServer = Legacy_Auth,Srp,Win_Sspi
|
|
25
27
|
|
|
26
28
|
- name: Use Node.js ${{ matrix.node }}
|
|
27
29
|
uses: actions/setup-node@v3
|
package/README.md
CHANGED
|
@@ -68,6 +68,7 @@ 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
70
|
options.encoding = 'UTF8'; // default encoding for connection is UTF-8
|
|
71
|
+
options.wireCompression = false; // set to true for enable firebird compression on the wire (Work only on FB >= 3 and compression is enable on server (WireCompression = true in firebird.conf))
|
|
71
72
|
```
|
|
72
73
|
|
|
73
74
|
### Classic
|
|
@@ -113,7 +114,21 @@ pool.destroy();
|
|
|
113
114
|
- `db.execute(query, [params], function(err, result))` - classic query, returns Array of Array
|
|
114
115
|
- `db.sequentially(query, [params], function(row, index), function(err))` - sequentially query
|
|
115
116
|
- `db.detach(function(err))` detach a database
|
|
116
|
-
- `db.transaction(
|
|
117
|
+
- `db.transaction(options, function(err, transaction))` create transaction
|
|
118
|
+
|
|
119
|
+
### Transaction options
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const options = {
|
|
123
|
+
autoCommit: false,
|
|
124
|
+
autoUndo: true,
|
|
125
|
+
isolation: Firebird.ISOLATION_READ_COMMITTED,
|
|
126
|
+
ignoreLimbo: false,
|
|
127
|
+
readOnly: false,
|
|
128
|
+
wait: true,
|
|
129
|
+
waitTimeout: 0,
|
|
130
|
+
};
|
|
131
|
+
```
|
|
117
132
|
|
|
118
133
|
### Transaction methods
|
|
119
134
|
|
|
@@ -581,7 +596,7 @@ for Firebird 3.0 you need to add the following in firebird.conf according to Fir
|
|
|
581
596
|
|
|
582
597
|
```bash
|
|
583
598
|
AuthServer = Srp, Legacy_Auth
|
|
584
|
-
WireCrypt =
|
|
599
|
+
WireCrypt = Enabled
|
|
585
600
|
UserManager = Legacy_UserManager
|
|
586
601
|
```
|
|
587
602
|
|
|
@@ -591,13 +606,27 @@ for Firebird 4.0 you need to add the following in firebird.conf according to Fir
|
|
|
591
606
|
|
|
592
607
|
```bash
|
|
593
608
|
AuthServer = Srp256, Srp, Legacy_Auth
|
|
594
|
-
WireCrypt =
|
|
609
|
+
WireCrypt = Enabled
|
|
595
610
|
UserManager = Legacy_UserManager
|
|
596
611
|
```
|
|
597
612
|
|
|
598
613
|
Please read also Authorization with Firebird 2.5 client library from Firebird 4 migration guide
|
|
599
614
|
<https://ib-aid.com/download/docs/fb4migrationguide.html#_authorization_with_firebird_2_5_client_library_fbclient_dll>
|
|
600
615
|
|
|
616
|
+
Firebird 5 wire protocol is not supported yet so
|
|
617
|
+
for Firebird 5.0 you need to add the following in firebird.conf according to Firebird release notes
|
|
618
|
+
<https://firebirdsql.org/file/documentation/release_notes/html/en/4_0/rlsnotes40.html#rnfb40-config-srp256>
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
AuthServer = Srp256, Srp, Legacy_Auth
|
|
622
|
+
WireCrypt = Enabled
|
|
623
|
+
UserManager = Legacy_UserManager
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Please read also Authorization with Firebird 2.5 client library from Firebird 5 migration guide
|
|
627
|
+
<https://ib-aid.com/download/docs/fb5migrationguide.html#_authorization_from_firebird_2_5_client_libraries>
|
|
628
|
+
|
|
629
|
+
|
|
601
630
|
## Contributors
|
|
602
631
|
|
|
603
632
|
- Henri Gourvest, <https://github.com/hgourvest>
|
package/Roadmap.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Node-Firebird Roadmap
|
|
2
|
+
|
|
3
|
+
This document outlines the future development direction for the `node-firebird` library. Our primary goals are to modernize the codebase, implement support for the latest Firebird features, and improve the overall developer experience.
|
|
4
|
+
|
|
5
|
+
## Protocol Implementation Status
|
|
6
|
+
|
|
7
|
+
The following table summarizes the current and planned implementation status of the Firebird wire protocol for each major version.
|
|
8
|
+
|
|
9
|
+
| Firebird Version | Protocol Versions | Status |
|
|
10
|
+
| :--- | :--- | :--- |
|
|
11
|
+
| 2.5 | 10, 11, 12, 13 | ✅ Implemented |
|
|
12
|
+
| 3.0 | 14, 15 | ❌ Not Implemented |
|
|
13
|
+
| 4.0 | 16, 17 | ❌ Not Implemented |
|
|
14
|
+
| 5.0 | N/A | ❌ Not Implemented |
|
|
15
|
+
| 6.0 | N/A | ❌ Not Implemented |
|
|
16
|
+
|
|
17
|
+
## Firebird 3 Support
|
|
18
|
+
|
|
19
|
+
Firebird 3 introduced Protocol 13, which brought significant changes focusing on security and performance. While the base protocol is implemented, several key features are still missing. To fully support Firebird 3, we need to implement the following:
|
|
20
|
+
|
|
21
|
+
- **Protocol Versions 14 and 15:** Implement the newer wire protocol versions.
|
|
22
|
+
- **Enhanced Authentication:** Fully support the new authentication mechanisms and plugin architecture.
|
|
23
|
+
- **Wire Protocol Encryption:** Implement support for encrypting all network traffic.
|
|
24
|
+
- **Wire Protocol Compression:** Add support for data compression.
|
|
25
|
+
- **Database Encryption Callback:** Support the new callback mechanism for handling database encryption keys.
|
|
26
|
+
- **Packed (NULL-aware) Row Data:** Implement support for the optimized row format.
|
|
27
|
+
- **Performance Optimizations:**
|
|
28
|
+
- Implement support for the denser data stream and improved prefetch logic.
|
|
29
|
+
- Utilize the new bitmap for transmitting NULL flags to reduce network traffic.
|
|
30
|
+
- **UTF-8 User Identification:** Ensure all user identification is properly handled with UTF-8 encoding.
|
|
31
|
+
|
|
32
|
+
## Firebird 4 Support
|
|
33
|
+
|
|
34
|
+
Firebird 4 introduced Protocol versions 16 and 17, continuing to build upon the foundation of Firebird 3. Key features to implement include:
|
|
35
|
+
|
|
36
|
+
- **Protocol Versions 16 and 17:** Implement the latest protocol versions to support Firebird 4 features.
|
|
37
|
+
|
|
38
|
+
## Firebird 5 Support
|
|
39
|
+
|
|
40
|
+
Firebird 5 introduces a host of new SQL features and performance improvements that will require significant client-side implementation:
|
|
41
|
+
|
|
42
|
+
- **Bidirectional Cursors:** Implement support for scrollable cursors for remote database access.
|
|
43
|
+
- **`RETURNING` Multiple Rows:** Enhance DML operations to support returning multiple rows.
|
|
44
|
+
- **`SKIP LOCKED`:** Add support for the `SKIP LOCKED` clause in `SELECT WITH LOCK`, `UPDATE`, and `DELETE` statements.
|
|
45
|
+
- **New Data Types and Functions:** Add support for new built-in functions and packages.
|
|
46
|
+
|
|
47
|
+
## Firebird 6 and Beyond
|
|
48
|
+
|
|
49
|
+
As Firebird 6 and future versions are released, we will continue to monitor new features and plan for their implementation. Key areas of interest include:
|
|
50
|
+
|
|
51
|
+
- **JSON Support:** Implement client-side support for the new SQL-compliant JSON functions.
|
|
52
|
+
- **Tablespaces:** Add support for tablespaces.
|
|
53
|
+
- **SQL Schemas:** Implement support for SQL schemas.
|
|
54
|
+
|
|
55
|
+
## Codebase Refactoring
|
|
56
|
+
|
|
57
|
+
The current codebase is functional but could be significantly improved by adopting modern JavaScript and TypeScript features.
|
|
58
|
+
|
|
59
|
+
### Modern JavaScript Classes
|
|
60
|
+
|
|
61
|
+
The existing codebase is written in a prototype-based style. We plan to refactor the entire library to use modern JavaScript classes (`class` syntax). This will improve readability, maintainability, and make the code easier to understand for new contributors.
|
|
62
|
+
|
|
63
|
+
**Benefits:**
|
|
64
|
+
|
|
65
|
+
- Improved code structure and organization.
|
|
66
|
+
- Easier to reason about inheritance and object-oriented patterns.
|
|
67
|
+
- Better alignment with modern JavaScript best practices.
|
|
68
|
+
|
|
69
|
+
### TypeScript Rewrite
|
|
70
|
+
|
|
71
|
+
A full rewrite of the library in TypeScript is a long-term goal. This would bring the benefits of static typing, improved developer tooling, and a more robust codebase.
|
|
72
|
+
|
|
73
|
+
**Benefits:**
|
|
74
|
+
|
|
75
|
+
- **Type Safety:** Catch errors at compile-time instead of runtime.
|
|
76
|
+
- **Improved Autocomplete:** Better editor support and developer experience.
|
|
77
|
+
- **Self-documenting Code:** Types make the code easier to understand and use.
|
|
78
|
+
- **Easier Refactoring:** Static analysis makes it easier to refactor code with confidence.
|
|
79
|
+
|
|
80
|
+
We believe these changes will make `node-firebird` a more robust, modern, and developer-friendly library for accessing Firebird databases.
|
package/lib/index.d.ts
CHANGED
|
@@ -31,9 +31,19 @@ declare module 'node-firebird' {
|
|
|
31
31
|
|
|
32
32
|
export type Isolation = number[];
|
|
33
33
|
|
|
34
|
+
export type TransactionOptions = {
|
|
35
|
+
autoCommit?: boolean;
|
|
36
|
+
autoUndo?: boolean;
|
|
37
|
+
isolation?: Isolation;
|
|
38
|
+
ignoreLimbo?: boolean;
|
|
39
|
+
readOnly?: boolean;
|
|
40
|
+
wait?: boolean;
|
|
41
|
+
waitTimeout?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
34
44
|
export interface Database {
|
|
35
45
|
detach(callback?: SimpleCallback): Database;
|
|
36
|
-
transaction(
|
|
46
|
+
transaction(options: TransactionOptions|Isolation|TransactionCallback, callback?: TransactionCallback): Database;
|
|
37
47
|
query(query: string, params: any[], callback: QueryCallback): Database;
|
|
38
48
|
execute(query: string, params: any[], callback: QueryCallback): Database;
|
|
39
49
|
sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, asArray?: boolean): Database;
|
package/lib/wire/connection.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const Events = require('events');
|
|
2
|
-
const net = require('net');
|
|
3
2
|
const os = require('os');
|
|
4
3
|
const path = require('path');
|
|
5
4
|
const BigInt = require('big-integer');
|
|
@@ -15,6 +14,7 @@ const Database = require('./database');
|
|
|
15
14
|
const Statement = require('./statement');
|
|
16
15
|
const Transaction = require('./transaction');
|
|
17
16
|
const {lookupMessages, noop, parseDate} = require('../utils');
|
|
17
|
+
const Socket = require("./socket");
|
|
18
18
|
|
|
19
19
|
/***************************************
|
|
20
20
|
*
|
|
@@ -32,13 +32,14 @@ var Connection = function (host, port, callback, options, db, svc) {
|
|
|
32
32
|
this._detachTimeout;
|
|
33
33
|
this._detachCallback;
|
|
34
34
|
this._detachAuto;
|
|
35
|
-
this._socket =
|
|
35
|
+
this._socket = new Socket(port, host);
|
|
36
36
|
this._pending = [];
|
|
37
37
|
this._isOpened = false;
|
|
38
38
|
this._isClosed = false;
|
|
39
39
|
this._isDetach = false;
|
|
40
40
|
this._isUsed = false;
|
|
41
41
|
this._pooled = options.isPool||false;
|
|
42
|
+
if (options && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
|
|
42
43
|
this.options = options;
|
|
43
44
|
this._bind_events(host, port, callback);
|
|
44
45
|
this.error;
|
|
@@ -368,12 +369,14 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
368
369
|
protocolVersion: data.readInt(),
|
|
369
370
|
protocolArchitecture: data.readInt(),
|
|
370
371
|
protocolMinimumType: data.readInt(),
|
|
372
|
+
compress: false,
|
|
371
373
|
pluginName: '',
|
|
372
374
|
authData: '',
|
|
373
375
|
sessionKey: ''
|
|
374
376
|
};
|
|
375
377
|
|
|
376
|
-
accept.
|
|
378
|
+
accept.compress = (accept.protocolMinimumType & Const.pflag_compress) !== 0;
|
|
379
|
+
accept.protocolMinimumType = accept.protocolMinimumType & Const.ptype_mask;
|
|
377
380
|
//accept.compress = (accept.acceptType & pflag_compress) !== 0; // TODO Handle zlib compression
|
|
378
381
|
if (accept.protocolVersion < 0) {
|
|
379
382
|
accept.protocolVersion = (accept.protocolVersion & Const.FB_PROTOCOL_MASK) | Const.FB_PROTOCOL_FLAG;
|
|
@@ -450,6 +453,10 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
450
453
|
}
|
|
451
454
|
}
|
|
452
455
|
|
|
456
|
+
if (accept.compress) {
|
|
457
|
+
cnx._socket.enableCompression();
|
|
458
|
+
}
|
|
459
|
+
|
|
453
460
|
return cb(undefined, accept);
|
|
454
461
|
case Const.op_cont_auth:
|
|
455
462
|
var d = new BlrReader(data.readArray());
|
|
@@ -577,7 +584,7 @@ Connection.prototype.sendOpContAuth = function(authData, authDataEnc, pluginName
|
|
|
577
584
|
this._socket.write(msg.getData());
|
|
578
585
|
}
|
|
579
586
|
|
|
580
|
-
Connection.prototype._queueEvent = function(callback){
|
|
587
|
+
Connection.prototype._queueEvent = function(callback, defer = false){
|
|
581
588
|
var self = this;
|
|
582
589
|
|
|
583
590
|
if (self._isClosed) {
|
|
@@ -586,8 +593,14 @@ Connection.prototype._queueEvent = function(callback){
|
|
|
586
593
|
return;
|
|
587
594
|
}
|
|
588
595
|
|
|
589
|
-
|
|
590
|
-
|
|
596
|
+
const canDefer = defer && this.accept.protocolVersion >= Const.PROTOCOL_VERSION11;
|
|
597
|
+
|
|
598
|
+
self._socket.write(self._msg.getData(), canDefer);
|
|
599
|
+
if (canDefer && callback) {
|
|
600
|
+
callback();
|
|
601
|
+
} else {
|
|
602
|
+
self._queue.push(callback);
|
|
603
|
+
}
|
|
591
604
|
};
|
|
592
605
|
|
|
593
606
|
Connection.prototype.connect = function (options, callback) {
|
|
@@ -633,7 +646,11 @@ Connection.prototype.connect = function (options, callback) {
|
|
|
633
646
|
msg.addInt(protocol[0]); // Version
|
|
634
647
|
msg.addInt(protocol[1]); // Architecture
|
|
635
648
|
msg.addInt(protocol[2]); // Min type
|
|
636
|
-
|
|
649
|
+
if (protocol[0] >= Const.PROTOCOL_VERSION13 && options.wireCompression) {
|
|
650
|
+
msg.addInt(protocol[3] | Const.pflag_compress); // Max type with compress flag
|
|
651
|
+
} else {
|
|
652
|
+
msg.addInt(protocol[3]); // Max type
|
|
653
|
+
}
|
|
637
654
|
msg.addInt(protocol[4]); // Preference weight
|
|
638
655
|
}
|
|
639
656
|
|
|
@@ -845,14 +862,34 @@ Connection.prototype.throwClosed = function(callback) {
|
|
|
845
862
|
return this;
|
|
846
863
|
};
|
|
847
864
|
|
|
848
|
-
Connection.prototype.startTransaction = function(
|
|
865
|
+
Connection.prototype.startTransaction = function(options, callback) {
|
|
849
866
|
|
|
850
|
-
if (typeof(
|
|
851
|
-
var tmp =
|
|
852
|
-
|
|
867
|
+
if (typeof(options) === 'function') {
|
|
868
|
+
var tmp = options;
|
|
869
|
+
options = callback;
|
|
853
870
|
callback = tmp;
|
|
854
871
|
}
|
|
855
872
|
|
|
873
|
+
// Compatibility
|
|
874
|
+
if (Array.isArray(options)) {
|
|
875
|
+
options = {
|
|
876
|
+
isolation: options,
|
|
877
|
+
readOnly: (options === Const.ISOLATION_READ_COMMITTED_READ_ONLY),
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Default options
|
|
882
|
+
options = Object.assign({
|
|
883
|
+
autoCommit: false,
|
|
884
|
+
autoUndo: true,
|
|
885
|
+
isolation: Const.ISOLATION_READ_COMMITTED,
|
|
886
|
+
ignoreLimbo: false,
|
|
887
|
+
//lock: [],
|
|
888
|
+
readOnly: false,
|
|
889
|
+
wait: true,
|
|
890
|
+
waitTimeout: 0,
|
|
891
|
+
}, options);
|
|
892
|
+
|
|
856
893
|
if (this._isClosed)
|
|
857
894
|
return this.throwClosed(callback);
|
|
858
895
|
|
|
@@ -865,13 +902,44 @@ Connection.prototype.startTransaction = function(isolation, callback) {
|
|
|
865
902
|
blr.pos = 0;
|
|
866
903
|
msg.pos = 0;
|
|
867
904
|
|
|
868
|
-
blr.
|
|
905
|
+
blr.addByte(Const.isc_tpb_version3);
|
|
906
|
+
blr.addBytes(options.isolation);
|
|
907
|
+
blr.addByte(options.readOnly ? Const.isc_tpb_read : Const.isc_tpb_write);
|
|
908
|
+
if (options.wait) {
|
|
909
|
+
blr.addByte(Const.isc_tpb_wait);
|
|
910
|
+
|
|
911
|
+
if (options.waitTimeout) {
|
|
912
|
+
blr.addNumeric(Const.isc_tpb_lock_timeout, options.waitTimeout);
|
|
913
|
+
}
|
|
914
|
+
} else {
|
|
915
|
+
blr.addByte(Const.isc_tpb_nowait);
|
|
916
|
+
}
|
|
917
|
+
if (!options.autoUndo) {
|
|
918
|
+
blr.addByte(Const.isc_tpb_no_auto_undo);
|
|
919
|
+
}
|
|
920
|
+
if (options.autoCommit) {
|
|
921
|
+
blr.addByte(Const.isc_tpb_autocommit);
|
|
922
|
+
}
|
|
923
|
+
if (options.ignoreLimbo) {
|
|
924
|
+
blr.addByte(Const.isc_tpb_ignore_limbo);
|
|
925
|
+
}
|
|
926
|
+
// TODO
|
|
927
|
+
/*if (options.lock.length) {
|
|
928
|
+
for (let table of options.lock) {
|
|
929
|
+
const lockMode = table.write ? Const.isc_tpb_lock_write : Const.isc_tpb_lock_read;
|
|
930
|
+
const lockType = table.protected ? Const.isc_tpb_protected : Const.isc_tpb_shared;
|
|
931
|
+
|
|
932
|
+
blr.addString(lockMode, table.table || table, Const.DEFAULT_ENCODING);
|
|
933
|
+
blr.addByte(lockType);
|
|
934
|
+
}
|
|
935
|
+
}*/
|
|
936
|
+
|
|
869
937
|
msg.addInt(Const.op_transaction);
|
|
870
938
|
msg.addInt(this.dbhandle);
|
|
871
939
|
msg.addBlr(blr);
|
|
872
940
|
callback.response = new Transaction(this);
|
|
873
941
|
|
|
874
|
-
this.db.emit('transaction',
|
|
942
|
+
this.db.emit('transaction', options);
|
|
875
943
|
this._queueEvent(callback);
|
|
876
944
|
};
|
|
877
945
|
|
|
@@ -966,7 +1034,8 @@ Connection.prototype.dropStatement = function (statement, callback) {
|
|
|
966
1034
|
msg.addInt(Const.op_free_statement);
|
|
967
1035
|
msg.addInt(statement.handle);
|
|
968
1036
|
msg.addInt(Const.DSQL_drop);
|
|
969
|
-
|
|
1037
|
+
|
|
1038
|
+
this._queueEvent(callback, true);
|
|
970
1039
|
};
|
|
971
1040
|
|
|
972
1041
|
Connection.prototype.closeStatement = function (statement, callback) {
|
|
@@ -983,7 +1052,7 @@ Connection.prototype.closeStatement = function (statement, callback) {
|
|
|
983
1052
|
msg.addInt(statement.handle);
|
|
984
1053
|
msg.addInt(Const.DSQL_close);
|
|
985
1054
|
|
|
986
|
-
this._queueEvent(callback);
|
|
1055
|
+
this._queueEvent(callback, true);
|
|
987
1056
|
};
|
|
988
1057
|
|
|
989
1058
|
Connection.prototype.allocateAndPrepareStatement = function (transaction, query, plan, callback) {
|
|
@@ -1259,8 +1328,11 @@ Connection.prototype.executeStatement = function(transaction, statement, params,
|
|
|
1259
1328
|
else if (!isStream)
|
|
1260
1329
|
b = Buffer.from(JSON.stringify(value), Const.DEFAULT_ENCODING);
|
|
1261
1330
|
|
|
1331
|
+
// Use configured transfer size or default to 1024
|
|
1332
|
+
var chunkSize = self.options.blobChunkSize || 1024;
|
|
1333
|
+
|
|
1262
1334
|
if (Buffer.isBuffer(b)) {
|
|
1263
|
-
bufferReader(b,
|
|
1335
|
+
bufferReader(b, chunkSize, function (b, next) {
|
|
1264
1336
|
self.batchSegments(blob, b, next);
|
|
1265
1337
|
}, function() {
|
|
1266
1338
|
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
@@ -1273,9 +1345,20 @@ Connection.prototype.executeStatement = function(transaction, statement, params,
|
|
|
1273
1345
|
var isEnd = false;
|
|
1274
1346
|
|
|
1275
1347
|
value.on('data', function(chunk) {
|
|
1348
|
+
// Optimization: If chunk is smaller than transfer size, send directly
|
|
1349
|
+
if (chunk.length <= chunkSize) {
|
|
1350
|
+
self.batchSegments(blob, chunk, function () {
|
|
1351
|
+
if (isEnd && !isReading) {
|
|
1352
|
+
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
1353
|
+
self.closeBlob(blob, callback);
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1276
1359
|
value.pause();
|
|
1277
1360
|
isReading = true;
|
|
1278
|
-
bufferReader(chunk,
|
|
1361
|
+
bufferReader(chunk, chunkSize, function (b, next) {
|
|
1279
1362
|
self.batchSegments(blob, b, next);
|
|
1280
1363
|
}, function() {
|
|
1281
1364
|
isReading = false;
|
|
@@ -1292,8 +1375,12 @@ Connection.prototype.executeStatement = function(transaction, statement, params,
|
|
|
1292
1375
|
isEnd = true;
|
|
1293
1376
|
if (isReading)
|
|
1294
1377
|
return;
|
|
1295
|
-
|
|
1296
|
-
|
|
1378
|
+
// If we are not currently reading (paused), close immediately
|
|
1379
|
+
// If we are reading, the callback in batchSegments/bufferReader will handle closure
|
|
1380
|
+
if (!isReading) {
|
|
1381
|
+
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
1382
|
+
self.closeBlob(blob, callback);
|
|
1383
|
+
}
|
|
1297
1384
|
});
|
|
1298
1385
|
});
|
|
1299
1386
|
}
|
|
@@ -1717,7 +1804,7 @@ Connection.prototype.closeBlob = function(blob, callback) {
|
|
|
1717
1804
|
msg.pos = 0;
|
|
1718
1805
|
msg.addInt(Const.op_close_blob);
|
|
1719
1806
|
msg.addInt(blob.handle);
|
|
1720
|
-
this._queueEvent(callback);
|
|
1807
|
+
this._queueEvent(callback, true);
|
|
1721
1808
|
};
|
|
1722
1809
|
|
|
1723
1810
|
Connection.prototype.getSegment = function(blob, callback) {
|
package/lib/wire/const.js
CHANGED
|
@@ -435,11 +435,11 @@ const tpb = {
|
|
|
435
435
|
};
|
|
436
436
|
|
|
437
437
|
const transactionIsolation = {
|
|
438
|
-
ISOLATION_READ_UNCOMMITTED : [tpb.
|
|
439
|
-
ISOLATION_READ_COMMITTED : [tpb.
|
|
440
|
-
ISOLATION_REPEATABLE_READ : [tpb.
|
|
441
|
-
ISOLATION_SERIALIZABLE : [tpb.
|
|
442
|
-
ISOLATION_READ_COMMITTED_READ_ONLY : [tpb.
|
|
438
|
+
ISOLATION_READ_UNCOMMITTED : [tpb.isc_tpb_read_committed, tpb.isc_tpb_rec_version],
|
|
439
|
+
ISOLATION_READ_COMMITTED : [tpb.isc_tpb_read_committed, tpb.isc_tpb_no_rec_version],
|
|
440
|
+
ISOLATION_REPEATABLE_READ : [tpb.isc_tpb_concurrency],
|
|
441
|
+
ISOLATION_SERIALIZABLE : [tpb.isc_tpb_consistency],
|
|
442
|
+
ISOLATION_READ_COMMITTED_READ_ONLY : [tpb.isc_tpb_read_committed, tpb.isc_tpb_no_rec_version],
|
|
443
443
|
};
|
|
444
444
|
|
|
445
445
|
/*************************/
|
package/lib/wire/database.js
CHANGED
|
@@ -56,12 +56,12 @@ Database.prototype.detach = function(callback, force) {
|
|
|
56
56
|
return self;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
Database.prototype.transaction = function(
|
|
60
|
-
return this.startTransaction(
|
|
59
|
+
Database.prototype.transaction = function(options, callback) {
|
|
60
|
+
return this.startTransaction(options, callback);
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
Database.prototype.startTransaction = function(
|
|
64
|
-
this.connection.startTransaction(
|
|
63
|
+
Database.prototype.startTransaction = function(options, callback) {
|
|
64
|
+
this.connection.startTransaction(options, callback);
|
|
65
65
|
return this;
|
|
66
66
|
};
|
|
67
67
|
|
package/lib/wire/serialize.js
CHANGED
|
@@ -102,7 +102,7 @@ BlrWriter.prototype.addString = function (c, s, encoding) {
|
|
|
102
102
|
};
|
|
103
103
|
|
|
104
104
|
BlrWriter.prototype.addBuffer = function (b) {
|
|
105
|
-
this.
|
|
105
|
+
this.addWord(b.length);
|
|
106
106
|
this.ensure(b.length);
|
|
107
107
|
b.copy(this.buffer, this.pos);
|
|
108
108
|
this.pos += b.length;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const net = require("net");
|
|
2
|
+
const zlib = require("zlib");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Socket proxy.
|
|
6
|
+
*/
|
|
7
|
+
class Socket {
|
|
8
|
+
constructor(port, host) {
|
|
9
|
+
this._socket = net.createConnection(port, host);
|
|
10
|
+
this._socket.setNoDelay(true);
|
|
11
|
+
this.compressor = null;
|
|
12
|
+
this.compressorBuffer = [];
|
|
13
|
+
this.decompressor = null;
|
|
14
|
+
this.decompressorBuffer = [];
|
|
15
|
+
this.buffer = null;
|
|
16
|
+
|
|
17
|
+
return new Proxy(this._socket, this);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Decompress data when receive it if compression is enabled.
|
|
22
|
+
* Override on data event.
|
|
23
|
+
*/
|
|
24
|
+
on(event, cb) {
|
|
25
|
+
if (event === 'data') {
|
|
26
|
+
const mainCb = cb;
|
|
27
|
+
cb = (data) => {
|
|
28
|
+
if (this.compress) {
|
|
29
|
+
this.decompressor.write(data, () => {
|
|
30
|
+
mainCb(Buffer.concat(this.decompressorBuffer));
|
|
31
|
+
this.decompressorBuffer = []; // Reset buffer
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
mainCb(data);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this._socket.on(event, cb);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Compress data before sending to socket if compression is enabled.
|
|
44
|
+
*/
|
|
45
|
+
write(data, defer = false) {
|
|
46
|
+
if (defer) {
|
|
47
|
+
this.buffer = Buffer.from(data);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!defer && this.buffer) {
|
|
52
|
+
data = Buffer.concat([this.buffer, data]);
|
|
53
|
+
this.buffer = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.compress) {
|
|
57
|
+
this.compressor.write(data, () => {
|
|
58
|
+
this._socket.write(Buffer.concat(this.compressorBuffer));
|
|
59
|
+
this.compressorBuffer = []; // Reset buffer
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
this._socket.write(data);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Enable compression/decompression on the fly.
|
|
68
|
+
*/
|
|
69
|
+
enableCompression() {
|
|
70
|
+
this.compress = true;
|
|
71
|
+
|
|
72
|
+
// Create decompressor instance
|
|
73
|
+
this.decompressor = zlib.createInflate();
|
|
74
|
+
this.decompressor.on('data', (inflate) => {
|
|
75
|
+
this.decompressorBuffer.push(inflate);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Create compressor instance
|
|
79
|
+
this.compressor = zlib.createDeflate({
|
|
80
|
+
flush: zlib.constants.Z_FULL_FLUSH,
|
|
81
|
+
finishFlush: zlib.constants.Z_SYNC_FLUSH
|
|
82
|
+
});
|
|
83
|
+
this.compressor.on('data', (deflate) => {
|
|
84
|
+
this.compressorBuffer.push(deflate);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Proxy trap.
|
|
90
|
+
*/
|
|
91
|
+
get(target, field) {
|
|
92
|
+
if (field in this) {
|
|
93
|
+
return this[field].bind(this);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return target[field];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = Socket;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-firebird",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.10",
|
|
4
4
|
"description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"firebird",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"homepage": "https://github.com/hgourvest/node-firebird",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/hgourvest/node-firebird"
|
|
14
|
+
"url": "git+https://github.com/hgourvest/node-firebird.git"
|
|
15
15
|
},
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Henri Gourvest",
|
package/.travis.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
language: node_js
|
|
2
|
-
|
|
3
|
-
node_js:
|
|
4
|
-
- lts/*
|
|
5
|
-
- v10
|
|
6
|
-
|
|
7
|
-
notifications:
|
|
8
|
-
email:
|
|
9
|
-
- mapopa@gmail.com
|
|
10
|
-
|
|
11
|
-
env:
|
|
12
|
-
- FIREBIRD_DATA="/var/lib/firebird/2.5/data"
|
|
13
|
-
|
|
14
|
-
before_install:
|
|
15
|
-
- sudo apt-get update -qq
|
|
16
|
-
- sudo DEBIAN_FRONTEND=noninteractive apt-get install -qq firebird2.5-superclassic
|
|
17
|
-
- sudo gsec -modify SYSDBA -pw masterkey
|
|
18
|
-
|
|
19
|
-
script:
|
|
20
|
-
- nyc npm test
|
|
21
|
-
|
|
22
|
-
after_script:
|
|
23
|
-
- nyc report --reporter=text-lcov | coveralls
|