node-firebird 2.9.0 → 2.11.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 +211 -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 +165 -5
- package/lib/uri.js +2 -2
- package/lib/wire/connection.d.ts +92 -59
- package/lib/wire/connection.js +279 -58
- package/lib/wire/const.d.ts +9 -1
- package/lib/wire/const.js +23 -9
- package/lib/wire/database.d.ts +51 -26
- package/lib/wire/database.js +53 -20
- 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 +20 -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 +57 -18
- package/lib/wire/xsqlvar.js +59 -0
- 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 +162 -5
- package/src/unix-crypt.ts +9 -9
- package/src/uri.ts +2 -2
- package/src/wire/connection.ts +475 -234
- package/src/wire/const.ts +23 -9
- package/src/wire/database.ts +101 -54
- package/src/wire/eventConnection.ts +8 -5
- package/src/wire/query-stream.ts +80 -0
- package/src/wire/serialize.ts +31 -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 +85 -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;
|
|
113
226
|
}
|
|
227
|
+
const statement = cache.get(query);
|
|
228
|
+
if (!statement) {
|
|
229
|
+
return null;
|
|
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,9 @@ 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
|
-
|
|
1702
|
+
// nestTables === true rows: the value lives in the
|
|
1703
|
+
// per-table sub-object, not on the row itself
|
|
1704
|
+
Xsql.nestCell(ret.data[blob.row], blob.table)[blob.column] = applyTypeCast(statement.connection.options, blob.meta || {}, parseValueIfJson(blob.value, statement.connection.options));
|
|
1549
1705
|
}
|
|
1550
1706
|
doSynchronousLoop(ret.data, (row, _i, next) => {
|
|
1551
1707
|
const pos = asStream ? streamIndex++ : (data.push(row) - 1);
|
|
@@ -1912,12 +2068,26 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1912
2068
|
}
|
|
1913
2069
|
case const_1.default.op_fetch_response:
|
|
1914
2070
|
case const_1.default.op_sql_response:
|
|
2071
|
+
// fetch/sql_response entries always carry their statement
|
|
1915
2072
|
var statement = callback.statement;
|
|
1916
2073
|
var output = statement.output;
|
|
1917
2074
|
var custom = statement.options || {};
|
|
1918
2075
|
var isOpFetch = r === const_1.default.op_fetch_response;
|
|
1919
2076
|
var _xdrpos;
|
|
1920
2077
|
statement.nbrowsfetched = statement.nbrowsfetched || 0;
|
|
2078
|
+
// The f* decode state is only meaningful within a single
|
|
2079
|
+
// decode call: incomplete packets are re-decoded from scratch
|
|
2080
|
+
// on a fresh XdrReader (see the 'data' handler). State left by
|
|
2081
|
+
// an earlier packet in the same data event (e.g. fstatus=100 /
|
|
2082
|
+
// fcount=0 from a completed fetch) would make this decode
|
|
2083
|
+
// consume just the opcode and desync every later response.
|
|
2084
|
+
delete data.fstatus;
|
|
2085
|
+
delete data.fcount;
|
|
2086
|
+
delete data.fcolumn;
|
|
2087
|
+
delete data.frow;
|
|
2088
|
+
delete data.frows;
|
|
2089
|
+
delete data.fcols;
|
|
2090
|
+
delete data.ftables;
|
|
1921
2091
|
if (isOpFetch && data.fop) { // could be set when a packet is not complete
|
|
1922
2092
|
data.readBuffer(68); // ??
|
|
1923
2093
|
op = data.readInt(); // ??
|
|
@@ -1935,15 +2105,23 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1935
2105
|
data.frow = data.frow || (custom.asObject ? {} : new Array(output.length));
|
|
1936
2106
|
data.frows = data.frows || [];
|
|
1937
2107
|
if (custom.asObject && !data.fcols) {
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2108
|
+
const nest = Xsql.resolveNestTables(custom, cnx.options);
|
|
2109
|
+
const columnKeys = Xsql.computeColumnKeys(output, nest, lowercase_keys);
|
|
2110
|
+
data.fcols = columnKeys.map((k) => k.key);
|
|
2111
|
+
if (nest === true) {
|
|
2112
|
+
// computeColumnKeys always sets table when nesting
|
|
2113
|
+
data.ftables = columnKeys.map((k) => k.table);
|
|
1943
2114
|
}
|
|
1944
2115
|
}
|
|
1945
2116
|
const arrBlob = [];
|
|
1946
2117
|
const lowerV13 = statement.connection.accept.protocolVersion < const_1.default.PROTOCOL_VERSION13;
|
|
2118
|
+
// op_sql_response (op_execute2) is always followed by an
|
|
2119
|
+
// op_response carrying the execute status vector. The row loop
|
|
2120
|
+
// below consumes it after the last row, but with zero rows
|
|
2121
|
+
// (e.g. INSERT ... RETURNING failing on a constraint) it stays
|
|
2122
|
+
// in the buffer, shifting every later response to the wrong
|
|
2123
|
+
// callback and poisoning the connection (issue #341).
|
|
2124
|
+
var sqlResponseTrailerPending = !isOpFetch && !data.fcount;
|
|
1947
2125
|
while (data.fcount && (data.fstatus !== 100)) {
|
|
1948
2126
|
let nullBitSet;
|
|
1949
2127
|
if (!lowerV13) {
|
|
@@ -1954,12 +2132,11 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1954
2132
|
for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
|
|
1955
2133
|
item = output[data.fcolumn];
|
|
1956
2134
|
if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
}
|
|
2135
|
+
const nullKey = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
2136
|
+
// ftables is only set when nestTables === true, so
|
|
2137
|
+
// the default path writes straight into the row
|
|
2138
|
+
(data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn]) : data.frow)[nullKey] =
|
|
2139
|
+
applyTypeCast(cnx.options, item, null);
|
|
1963
2140
|
continue;
|
|
1964
2141
|
}
|
|
1965
2142
|
try {
|
|
@@ -1967,16 +2144,23 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1967
2144
|
const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
1968
2145
|
const row = data.frows.length;
|
|
1969
2146
|
let value = item.decode(data, lowerV13, cnx.options);
|
|
2147
|
+
// text blobs resolved by blobAsText run through the
|
|
2148
|
+
// typeCast hook once the text arrives (see fetchAll),
|
|
2149
|
+
// not here where the value is still a pending fetch
|
|
2150
|
+
let pendingTextBlob = false;
|
|
1970
2151
|
if (item.type === const_1.default.SQL_BLOB && value !== null) {
|
|
1971
2152
|
if (item.subType === const_1.default.isc_blob_text && cnx.options.blobAsText) {
|
|
1972
|
-
value = fetch_blob_async_transaction(statement, value, key, row);
|
|
2153
|
+
value = fetch_blob_async_transaction(statement, value, key, row, item, data.ftables && data.ftables[data.fcolumn]);
|
|
1973
2154
|
arrBlob.push(value);
|
|
2155
|
+
pendingTextBlob = true;
|
|
1974
2156
|
}
|
|
1975
2157
|
else {
|
|
1976
2158
|
value = fetch_blob_async(statement, value, key, row);
|
|
1977
2159
|
}
|
|
1978
2160
|
}
|
|
1979
|
-
data.frow[key] =
|
|
2161
|
+
(data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn]) : data.frow)[key] = pendingTextBlob
|
|
2162
|
+
? value
|
|
2163
|
+
: applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
|
|
1980
2164
|
}
|
|
1981
2165
|
catch (e) {
|
|
1982
2166
|
// uncomplete packet read
|
|
@@ -2023,6 +2207,16 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2023
2207
|
}
|
|
2024
2208
|
statement.nbrowsfetched++;
|
|
2025
2209
|
}
|
|
2210
|
+
if (sqlResponseTrailerPending) {
|
|
2211
|
+
op = data.readInt();
|
|
2212
|
+
if (op === const_1.default.op_response) {
|
|
2213
|
+
response = {};
|
|
2214
|
+
parseOpResponse(data, response);
|
|
2215
|
+
if (response.status) {
|
|
2216
|
+
return cb(null, response);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2026
2220
|
// ToDo: emit "result" with blob subtype string decoded
|
|
2027
2221
|
statement.connection.db.emit('result', data.frows, arrBlob);
|
|
2028
2222
|
return cb(null, { data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob });
|
|
@@ -2357,6 +2551,17 @@ function readStatusVector(data) {
|
|
|
2357
2551
|
result.sqlcode = n;
|
|
2358
2552
|
}
|
|
2359
2553
|
break;
|
|
2554
|
+
case const_1.default.isc_arg_warning:
|
|
2555
|
+
// A warning attached to a SUCCESS vector (e.g. "parallel
|
|
2556
|
+
// workers value capped"). Keep it out of `status` so the
|
|
2557
|
+
// operation is not mistaken for a failure; later string/
|
|
2558
|
+
// number items attach to the warning entry.
|
|
2559
|
+
var wnum = data.readInt();
|
|
2560
|
+
item = { gdscode: wnum };
|
|
2561
|
+
if (wnum) {
|
|
2562
|
+
(result.warnings = result.warnings || []).push(item);
|
|
2563
|
+
}
|
|
2564
|
+
break;
|
|
2360
2565
|
default:
|
|
2361
2566
|
throw new Error('Unexpected status vector item: ' + op);
|
|
2362
2567
|
}
|
|
@@ -2417,13 +2622,25 @@ function parseOpResponse(data, response, cb) {
|
|
|
2417
2622
|
response.sqlcode = num;
|
|
2418
2623
|
}
|
|
2419
2624
|
break;
|
|
2625
|
+
case const_1.default.isc_arg_warning:
|
|
2626
|
+
// A warning attached to a SUCCESS response (e.g. Firebird's
|
|
2627
|
+
// "parallel workers value capped" on attach). Keep it out of
|
|
2628
|
+
// `status` so the response is not mistaken for an error;
|
|
2629
|
+
// later string/number items attach to the warning entry.
|
|
2630
|
+
num = data.readInt();
|
|
2631
|
+
item = { gdscode: num };
|
|
2632
|
+
if (num) {
|
|
2633
|
+
(response.warnings = response.warnings || []).push(item);
|
|
2634
|
+
}
|
|
2635
|
+
break;
|
|
2420
2636
|
default:
|
|
2637
|
+
// Stop parsing: continuing the loop after an unknown item
|
|
2638
|
+
// re-read the same bytes forever (the caller resets the
|
|
2639
|
+
// reader position when the error is delivered).
|
|
2421
2640
|
if (cb) {
|
|
2422
|
-
cb(new Error('Unexpected: ' + op));
|
|
2423
|
-
}
|
|
2424
|
-
else {
|
|
2425
|
-
throw new Error('Unexpected: ' + op);
|
|
2641
|
+
return cb(new Error('Unexpected: ' + op));
|
|
2426
2642
|
}
|
|
2643
|
+
throw new Error('Unexpected: ' + op);
|
|
2427
2644
|
}
|
|
2428
2645
|
}
|
|
2429
2646
|
}
|
|
@@ -2460,6 +2677,8 @@ function describe(buff, statement) {
|
|
|
2460
2677
|
case const_1.default.isc_info_sql_describe_end:
|
|
2461
2678
|
break;
|
|
2462
2679
|
case const_1.default.isc_info_sql_sqlda_seq:
|
|
2680
|
+
// describe output always encodes the sequence as a
|
|
2681
|
+
// 1/2/4-byte int, so readInt cannot return undefined
|
|
2463
2682
|
var num = br.readInt();
|
|
2464
2683
|
break;
|
|
2465
2684
|
case const_1.default.isc_info_sql_type:
|
|
@@ -2535,6 +2754,8 @@ function describe(buff, statement) {
|
|
|
2535
2754
|
default:
|
|
2536
2755
|
throw new Error('Unexpected');
|
|
2537
2756
|
}
|
|
2757
|
+
// isc_info_sql_sqlda_seq always precedes the type
|
|
2758
|
+
// item in the describe stream, so num is set here
|
|
2538
2759
|
parameters[num - 1] = param;
|
|
2539
2760
|
param.type = type;
|
|
2540
2761
|
param.nullable = Boolean(param.type & 1);
|
|
@@ -2805,8 +3026,8 @@ function CalcBlr(blr, xsqlda) {
|
|
|
2805
3026
|
blr.addByte(const_1.default.blr_end);
|
|
2806
3027
|
blr.addByte(const_1.default.blr_eoc);
|
|
2807
3028
|
}
|
|
2808
|
-
function fetch_blob_async_transaction(statement, id, column, row) {
|
|
2809
|
-
const infoValue = { row, column, value: '' };
|
|
3029
|
+
function fetch_blob_async_transaction(statement, id, column, row, meta, table) {
|
|
3030
|
+
const infoValue = { row, column, value: '', meta, table };
|
|
2810
3031
|
return (transactionArg) => {
|
|
2811
3032
|
const cacheKey = `${id.high}:${id.low}`;
|
|
2812
3033
|
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
|
|
@@ -565,6 +577,7 @@ const DESCRIBE = [
|
|
|
565
577
|
sqlInfo.isc_info_sql_length,
|
|
566
578
|
sqlInfo.isc_info_sql_field,
|
|
567
579
|
sqlInfo.isc_info_sql_relation,
|
|
580
|
+
sqlInfo.isc_info_sql_relation_alias, // FB 2.0+: query alias of the source relation (nestTables)
|
|
568
581
|
//isc_info_sql_owner,
|
|
569
582
|
sqlInfo.isc_info_sql_alias,
|
|
570
583
|
sqlInfo.isc_info_sql_describe_end,
|
|
@@ -592,6 +605,7 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
592
605
|
sqlInfo.isc_info_sql_field,
|
|
593
606
|
sqlInfo.isc_info_sql_relation,
|
|
594
607
|
sqlInfo.isc_info_sql_relation_schema, // FB 6.0: schema of source relation
|
|
608
|
+
sqlInfo.isc_info_sql_relation_alias, // query alias of the source relation (nestTables)
|
|
595
609
|
//isc_info_sql_owner,
|
|
596
610
|
sqlInfo.isc_info_sql_alias,
|
|
597
611
|
sqlInfo.isc_info_sql_describe_end,
|