node-firebird 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -6
- package/lib/index.d.ts +5 -0
- package/lib/index.js +32 -1
- package/lib/pool.js +1 -1
- package/lib/srp.d.ts +3 -3
- package/lib/types.d.ts +143 -5
- package/lib/uri.js +2 -2
- package/lib/wire/connection.d.ts +92 -59
- package/lib/wire/connection.js +267 -53
- package/lib/wire/const.d.ts +9 -1
- package/lib/wire/const.js +21 -9
- package/lib/wire/database.d.ts +51 -26
- package/lib/wire/database.js +26 -8
- package/lib/wire/eventConnection.js +5 -3
- package/lib/wire/query-stream.d.ts +18 -0
- package/lib/wire/query-stream.js +73 -0
- package/lib/wire/serialize.d.ts +18 -2
- package/lib/wire/serialize.js +7 -0
- package/lib/wire/service.d.ts +42 -0
- package/lib/wire/service.js +145 -0
- package/lib/wire/socket.d.ts +3 -1
- package/lib/wire/socket.js +5 -2
- package/lib/wire/statement.d.ts +31 -19
- package/lib/wire/statement.js +1 -5
- package/lib/wire/transaction.d.ts +30 -18
- package/lib/wire/transaction.js +23 -4
- package/lib/wire/wire-types.d.ts +116 -0
- package/lib/wire/wire-types.js +10 -0
- package/lib/wire/xsqlvar.d.ts +18 -18
- package/package.json +1 -1
- package/src/index.ts +36 -4
- package/src/messages.ts +1 -1
- package/src/pool.ts +1 -1
- package/src/srp.ts +6 -6
- package/src/types.ts +140 -5
- package/src/unix-crypt.ts +9 -9
- package/src/uri.ts +2 -2
- package/src/wire/connection.ts +464 -232
- package/src/wire/const.ts +21 -9
- package/src/wire/database.ts +75 -43
- package/src/wire/eventConnection.ts +8 -5
- package/src/wire/query-stream.ts +80 -0
- package/src/wire/serialize.ts +29 -0
- package/src/wire/service.ts +188 -6
- package/src/wire/socket.ts +17 -8
- package/src/wire/statement.ts +37 -29
- package/src/wire/transaction.ts +57 -32
- package/src/wire/wire-types.ts +127 -0
- package/src/wire/xsqlvar.ts +9 -7
package/lib/wire/connection.js
CHANGED
|
@@ -61,6 +61,112 @@ function parseValueIfJson(value, options) {
|
|
|
61
61
|
}
|
|
62
62
|
return value;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the prepared-statement cache limit from the connection options:
|
|
66
|
+
* `statementCacheSize` (new), or the legacy `cacheQuery`/`maxCachedQuery`
|
|
67
|
+
* pair (which had no eviction; it now gets a bounded LRU). 0 = disabled.
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* Build the isc_dpb_search_path value from the defaultSchema / searchPath
|
|
71
|
+
* options (Firebird 6.0 / protocol 20+). There is no "default schema" DPB
|
|
72
|
+
* tag in Firebird: CURRENT_SCHEMA is simply the first existing schema of
|
|
73
|
+
* the search path, so defaultSchema is implemented by putting it at the
|
|
74
|
+
* front of the list. When only defaultSchema is given, PUBLIC is kept as a
|
|
75
|
+
* fallback so unqualified names outside the new schema still resolve (the
|
|
76
|
+
* server always appends SYSTEM itself). Returns null when neither option
|
|
77
|
+
* is set.
|
|
78
|
+
*/
|
|
79
|
+
function buildSchemaSearchPath(options) {
|
|
80
|
+
var list = [];
|
|
81
|
+
if (options.searchPath) {
|
|
82
|
+
list = Array.isArray(options.searchPath)
|
|
83
|
+
? options.searchPath.slice()
|
|
84
|
+
: String(options.searchPath).split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
var def = options.defaultSchema;
|
|
87
|
+
if (def) {
|
|
88
|
+
if (list.length === 0) {
|
|
89
|
+
// defaultSchema alone: keep PUBLIC as a fallback after it
|
|
90
|
+
list = def === 'PUBLIC' ? ['PUBLIC'] : [def, 'PUBLIC'];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// explicit searchPath: respect it, just move defaultSchema first
|
|
94
|
+
list = [def].concat(list.filter(function (s) { return s !== def; }));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return list.length ? list.join(',') : null;
|
|
98
|
+
}
|
|
99
|
+
function statementCacheLimit(options) {
|
|
100
|
+
const size = options && options.statementCacheSize;
|
|
101
|
+
if (size && size > 0) {
|
|
102
|
+
return Math.floor(size);
|
|
103
|
+
}
|
|
104
|
+
if (options && options.cacheQuery) {
|
|
105
|
+
const legacy = options.maxCachedQuery;
|
|
106
|
+
return legacy && legacy > 0 ? Math.floor(legacy) : 100;
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
const SQL_TYPE_NAMES = {
|
|
111
|
+
[const_1.default.SQL_TEXT]: 'TEXT',
|
|
112
|
+
[const_1.default.SQL_VARYING]: 'VARYING',
|
|
113
|
+
[const_1.default.SQL_SHORT]: 'SHORT',
|
|
114
|
+
[const_1.default.SQL_LONG]: 'LONG',
|
|
115
|
+
[const_1.default.SQL_FLOAT]: 'FLOAT',
|
|
116
|
+
[const_1.default.SQL_DOUBLE]: 'DOUBLE',
|
|
117
|
+
[const_1.default.SQL_D_FLOAT]: 'D_FLOAT',
|
|
118
|
+
[const_1.default.SQL_TIMESTAMP]: 'TIMESTAMP',
|
|
119
|
+
[const_1.default.SQL_BLOB]: 'BLOB',
|
|
120
|
+
[const_1.default.SQL_ARRAY]: 'ARRAY',
|
|
121
|
+
[const_1.default.SQL_QUAD]: 'QUAD',
|
|
122
|
+
[const_1.default.SQL_TYPE_TIME]: 'TIME',
|
|
123
|
+
[const_1.default.SQL_TYPE_DATE]: 'DATE',
|
|
124
|
+
[const_1.default.SQL_INT64]: 'INT64',
|
|
125
|
+
[const_1.default.SQL_INT128]: 'INT128',
|
|
126
|
+
[const_1.default.SQL_TIMESTAMP_TZ]: 'TIMESTAMP_TZ',
|
|
127
|
+
[const_1.default.SQL_TIMESTAMP_TZ_EX]: 'TIMESTAMP_TZ_EX',
|
|
128
|
+
[const_1.default.SQL_TIME_TZ]: 'TIME_TZ',
|
|
129
|
+
[const_1.default.SQL_TIME_TZ_EX]: 'TIME_TZ_EX',
|
|
130
|
+
[const_1.default.SQL_DEC16]: 'DEC16',
|
|
131
|
+
[const_1.default.SQL_DEC34]: 'DEC34',
|
|
132
|
+
[const_1.default.SQL_BOOLEAN]: 'BOOLEAN',
|
|
133
|
+
[const_1.default.SQL_NULL]: 'NULL',
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Run the user's typeCast hook (options.typeCast) for one column value.
|
|
137
|
+
* The hook receives the column metadata and a next() returning the value
|
|
138
|
+
* the driver would produce by default (after blobAsText/jsonAsObject);
|
|
139
|
+
* whatever it returns becomes the value in the row. Rows may be decoded
|
|
140
|
+
* more than once when a response spans TCP packets, so the hook must be
|
|
141
|
+
* a pure function of its inputs.
|
|
142
|
+
*/
|
|
143
|
+
function applyTypeCast(options, meta, defaultValue) {
|
|
144
|
+
const typeCast = options && options.typeCast;
|
|
145
|
+
if (typeof typeCast !== 'function') {
|
|
146
|
+
return defaultValue;
|
|
147
|
+
}
|
|
148
|
+
const column = {
|
|
149
|
+
type: meta.type,
|
|
150
|
+
typeName: SQL_TYPE_NAMES[meta.type] || 'UNKNOWN',
|
|
151
|
+
subType: meta.subType,
|
|
152
|
+
scale: meta.scale,
|
|
153
|
+
length: meta.length,
|
|
154
|
+
field: meta.field,
|
|
155
|
+
relation: meta.relation,
|
|
156
|
+
alias: meta.alias,
|
|
157
|
+
};
|
|
158
|
+
// A hook exception must never escape into the row-decode loop: there it
|
|
159
|
+
// would be mistaken for an incomplete packet and desync the response
|
|
160
|
+
// queue (the same failure mode as issue #341). Fall back to the default
|
|
161
|
+
// value instead and tell the user.
|
|
162
|
+
try {
|
|
163
|
+
return typeCast(column, function () { return defaultValue; });
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
console.warn('[node-firebird] typeCast hook threw for column "%s" (%s): %s — using default value', column.alias || column.field, column.typeName, err && err.message);
|
|
167
|
+
return defaultValue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
64
170
|
/***************************************
|
|
65
171
|
*
|
|
66
172
|
* Connection
|
|
@@ -69,6 +175,8 @@ function parseValueIfJson(value, options) {
|
|
|
69
175
|
class Connection {
|
|
70
176
|
constructor(host, port, callback, options, db, svc) {
|
|
71
177
|
var self = this;
|
|
178
|
+
// db is absent for service-manager connections; the wire core only
|
|
179
|
+
// touches it on database attachments, where it is always set
|
|
72
180
|
this.db = db;
|
|
73
181
|
this.svc = svc;
|
|
74
182
|
this._msg = new serialize_1.XdrWriter(32);
|
|
@@ -77,7 +185,7 @@ class Connection {
|
|
|
77
185
|
this._detachTimeout;
|
|
78
186
|
this._detachCallback;
|
|
79
187
|
this._detachAuto;
|
|
80
|
-
this._socket = new socket_1.default(port, host);
|
|
188
|
+
this._socket = new socket_1.default(port, host, options.enableKeepAlive !== false, options.keepAliveInitialDelay);
|
|
81
189
|
this._pending = [];
|
|
82
190
|
this._isOpened = false;
|
|
83
191
|
this._isClosed = false;
|
|
@@ -92,28 +200,63 @@ class Connection {
|
|
|
92
200
|
options.user = const_1.default.DEFAULT_USER;
|
|
93
201
|
if (options && !options.password)
|
|
94
202
|
options.password = const_1.default.DEFAULT_PASSWORD;
|
|
95
|
-
if (options && options.blobChunkSize > 65535)
|
|
203
|
+
if (options && options.blobChunkSize && options.blobChunkSize > 65535)
|
|
96
204
|
options.blobChunkSize = 65535;
|
|
97
|
-
if (options && options.blobReadChunkSize > 65535)
|
|
205
|
+
if (options && options.blobReadChunkSize && options.blobReadChunkSize > 65535)
|
|
98
206
|
options.blobReadChunkSize = 65535;
|
|
99
207
|
this.options = options;
|
|
100
208
|
this._bind_events(host, port, callback);
|
|
101
209
|
this.error;
|
|
102
210
|
this._retry_connection_id;
|
|
103
211
|
this._retry_connection_interval = options.retryConnectionInterval || 1000;
|
|
104
|
-
this.
|
|
105
|
-
this.
|
|
212
|
+
this._statementCacheSize = statementCacheLimit(options);
|
|
213
|
+
this._statementCache = this._statementCacheSize > 0 ? new Map() : null;
|
|
106
214
|
this._messageFile = options.messageFile || path_1.default.join(__dirname, 'firebird.msg');
|
|
107
215
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Take an idle prepared statement for `query` out of the cache, or null.
|
|
218
|
+
* The statement leaves the cache while in use, so concurrent callers of
|
|
219
|
+
* the same query never share a server-side cursor — they simply prepare
|
|
220
|
+
* a fresh statement and the spare is dropped when released.
|
|
221
|
+
*/
|
|
222
|
+
takeCachedStatement(query) {
|
|
223
|
+
const cache = this._statementCache;
|
|
224
|
+
if (!cache) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const statement = cache.get(query);
|
|
228
|
+
if (!statement) {
|
|
229
|
+
return null;
|
|
113
230
|
}
|
|
231
|
+
cache.delete(query);
|
|
232
|
+
return statement;
|
|
114
233
|
}
|
|
115
|
-
|
|
116
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Return a statement after use. With the statement cache enabled the
|
|
236
|
+
* statement goes back into the cache as most-recently-used (closing its
|
|
237
|
+
* cursor but keeping the prepared handle), evicting the least-recently
|
|
238
|
+
* used statement over the limit. Failed statements, DDL and spares for
|
|
239
|
+
* an already-cached query are dropped instead.
|
|
240
|
+
*/
|
|
241
|
+
releaseStatement(statement, callback) {
|
|
242
|
+
const cache = this._statementCache;
|
|
243
|
+
const cacheable = cache &&
|
|
244
|
+
statement.query &&
|
|
245
|
+
!statement._failed &&
|
|
246
|
+
statement.type !== const_1.default.isc_info_sql_stmt_ddl &&
|
|
247
|
+
!cache.has(statement.query);
|
|
248
|
+
if (!cacheable) {
|
|
249
|
+
this.dropStatement(statement, callback);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
cache.set(statement.query, statement);
|
|
253
|
+
while (cache.size > this._statementCacheSize) {
|
|
254
|
+
const oldestKey = cache.keys().next().value;
|
|
255
|
+
const oldest = cache.get(oldestKey);
|
|
256
|
+
cache.delete(oldestKey);
|
|
257
|
+
this.dropStatement(oldest, undefined);
|
|
258
|
+
}
|
|
259
|
+
this.closeStatement(statement, callback);
|
|
117
260
|
}
|
|
118
261
|
// Reject every request that is still awaiting a server response (e.g. a
|
|
119
262
|
// transaction.commit() sent right before the server went away) instead of
|
|
@@ -147,6 +290,8 @@ class Connection {
|
|
|
147
290
|
self._rejectPending(lostError);
|
|
148
291
|
self._retry_connection_id = setTimeout(function () {
|
|
149
292
|
self._socket.removeAllListeners();
|
|
293
|
+
// transiently null while the replacement Connection is built
|
|
294
|
+
// (restored by the Object.assign(self, ctx) below)
|
|
150
295
|
self._socket = null;
|
|
151
296
|
var ctx = new Connection(host, port, function (err) {
|
|
152
297
|
ctx.connect(self.options, function (err) {
|
|
@@ -401,10 +546,17 @@ class Connection {
|
|
|
401
546
|
msg.addInt(const_1.default.CONNECT_VERSION3);
|
|
402
547
|
msg.addInt(const_1.default.ARCHITECTURE_GENERIC);
|
|
403
548
|
msg.addString(options.database || options.filename, const_1.default.DEFAULT_ENCODING);
|
|
404
|
-
|
|
549
|
+
// Send the full list by default. Servers parse every entry and ignore
|
|
550
|
+
// versions they do not know (verified back to Firebird 2.5), so the
|
|
551
|
+
// list length itself is harmless; the option remains as an escape
|
|
552
|
+
// hatch to cap negotiation at an older protocol.
|
|
553
|
+
var maxProtocols = options.maxNegotiatedProtocols !== undefined ? options.maxNegotiatedProtocols : const_1.default.SUPPORTED_PROTOCOL.length;
|
|
405
554
|
var protocolsToSend = const_1.default.SUPPORTED_PROTOCOL;
|
|
406
555
|
if (protocolsToSend.length > maxProtocols) {
|
|
407
|
-
|
|
556
|
+
// keep the FIRST N entries: the list is ordered oldest→newest, so
|
|
557
|
+
// capping the count caps the newest protocol offered (e.g. 10 =
|
|
558
|
+
// stop at protocol 19, the documented pre-Firebird-6 behavior)
|
|
559
|
+
protocolsToSend = protocolsToSend.slice(0, maxProtocols);
|
|
408
560
|
}
|
|
409
561
|
msg.addInt(protocolsToSend.length); // Count of Protocol version understood count.
|
|
410
562
|
msg.addBlr(this._blr);
|
|
@@ -540,21 +692,9 @@ class Connection {
|
|
|
540
692
|
blr.addNumeric(const_1.default.isc_dpb_max_inline_blob_size, options.maxInlineBlobSize);
|
|
541
693
|
}
|
|
542
694
|
// Firebird 6.0 SQL Schema parameters (Protocol 20+).
|
|
543
|
-
// These DPB tags configure the session's current schema and the
|
|
544
|
-
// schema search path for unqualified object name resolution.
|
|
545
695
|
if (this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION20) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// SET SCHEMA <name> immediately after connecting.
|
|
549
|
-
blr.addString(const_1.default.isc_dpb_default_schema, options.defaultSchema, const_1.default.DEFAULT_ENCODING);
|
|
550
|
-
}
|
|
551
|
-
if (options.searchPath) {
|
|
552
|
-
// Comma-separated ordered schema name list, like PostgreSQL's
|
|
553
|
-
// search_path. Unqualified object references are resolved by
|
|
554
|
-
// scanning schemas in this order.
|
|
555
|
-
const sp = Array.isArray(options.searchPath)
|
|
556
|
-
? options.searchPath.join(',')
|
|
557
|
-
: String(options.searchPath);
|
|
696
|
+
const sp = buildSchemaSearchPath(options);
|
|
697
|
+
if (sp) {
|
|
558
698
|
blr.addString(const_1.default.isc_dpb_search_path, sp, const_1.default.DEFAULT_ENCODING);
|
|
559
699
|
}
|
|
560
700
|
}
|
|
@@ -654,16 +794,17 @@ class Connection {
|
|
|
654
794
|
}
|
|
655
795
|
// Firebird 6.0 SQL Schema parameters (Protocol 20+).
|
|
656
796
|
if (this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION20) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
if (options.searchPath) {
|
|
661
|
-
const sp = Array.isArray(options.searchPath)
|
|
662
|
-
? options.searchPath.join(',')
|
|
663
|
-
: String(options.searchPath);
|
|
797
|
+
const sp = buildSchemaSearchPath(options);
|
|
798
|
+
if (sp) {
|
|
664
799
|
blr.addString(const_1.default.isc_dpb_search_path, sp, const_1.default.DEFAULT_ENCODING);
|
|
665
800
|
}
|
|
666
801
|
}
|
|
802
|
+
if (options.owner) {
|
|
803
|
+
// Firebird 6.0+ (issue #7718): create the database owned by a
|
|
804
|
+
// different user (requires superuser rights). Older servers
|
|
805
|
+
// ignore unknown DPB tags, so this is safe to always send.
|
|
806
|
+
blr.addString(const_1.default.isc_dpb_owner, options.owner, const_1.default.DEFAULT_ENCODING);
|
|
807
|
+
}
|
|
667
808
|
blr.addNumeric(const_1.default.isc_dpb_sql_dialect, 3);
|
|
668
809
|
blr.addNumeric(const_1.default.isc_dpb_force_write, 1);
|
|
669
810
|
blr.addNumeric(const_1.default.isc_dpb_overwrite, 1);
|
|
@@ -707,6 +848,8 @@ class Connection {
|
|
|
707
848
|
callback(err);
|
|
708
849
|
return this;
|
|
709
850
|
}
|
|
851
|
+
/** `options` is a resolved options object, a bare isolation array, or
|
|
852
|
+
* the callback itself when no options are given. */
|
|
710
853
|
startTransaction(options, callback) {
|
|
711
854
|
if (typeof (options) === 'function') {
|
|
712
855
|
var tmp = options;
|
|
@@ -868,7 +1011,6 @@ class Connection {
|
|
|
868
1011
|
mainCallback.response.query = query;
|
|
869
1012
|
self.db.emit('query', query);
|
|
870
1013
|
ret = mainCallback.response;
|
|
871
|
-
self._setcachedquery(query, ret);
|
|
872
1014
|
}
|
|
873
1015
|
if (callback)
|
|
874
1016
|
callback(err, ret);
|
|
@@ -896,6 +1038,12 @@ class Connection {
|
|
|
896
1038
|
msg.addString(query, const_1.default.DEFAULT_ENCODING);
|
|
897
1039
|
msg.addBlr(blr);
|
|
898
1040
|
msg.addInt(65535); // buffer_length
|
|
1041
|
+
if (this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION20) {
|
|
1042
|
+
// p_sqlst_flags (IStatement::PREPARE_* bits, none needed) — the
|
|
1043
|
+
// server blocks reading this field if it is missing, which was
|
|
1044
|
+
// the protocol-20 "prepare hang"
|
|
1045
|
+
msg.addInt(0);
|
|
1046
|
+
}
|
|
899
1047
|
mainCallback.lazy_count += 1;
|
|
900
1048
|
mainCallback.response = new statement_1.default(this);
|
|
901
1049
|
this._queueEvent(mainCallback);
|
|
@@ -915,6 +1063,7 @@ class Connection {
|
|
|
915
1063
|
});
|
|
916
1064
|
}
|
|
917
1065
|
}
|
|
1066
|
+
/** `plan` may be the callback itself when no plan flag is given. */
|
|
918
1067
|
prepareStatement(transaction, statement, query, plan, callback) {
|
|
919
1068
|
if (this._isClosed)
|
|
920
1069
|
return this.throwClosed(callback);
|
|
@@ -937,6 +1086,9 @@ class Connection {
|
|
|
937
1086
|
msg.addString(query, const_1.default.DEFAULT_ENCODING);
|
|
938
1087
|
msg.addBlr(blr);
|
|
939
1088
|
msg.addInt(65535); // buffer_length
|
|
1089
|
+
if (this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION20) {
|
|
1090
|
+
msg.addInt(0); // p_sqlst_flags (see allocateAndPrepareStatement)
|
|
1091
|
+
}
|
|
940
1092
|
var self = this;
|
|
941
1093
|
this._queueEvent(function (err, ret) {
|
|
942
1094
|
if (!err) {
|
|
@@ -944,7 +1096,6 @@ class Connection {
|
|
|
944
1096
|
statement.query = query;
|
|
945
1097
|
self.db.emit('query', query);
|
|
946
1098
|
ret = statement;
|
|
947
|
-
self._setcachedquery(query, ret);
|
|
948
1099
|
}
|
|
949
1100
|
if (callback)
|
|
950
1101
|
callback(err, ret);
|
|
@@ -1002,7 +1153,7 @@ class Connection {
|
|
|
1002
1153
|
}
|
|
1003
1154
|
var self = this;
|
|
1004
1155
|
var encoders = built.encoders;
|
|
1005
|
-
var chunkSize = options.chunkSize > 0 ? options.chunkSize : 500;
|
|
1156
|
+
var chunkSize = options.chunkSize && options.chunkSize > 0 ? options.chunkSize : 500;
|
|
1006
1157
|
var chunkCount = Math.ceil(rows.length / chunkSize);
|
|
1007
1158
|
var failure = null;
|
|
1008
1159
|
var completion = null;
|
|
@@ -1060,6 +1211,7 @@ class Connection {
|
|
|
1060
1211
|
msg.addInt(statement.handle);
|
|
1061
1212
|
msg.addInt(end - start);
|
|
1062
1213
|
for (var i = start; i < end; i++) {
|
|
1214
|
+
// validated as an array of input.length values above
|
|
1063
1215
|
var row = rows[i];
|
|
1064
1216
|
var nullBits = new serialize_1.BitSet();
|
|
1065
1217
|
for (var j = 0; j < input.length; j++) {
|
|
@@ -1131,6 +1283,7 @@ class Connection {
|
|
|
1131
1283
|
}
|
|
1132
1284
|
}
|
|
1133
1285
|
}
|
|
1286
|
+
/** `params` may be the callback itself when the statement has no parameters. */
|
|
1134
1287
|
executeStatement(transaction, statement, params, callback, custom) {
|
|
1135
1288
|
if (this._isClosed)
|
|
1136
1289
|
return this.throwClosed(callback);
|
|
@@ -1442,6 +1595,7 @@ class Connection {
|
|
|
1442
1595
|
callback.statement = statement;
|
|
1443
1596
|
this._queueEvent(callback);
|
|
1444
1597
|
}
|
|
1598
|
+
/** `count` may be the callback itself when no fetch size is given. */
|
|
1445
1599
|
fetch(statement, transaction, count, callback) {
|
|
1446
1600
|
var msg = this._msg;
|
|
1447
1601
|
var blr = this._blr;
|
|
@@ -1545,7 +1699,7 @@ class Connection {
|
|
|
1545
1699
|
readBlobsSequentially(0, []).then((arrBlob) => {
|
|
1546
1700
|
for (let i = 0; i < arrBlob.length; i++) {
|
|
1547
1701
|
const blob = arrBlob[i];
|
|
1548
|
-
ret.data[blob.row][blob.column] = parseValueIfJson(blob.value, statement.connection.options);
|
|
1702
|
+
ret.data[blob.row][blob.column] = applyTypeCast(statement.connection.options, blob.meta || {}, parseValueIfJson(blob.value, statement.connection.options));
|
|
1549
1703
|
}
|
|
1550
1704
|
doSynchronousLoop(ret.data, (row, _i, next) => {
|
|
1551
1705
|
const pos = asStream ? streamIndex++ : (data.push(row) - 1);
|
|
@@ -1912,12 +2066,25 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1912
2066
|
}
|
|
1913
2067
|
case const_1.default.op_fetch_response:
|
|
1914
2068
|
case const_1.default.op_sql_response:
|
|
2069
|
+
// fetch/sql_response entries always carry their statement
|
|
1915
2070
|
var statement = callback.statement;
|
|
1916
2071
|
var output = statement.output;
|
|
1917
2072
|
var custom = statement.options || {};
|
|
1918
2073
|
var isOpFetch = r === const_1.default.op_fetch_response;
|
|
1919
2074
|
var _xdrpos;
|
|
1920
2075
|
statement.nbrowsfetched = statement.nbrowsfetched || 0;
|
|
2076
|
+
// The f* decode state is only meaningful within a single
|
|
2077
|
+
// decode call: incomplete packets are re-decoded from scratch
|
|
2078
|
+
// on a fresh XdrReader (see the 'data' handler). State left by
|
|
2079
|
+
// an earlier packet in the same data event (e.g. fstatus=100 /
|
|
2080
|
+
// fcount=0 from a completed fetch) would make this decode
|
|
2081
|
+
// consume just the opcode and desync every later response.
|
|
2082
|
+
delete data.fstatus;
|
|
2083
|
+
delete data.fcount;
|
|
2084
|
+
delete data.fcolumn;
|
|
2085
|
+
delete data.frow;
|
|
2086
|
+
delete data.frows;
|
|
2087
|
+
delete data.fcols;
|
|
1921
2088
|
if (isOpFetch && data.fop) { // could be set when a packet is not complete
|
|
1922
2089
|
data.readBuffer(68); // ??
|
|
1923
2090
|
op = data.readInt(); // ??
|
|
@@ -1944,6 +2111,13 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1944
2111
|
}
|
|
1945
2112
|
const arrBlob = [];
|
|
1946
2113
|
const lowerV13 = statement.connection.accept.protocolVersion < const_1.default.PROTOCOL_VERSION13;
|
|
2114
|
+
// op_sql_response (op_execute2) is always followed by an
|
|
2115
|
+
// op_response carrying the execute status vector. The row loop
|
|
2116
|
+
// below consumes it after the last row, but with zero rows
|
|
2117
|
+
// (e.g. INSERT ... RETURNING failing on a constraint) it stays
|
|
2118
|
+
// in the buffer, shifting every later response to the wrong
|
|
2119
|
+
// callback and poisoning the connection (issue #341).
|
|
2120
|
+
var sqlResponseTrailerPending = !isOpFetch && !data.fcount;
|
|
1947
2121
|
while (data.fcount && (data.fstatus !== 100)) {
|
|
1948
2122
|
let nullBitSet;
|
|
1949
2123
|
if (!lowerV13) {
|
|
@@ -1954,12 +2128,8 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1954
2128
|
for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
|
|
1955
2129
|
item = output[data.fcolumn];
|
|
1956
2130
|
if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
}
|
|
1960
|
-
else {
|
|
1961
|
-
data.frow[data.fcolumn] = null;
|
|
1962
|
-
}
|
|
2131
|
+
const nullKey = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
2132
|
+
data.frow[nullKey] = applyTypeCast(cnx.options, item, null);
|
|
1963
2133
|
continue;
|
|
1964
2134
|
}
|
|
1965
2135
|
try {
|
|
@@ -1967,16 +2137,23 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1967
2137
|
const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
1968
2138
|
const row = data.frows.length;
|
|
1969
2139
|
let value = item.decode(data, lowerV13, cnx.options);
|
|
2140
|
+
// text blobs resolved by blobAsText run through the
|
|
2141
|
+
// typeCast hook once the text arrives (see fetchAll),
|
|
2142
|
+
// not here where the value is still a pending fetch
|
|
2143
|
+
let pendingTextBlob = false;
|
|
1970
2144
|
if (item.type === const_1.default.SQL_BLOB && value !== null) {
|
|
1971
2145
|
if (item.subType === const_1.default.isc_blob_text && cnx.options.blobAsText) {
|
|
1972
|
-
value = fetch_blob_async_transaction(statement, value, key, row);
|
|
2146
|
+
value = fetch_blob_async_transaction(statement, value, key, row, item);
|
|
1973
2147
|
arrBlob.push(value);
|
|
2148
|
+
pendingTextBlob = true;
|
|
1974
2149
|
}
|
|
1975
2150
|
else {
|
|
1976
2151
|
value = fetch_blob_async(statement, value, key, row);
|
|
1977
2152
|
}
|
|
1978
2153
|
}
|
|
1979
|
-
data.frow[key] =
|
|
2154
|
+
data.frow[key] = pendingTextBlob
|
|
2155
|
+
? value
|
|
2156
|
+
: applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
|
|
1980
2157
|
}
|
|
1981
2158
|
catch (e) {
|
|
1982
2159
|
// uncomplete packet read
|
|
@@ -2023,6 +2200,16 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2023
2200
|
}
|
|
2024
2201
|
statement.nbrowsfetched++;
|
|
2025
2202
|
}
|
|
2203
|
+
if (sqlResponseTrailerPending) {
|
|
2204
|
+
op = data.readInt();
|
|
2205
|
+
if (op === const_1.default.op_response) {
|
|
2206
|
+
response = {};
|
|
2207
|
+
parseOpResponse(data, response);
|
|
2208
|
+
if (response.status) {
|
|
2209
|
+
return cb(null, response);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2026
2213
|
// ToDo: emit "result" with blob subtype string decoded
|
|
2027
2214
|
statement.connection.db.emit('result', data.frows, arrBlob);
|
|
2028
2215
|
return cb(null, { data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob });
|
|
@@ -2357,6 +2544,17 @@ function readStatusVector(data) {
|
|
|
2357
2544
|
result.sqlcode = n;
|
|
2358
2545
|
}
|
|
2359
2546
|
break;
|
|
2547
|
+
case const_1.default.isc_arg_warning:
|
|
2548
|
+
// A warning attached to a SUCCESS vector (e.g. "parallel
|
|
2549
|
+
// workers value capped"). Keep it out of `status` so the
|
|
2550
|
+
// operation is not mistaken for a failure; later string/
|
|
2551
|
+
// number items attach to the warning entry.
|
|
2552
|
+
var wnum = data.readInt();
|
|
2553
|
+
item = { gdscode: wnum };
|
|
2554
|
+
if (wnum) {
|
|
2555
|
+
(result.warnings = result.warnings || []).push(item);
|
|
2556
|
+
}
|
|
2557
|
+
break;
|
|
2360
2558
|
default:
|
|
2361
2559
|
throw new Error('Unexpected status vector item: ' + op);
|
|
2362
2560
|
}
|
|
@@ -2417,13 +2615,25 @@ function parseOpResponse(data, response, cb) {
|
|
|
2417
2615
|
response.sqlcode = num;
|
|
2418
2616
|
}
|
|
2419
2617
|
break;
|
|
2618
|
+
case const_1.default.isc_arg_warning:
|
|
2619
|
+
// A warning attached to a SUCCESS response (e.g. Firebird's
|
|
2620
|
+
// "parallel workers value capped" on attach). Keep it out of
|
|
2621
|
+
// `status` so the response is not mistaken for an error;
|
|
2622
|
+
// later string/number items attach to the warning entry.
|
|
2623
|
+
num = data.readInt();
|
|
2624
|
+
item = { gdscode: num };
|
|
2625
|
+
if (num) {
|
|
2626
|
+
(response.warnings = response.warnings || []).push(item);
|
|
2627
|
+
}
|
|
2628
|
+
break;
|
|
2420
2629
|
default:
|
|
2630
|
+
// Stop parsing: continuing the loop after an unknown item
|
|
2631
|
+
// re-read the same bytes forever (the caller resets the
|
|
2632
|
+
// reader position when the error is delivered).
|
|
2421
2633
|
if (cb) {
|
|
2422
|
-
cb(new Error('Unexpected: ' + op));
|
|
2423
|
-
}
|
|
2424
|
-
else {
|
|
2425
|
-
throw new Error('Unexpected: ' + op);
|
|
2634
|
+
return cb(new Error('Unexpected: ' + op));
|
|
2426
2635
|
}
|
|
2636
|
+
throw new Error('Unexpected: ' + op);
|
|
2427
2637
|
}
|
|
2428
2638
|
}
|
|
2429
2639
|
}
|
|
@@ -2460,6 +2670,8 @@ function describe(buff, statement) {
|
|
|
2460
2670
|
case const_1.default.isc_info_sql_describe_end:
|
|
2461
2671
|
break;
|
|
2462
2672
|
case const_1.default.isc_info_sql_sqlda_seq:
|
|
2673
|
+
// describe output always encodes the sequence as a
|
|
2674
|
+
// 1/2/4-byte int, so readInt cannot return undefined
|
|
2463
2675
|
var num = br.readInt();
|
|
2464
2676
|
break;
|
|
2465
2677
|
case const_1.default.isc_info_sql_type:
|
|
@@ -2535,6 +2747,8 @@ function describe(buff, statement) {
|
|
|
2535
2747
|
default:
|
|
2536
2748
|
throw new Error('Unexpected');
|
|
2537
2749
|
}
|
|
2750
|
+
// isc_info_sql_sqlda_seq always precedes the type
|
|
2751
|
+
// item in the describe stream, so num is set here
|
|
2538
2752
|
parameters[num - 1] = param;
|
|
2539
2753
|
param.type = type;
|
|
2540
2754
|
param.nullable = Boolean(param.type & 1);
|
|
@@ -2805,8 +3019,8 @@ function CalcBlr(blr, xsqlda) {
|
|
|
2805
3019
|
blr.addByte(const_1.default.blr_end);
|
|
2806
3020
|
blr.addByte(const_1.default.blr_eoc);
|
|
2807
3021
|
}
|
|
2808
|
-
function fetch_blob_async_transaction(statement, id, column, row) {
|
|
2809
|
-
const infoValue = { row, column, value: '' };
|
|
3022
|
+
function fetch_blob_async_transaction(statement, id, column, row, meta) {
|
|
3023
|
+
const infoValue = { row, column, value: '', meta };
|
|
2810
3024
|
return (transactionArg) => {
|
|
2811
3025
|
const cacheKey = `${id.high}:${id.low}`;
|
|
2812
3026
|
if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
|
package/lib/wire/const.d.ts
CHANGED
|
@@ -324,10 +324,18 @@ declare const Const: Readonly<{
|
|
|
324
324
|
isc_dpb_reset_icu: number;
|
|
325
325
|
isc_dpb_map_attach: number;
|
|
326
326
|
isc_dpb_session_time_zone: number;
|
|
327
|
+
isc_dpb_set_db_replica: number;
|
|
328
|
+
isc_dpb_set_bind: number;
|
|
329
|
+
isc_dpb_decfloat_round: number;
|
|
330
|
+
isc_dpb_decfloat_traps: number;
|
|
331
|
+
isc_dpb_clear_map: number;
|
|
332
|
+
isc_dpb_upgrade_db: number;
|
|
327
333
|
isc_dpb_parallel_workers: number;
|
|
334
|
+
isc_dpb_worker_attach: number;
|
|
335
|
+
isc_dpb_owner: number;
|
|
336
|
+
isc_dpb_max_blob_cache_size: number;
|
|
328
337
|
isc_dpb_max_inline_blob_size: number;
|
|
329
338
|
isc_dpb_search_path: number;
|
|
330
|
-
isc_dpb_default_schema: number;
|
|
331
339
|
CNCT_user: number;
|
|
332
340
|
CNCT_passwd: number;
|
|
333
341
|
CNCT_host: number;
|
package/lib/wire/const.js
CHANGED
|
@@ -236,7 +236,8 @@ const SUPPORTED_PROTOCOL = [
|
|
|
236
236
|
[protocol.PROTOCOL_VERSION16, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 7],
|
|
237
237
|
[protocol.PROTOCOL_VERSION17, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 8],
|
|
238
238
|
[protocol.PROTOCOL_VERSION18, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 9],
|
|
239
|
-
[protocol.PROTOCOL_VERSION19, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 10]
|
|
239
|
+
[protocol.PROTOCOL_VERSION19, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 10],
|
|
240
|
+
[protocol.PROTOCOL_VERSION20, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 11]
|
|
240
241
|
];
|
|
241
242
|
const authPlugin = {
|
|
242
243
|
AUTH_PLUGIN_LEGACY: 'Legacy_Auth',
|
|
@@ -430,16 +431,27 @@ const dpb = {
|
|
|
430
431
|
isc_dpb_reset_icu: 89,
|
|
431
432
|
isc_dpb_map_attach: 90,
|
|
432
433
|
isc_dpb_session_time_zone: 91,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
// Firebird 4.0
|
|
435
|
+
isc_dpb_set_db_replica: 92,
|
|
436
|
+
isc_dpb_set_bind: 93,
|
|
437
|
+
isc_dpb_decfloat_round: 94,
|
|
438
|
+
isc_dpb_decfloat_traps: 95,
|
|
439
|
+
isc_dpb_clear_map: 96,
|
|
440
|
+
// Firebird 5.0
|
|
441
|
+
isc_dpb_upgrade_db: 97,
|
|
442
|
+
isc_dpb_parallel_workers: 100,
|
|
443
|
+
isc_dpb_worker_attach: 101,
|
|
444
|
+
// Firebird 6.0
|
|
445
|
+
// isc_dpb_owner sets the owner of a newly created database to a user
|
|
446
|
+
// other than the connecting one (create only; requires superuser).
|
|
447
|
+
isc_dpb_owner: 102,
|
|
448
|
+
isc_dpb_max_blob_cache_size: 103,
|
|
449
|
+
isc_dpb_max_inline_blob_size: 104, // Firebird 5.0.3+
|
|
436
450
|
// isc_dpb_search_path sets the comma-separated ordered list of schema
|
|
437
451
|
// names to search for unqualified object names (similar to PostgreSQL
|
|
438
|
-
// search_path).
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// (CURRENT_SCHEMA) at connection time. Available on Protocol 20+.
|
|
442
|
-
isc_dpb_default_schema: 95,
|
|
452
|
+
// search_path). CURRENT_SCHEMA is the first existing schema of the list.
|
|
453
|
+
// Available on Protocol 20+ (Firebird 6.0).
|
|
454
|
+
isc_dpb_search_path: 105,
|
|
443
455
|
};
|
|
444
456
|
const cnct = {
|
|
445
457
|
CNCT_user: 1, // User name
|