node-firebird 2.8.1 → 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 +268 -7
- package/lib/index.d.ts +17 -9
- package/lib/index.js +42 -3
- package/lib/named-params.d.ts +42 -0
- package/lib/named-params.js +133 -0
- package/lib/pool.js +1 -1
- package/lib/srp.d.ts +3 -3
- package/lib/types.d.ts +185 -25
- package/lib/uri.d.ts +57 -0
- package/lib/uri.js +193 -0
- package/lib/wire/connection.d.ts +92 -59
- package/lib/wire/connection.js +286 -55
- 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 +40 -20
- package/lib/wire/statement.js +26 -6
- package/lib/wire/transaction.d.ts +32 -18
- package/lib/wire/transaction.js +50 -8
- 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 +54 -15
- package/src/messages.ts +1 -1
- package/src/named-params.ts +145 -0
- package/src/pool.ts +1 -1
- package/src/srp.ts +6 -6
- package/src/types.ts +183 -25
- package/src/unix-crypt.ts +9 -9
- package/src/uri.ts +204 -0
- package/src/wire/connection.ts +481 -234
- 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 +68 -31
- package/src/wire/transaction.ts +85 -33
- 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,35 +185,78 @@ 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;
|
|
84
192
|
this._isDetach = false;
|
|
85
193
|
this._isUsed = false;
|
|
86
194
|
this._pooled = options.isPool || false;
|
|
87
|
-
|
|
195
|
+
// Credentials may be absent (e.g. a traditional host:database
|
|
196
|
+
// connection string) — apply the driver defaults once here, so every
|
|
197
|
+
// auth path (op_connect CNCT block, SRP proof, legacy cont_auth) sees
|
|
198
|
+
// the same values.
|
|
199
|
+
if (options && !options.user)
|
|
200
|
+
options.user = const_1.default.DEFAULT_USER;
|
|
201
|
+
if (options && !options.password)
|
|
202
|
+
options.password = const_1.default.DEFAULT_PASSWORD;
|
|
203
|
+
if (options && options.blobChunkSize && options.blobChunkSize > 65535)
|
|
88
204
|
options.blobChunkSize = 65535;
|
|
89
|
-
if (options && options.blobReadChunkSize > 65535)
|
|
205
|
+
if (options && options.blobReadChunkSize && options.blobReadChunkSize > 65535)
|
|
90
206
|
options.blobReadChunkSize = 65535;
|
|
91
207
|
this.options = options;
|
|
92
208
|
this._bind_events(host, port, callback);
|
|
93
209
|
this.error;
|
|
94
210
|
this._retry_connection_id;
|
|
95
211
|
this._retry_connection_interval = options.retryConnectionInterval || 1000;
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
212
|
+
this._statementCacheSize = statementCacheLimit(options);
|
|
213
|
+
this._statementCache = this._statementCacheSize > 0 ? new Map() : null;
|
|
98
214
|
this._messageFile = options.messageFile || path_1.default.join(__dirname, 'firebird.msg');
|
|
99
215
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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;
|
|
105
230
|
}
|
|
231
|
+
cache.delete(query);
|
|
232
|
+
return statement;
|
|
106
233
|
}
|
|
107
|
-
|
|
108
|
-
|
|
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);
|
|
109
260
|
}
|
|
110
261
|
// Reject every request that is still awaiting a server response (e.g. a
|
|
111
262
|
// transaction.commit() sent right before the server went away) instead of
|
|
@@ -139,6 +290,8 @@ class Connection {
|
|
|
139
290
|
self._rejectPending(lostError);
|
|
140
291
|
self._retry_connection_id = setTimeout(function () {
|
|
141
292
|
self._socket.removeAllListeners();
|
|
293
|
+
// transiently null while the replacement Connection is built
|
|
294
|
+
// (restored by the Object.assign(self, ctx) below)
|
|
142
295
|
self._socket = null;
|
|
143
296
|
var ctx = new Connection(host, port, function (err) {
|
|
144
297
|
ctx.connect(self.options, function (err) {
|
|
@@ -339,8 +492,17 @@ class Connection {
|
|
|
339
492
|
}
|
|
340
493
|
const canDefer = defer && this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION11;
|
|
341
494
|
self._socket.write(self._msg.getData(), canDefer);
|
|
342
|
-
if (canDefer
|
|
343
|
-
|
|
495
|
+
if (canDefer) {
|
|
496
|
+
// A deferred packet sits in the socket buffer until the next
|
|
497
|
+
// non-deferred write flushes it, but the server still answers it
|
|
498
|
+
// with its own op_response (delivered along with that next
|
|
499
|
+
// exchange). Queue a placeholder to consume that response —
|
|
500
|
+
// otherwise the queue pairs it with the NEXT request and every
|
|
501
|
+
// later response is off by one. The op itself is fire-and-forget,
|
|
502
|
+
// so complete the caller right away.
|
|
503
|
+
self._queue.push(undefined);
|
|
504
|
+
if (callback)
|
|
505
|
+
callback();
|
|
344
506
|
}
|
|
345
507
|
else {
|
|
346
508
|
self._queue.push(callback);
|
|
@@ -384,10 +546,17 @@ class Connection {
|
|
|
384
546
|
msg.addInt(const_1.default.CONNECT_VERSION3);
|
|
385
547
|
msg.addInt(const_1.default.ARCHITECTURE_GENERIC);
|
|
386
548
|
msg.addString(options.database || options.filename, const_1.default.DEFAULT_ENCODING);
|
|
387
|
-
|
|
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;
|
|
388
554
|
var protocolsToSend = const_1.default.SUPPORTED_PROTOCOL;
|
|
389
555
|
if (protocolsToSend.length > maxProtocols) {
|
|
390
|
-
|
|
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);
|
|
391
560
|
}
|
|
392
561
|
msg.addInt(protocolsToSend.length); // Count of Protocol version understood count.
|
|
393
562
|
msg.addBlr(this._blr);
|
|
@@ -523,21 +692,9 @@ class Connection {
|
|
|
523
692
|
blr.addNumeric(const_1.default.isc_dpb_max_inline_blob_size, options.maxInlineBlobSize);
|
|
524
693
|
}
|
|
525
694
|
// Firebird 6.0 SQL Schema parameters (Protocol 20+).
|
|
526
|
-
// These DPB tags configure the session's current schema and the
|
|
527
|
-
// schema search path for unqualified object name resolution.
|
|
528
695
|
if (this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION20) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
// SET SCHEMA <name> immediately after connecting.
|
|
532
|
-
blr.addString(const_1.default.isc_dpb_default_schema, options.defaultSchema, const_1.default.DEFAULT_ENCODING);
|
|
533
|
-
}
|
|
534
|
-
if (options.searchPath) {
|
|
535
|
-
// Comma-separated ordered schema name list, like PostgreSQL's
|
|
536
|
-
// search_path. Unqualified object references are resolved by
|
|
537
|
-
// scanning schemas in this order.
|
|
538
|
-
const sp = Array.isArray(options.searchPath)
|
|
539
|
-
? options.searchPath.join(',')
|
|
540
|
-
: String(options.searchPath);
|
|
696
|
+
const sp = buildSchemaSearchPath(options);
|
|
697
|
+
if (sp) {
|
|
541
698
|
blr.addString(const_1.default.isc_dpb_search_path, sp, const_1.default.DEFAULT_ENCODING);
|
|
542
699
|
}
|
|
543
700
|
}
|
|
@@ -637,16 +794,17 @@ class Connection {
|
|
|
637
794
|
}
|
|
638
795
|
// Firebird 6.0 SQL Schema parameters (Protocol 20+).
|
|
639
796
|
if (this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION20) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
}
|
|
643
|
-
if (options.searchPath) {
|
|
644
|
-
const sp = Array.isArray(options.searchPath)
|
|
645
|
-
? options.searchPath.join(',')
|
|
646
|
-
: String(options.searchPath);
|
|
797
|
+
const sp = buildSchemaSearchPath(options);
|
|
798
|
+
if (sp) {
|
|
647
799
|
blr.addString(const_1.default.isc_dpb_search_path, sp, const_1.default.DEFAULT_ENCODING);
|
|
648
800
|
}
|
|
649
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
|
+
}
|
|
650
808
|
blr.addNumeric(const_1.default.isc_dpb_sql_dialect, 3);
|
|
651
809
|
blr.addNumeric(const_1.default.isc_dpb_force_write, 1);
|
|
652
810
|
blr.addNumeric(const_1.default.isc_dpb_overwrite, 1);
|
|
@@ -690,6 +848,8 @@ class Connection {
|
|
|
690
848
|
callback(err);
|
|
691
849
|
return this;
|
|
692
850
|
}
|
|
851
|
+
/** `options` is a resolved options object, a bare isolation array, or
|
|
852
|
+
* the callback itself when no options are given. */
|
|
693
853
|
startTransaction(options, callback) {
|
|
694
854
|
if (typeof (options) === 'function') {
|
|
695
855
|
var tmp = options;
|
|
@@ -851,7 +1011,6 @@ class Connection {
|
|
|
851
1011
|
mainCallback.response.query = query;
|
|
852
1012
|
self.db.emit('query', query);
|
|
853
1013
|
ret = mainCallback.response;
|
|
854
|
-
self._setcachedquery(query, ret);
|
|
855
1014
|
}
|
|
856
1015
|
if (callback)
|
|
857
1016
|
callback(err, ret);
|
|
@@ -879,6 +1038,12 @@ class Connection {
|
|
|
879
1038
|
msg.addString(query, const_1.default.DEFAULT_ENCODING);
|
|
880
1039
|
msg.addBlr(blr);
|
|
881
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
|
+
}
|
|
882
1047
|
mainCallback.lazy_count += 1;
|
|
883
1048
|
mainCallback.response = new statement_1.default(this);
|
|
884
1049
|
this._queueEvent(mainCallback);
|
|
@@ -898,6 +1063,7 @@ class Connection {
|
|
|
898
1063
|
});
|
|
899
1064
|
}
|
|
900
1065
|
}
|
|
1066
|
+
/** `plan` may be the callback itself when no plan flag is given. */
|
|
901
1067
|
prepareStatement(transaction, statement, query, plan, callback) {
|
|
902
1068
|
if (this._isClosed)
|
|
903
1069
|
return this.throwClosed(callback);
|
|
@@ -920,6 +1086,9 @@ class Connection {
|
|
|
920
1086
|
msg.addString(query, const_1.default.DEFAULT_ENCODING);
|
|
921
1087
|
msg.addBlr(blr);
|
|
922
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
|
+
}
|
|
923
1092
|
var self = this;
|
|
924
1093
|
this._queueEvent(function (err, ret) {
|
|
925
1094
|
if (!err) {
|
|
@@ -927,7 +1096,6 @@ class Connection {
|
|
|
927
1096
|
statement.query = query;
|
|
928
1097
|
self.db.emit('query', query);
|
|
929
1098
|
ret = statement;
|
|
930
|
-
self._setcachedquery(query, ret);
|
|
931
1099
|
}
|
|
932
1100
|
if (callback)
|
|
933
1101
|
callback(err, ret);
|
|
@@ -985,7 +1153,7 @@ class Connection {
|
|
|
985
1153
|
}
|
|
986
1154
|
var self = this;
|
|
987
1155
|
var encoders = built.encoders;
|
|
988
|
-
var chunkSize = options.chunkSize > 0 ? options.chunkSize : 500;
|
|
1156
|
+
var chunkSize = options.chunkSize && options.chunkSize > 0 ? options.chunkSize : 500;
|
|
989
1157
|
var chunkCount = Math.ceil(rows.length / chunkSize);
|
|
990
1158
|
var failure = null;
|
|
991
1159
|
var completion = null;
|
|
@@ -1043,6 +1211,7 @@ class Connection {
|
|
|
1043
1211
|
msg.addInt(statement.handle);
|
|
1044
1212
|
msg.addInt(end - start);
|
|
1045
1213
|
for (var i = start; i < end; i++) {
|
|
1214
|
+
// validated as an array of input.length values above
|
|
1046
1215
|
var row = rows[i];
|
|
1047
1216
|
var nullBits = new serialize_1.BitSet();
|
|
1048
1217
|
for (var j = 0; j < input.length; j++) {
|
|
@@ -1114,6 +1283,7 @@ class Connection {
|
|
|
1114
1283
|
}
|
|
1115
1284
|
}
|
|
1116
1285
|
}
|
|
1286
|
+
/** `params` may be the callback itself when the statement has no parameters. */
|
|
1117
1287
|
executeStatement(transaction, statement, params, callback, custom) {
|
|
1118
1288
|
if (this._isClosed)
|
|
1119
1289
|
return this.throwClosed(callback);
|
|
@@ -1425,6 +1595,7 @@ class Connection {
|
|
|
1425
1595
|
callback.statement = statement;
|
|
1426
1596
|
this._queueEvent(callback);
|
|
1427
1597
|
}
|
|
1598
|
+
/** `count` may be the callback itself when no fetch size is given. */
|
|
1428
1599
|
fetch(statement, transaction, count, callback) {
|
|
1429
1600
|
var msg = this._msg;
|
|
1430
1601
|
var blr = this._blr;
|
|
@@ -1528,7 +1699,7 @@ class Connection {
|
|
|
1528
1699
|
readBlobsSequentially(0, []).then((arrBlob) => {
|
|
1529
1700
|
for (let i = 0; i < arrBlob.length; i++) {
|
|
1530
1701
|
const blob = arrBlob[i];
|
|
1531
|
-
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));
|
|
1532
1703
|
}
|
|
1533
1704
|
doSynchronousLoop(ret.data, (row, _i, next) => {
|
|
1534
1705
|
const pos = asStream ? streamIndex++ : (data.push(row) - 1);
|
|
@@ -1895,12 +2066,25 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1895
2066
|
}
|
|
1896
2067
|
case const_1.default.op_fetch_response:
|
|
1897
2068
|
case const_1.default.op_sql_response:
|
|
2069
|
+
// fetch/sql_response entries always carry their statement
|
|
1898
2070
|
var statement = callback.statement;
|
|
1899
2071
|
var output = statement.output;
|
|
1900
2072
|
var custom = statement.options || {};
|
|
1901
2073
|
var isOpFetch = r === const_1.default.op_fetch_response;
|
|
1902
2074
|
var _xdrpos;
|
|
1903
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;
|
|
1904
2088
|
if (isOpFetch && data.fop) { // could be set when a packet is not complete
|
|
1905
2089
|
data.readBuffer(68); // ??
|
|
1906
2090
|
op = data.readInt(); // ??
|
|
@@ -1927,6 +2111,13 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1927
2111
|
}
|
|
1928
2112
|
const arrBlob = [];
|
|
1929
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;
|
|
1930
2121
|
while (data.fcount && (data.fstatus !== 100)) {
|
|
1931
2122
|
let nullBitSet;
|
|
1932
2123
|
if (!lowerV13) {
|
|
@@ -1937,12 +2128,8 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1937
2128
|
for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
|
|
1938
2129
|
item = output[data.fcolumn];
|
|
1939
2130
|
if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
}
|
|
1943
|
-
else {
|
|
1944
|
-
data.frow[data.fcolumn] = null;
|
|
1945
|
-
}
|
|
2131
|
+
const nullKey = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
2132
|
+
data.frow[nullKey] = applyTypeCast(cnx.options, item, null);
|
|
1946
2133
|
continue;
|
|
1947
2134
|
}
|
|
1948
2135
|
try {
|
|
@@ -1950,16 +2137,23 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1950
2137
|
const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
1951
2138
|
const row = data.frows.length;
|
|
1952
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;
|
|
1953
2144
|
if (item.type === const_1.default.SQL_BLOB && value !== null) {
|
|
1954
2145
|
if (item.subType === const_1.default.isc_blob_text && cnx.options.blobAsText) {
|
|
1955
|
-
value = fetch_blob_async_transaction(statement, value, key, row);
|
|
2146
|
+
value = fetch_blob_async_transaction(statement, value, key, row, item);
|
|
1956
2147
|
arrBlob.push(value);
|
|
2148
|
+
pendingTextBlob = true;
|
|
1957
2149
|
}
|
|
1958
2150
|
else {
|
|
1959
2151
|
value = fetch_blob_async(statement, value, key, row);
|
|
1960
2152
|
}
|
|
1961
2153
|
}
|
|
1962
|
-
data.frow[key] =
|
|
2154
|
+
data.frow[key] = pendingTextBlob
|
|
2155
|
+
? value
|
|
2156
|
+
: applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
|
|
1963
2157
|
}
|
|
1964
2158
|
catch (e) {
|
|
1965
2159
|
// uncomplete packet read
|
|
@@ -2006,6 +2200,16 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2006
2200
|
}
|
|
2007
2201
|
statement.nbrowsfetched++;
|
|
2008
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
|
+
}
|
|
2009
2213
|
// ToDo: emit "result" with blob subtype string decoded
|
|
2010
2214
|
statement.connection.db.emit('result', data.frows, arrBlob);
|
|
2011
2215
|
return cb(null, { data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob });
|
|
@@ -2340,6 +2544,17 @@ function readStatusVector(data) {
|
|
|
2340
2544
|
result.sqlcode = n;
|
|
2341
2545
|
}
|
|
2342
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;
|
|
2343
2558
|
default:
|
|
2344
2559
|
throw new Error('Unexpected status vector item: ' + op);
|
|
2345
2560
|
}
|
|
@@ -2400,13 +2615,25 @@ function parseOpResponse(data, response, cb) {
|
|
|
2400
2615
|
response.sqlcode = num;
|
|
2401
2616
|
}
|
|
2402
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;
|
|
2403
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).
|
|
2404
2633
|
if (cb) {
|
|
2405
|
-
cb(new Error('Unexpected: ' + op));
|
|
2406
|
-
}
|
|
2407
|
-
else {
|
|
2408
|
-
throw new Error('Unexpected: ' + op);
|
|
2634
|
+
return cb(new Error('Unexpected: ' + op));
|
|
2409
2635
|
}
|
|
2636
|
+
throw new Error('Unexpected: ' + op);
|
|
2410
2637
|
}
|
|
2411
2638
|
}
|
|
2412
2639
|
}
|
|
@@ -2443,6 +2670,8 @@ function describe(buff, statement) {
|
|
|
2443
2670
|
case const_1.default.isc_info_sql_describe_end:
|
|
2444
2671
|
break;
|
|
2445
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
|
|
2446
2675
|
var num = br.readInt();
|
|
2447
2676
|
break;
|
|
2448
2677
|
case const_1.default.isc_info_sql_type:
|
|
@@ -2518,6 +2747,8 @@ function describe(buff, statement) {
|
|
|
2518
2747
|
default:
|
|
2519
2748
|
throw new Error('Unexpected');
|
|
2520
2749
|
}
|
|
2750
|
+
// isc_info_sql_sqlda_seq always precedes the type
|
|
2751
|
+
// item in the describe stream, so num is set here
|
|
2521
2752
|
parameters[num - 1] = param;
|
|
2522
2753
|
param.type = type;
|
|
2523
2754
|
param.nullable = Boolean(param.type & 1);
|
|
@@ -2788,8 +3019,8 @@ function CalcBlr(blr, xsqlda) {
|
|
|
2788
3019
|
blr.addByte(const_1.default.blr_end);
|
|
2789
3020
|
blr.addByte(const_1.default.blr_eoc);
|
|
2790
3021
|
}
|
|
2791
|
-
function fetch_blob_async_transaction(statement, id, column, row) {
|
|
2792
|
-
const infoValue = { row, column, value: '' };
|
|
3022
|
+
function fetch_blob_async_transaction(statement, id, column, row, meta) {
|
|
3023
|
+
const infoValue = { row, column, value: '', meta };
|
|
2793
3024
|
return (transactionArg) => {
|
|
2794
3025
|
const cacheKey = `${id.high}:${id.low}`;
|
|
2795
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
|