node-firebird 2.3.2 → 2.3.3
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 +163 -31
- package/lib/index.d.ts +12 -0
- package/lib/pool.js +10 -1
- package/lib/srp.js +12 -1
- package/lib/wire/connection.js +39 -7
- package/lib/wire/socket.js +13 -1
- package/lib/wire/xsqlvar.js +69 -6
- package/package.json +1 -1
- package/poc/node_modules/.package-lock.json +14 -0
- package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
- package/poc/node_modules/node-firebird/LICENSE +373 -0
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
- package/poc/node_modules/node-firebird/README.md +794 -0
- package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
- package/poc/node_modules/node-firebird/lib/callback.js +38 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
- package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
- package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
- package/poc/node_modules/node-firebird/lib/index.js +128 -0
- package/poc/node_modules/node-firebird/lib/messages.js +162 -0
- package/poc/node_modules/node-firebird/lib/pool.js +108 -0
- package/poc/node_modules/node-firebird/lib/srp.js +299 -0
- package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
- package/poc/node_modules/node-firebird/lib/utils.js +164 -0
- package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
- package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
- package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
- package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
- package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
- package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
- package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
- package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
- package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
- package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
- package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
- package/poc/node_modules/node-firebird/package.json +38 -0
- package/poc/node_modules/node-firebird/vitest.config.js +24 -0
- package/vitest.config.js +3 -1
|
@@ -0,0 +1,2510 @@
|
|
|
1
|
+
const Events = require('events');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const {XdrWriter, BlrWriter, XdrReader, BitSet, BlrReader} = require('./serialize');
|
|
6
|
+
const {doCallback, doError} = require('../callback');
|
|
7
|
+
const srp = require('../srp');
|
|
8
|
+
const crypt = require('../unix-crypt');
|
|
9
|
+
const Const = require('./const');
|
|
10
|
+
const Xsql = require('./xsqlvar');
|
|
11
|
+
const ServiceManager = require('./service');
|
|
12
|
+
const Database = require('./database');
|
|
13
|
+
const Statement = require('./statement');
|
|
14
|
+
const Transaction = require('./transaction');
|
|
15
|
+
const {lookupMessages, noop, parseDate} = require('../utils');
|
|
16
|
+
const Socket = require("./socket");
|
|
17
|
+
|
|
18
|
+
/***************************************
|
|
19
|
+
*
|
|
20
|
+
* Connection
|
|
21
|
+
*
|
|
22
|
+
***************************************/
|
|
23
|
+
|
|
24
|
+
class Connection {
|
|
25
|
+
constructor(host, port, callback, options, db, svc) {
|
|
26
|
+
var self = this;
|
|
27
|
+
this.db = db;
|
|
28
|
+
this.svc = svc
|
|
29
|
+
this._msg = new XdrWriter(32);
|
|
30
|
+
this._blr = new BlrWriter(32);
|
|
31
|
+
this._queue = [];
|
|
32
|
+
this._detachTimeout;
|
|
33
|
+
this._detachCallback;
|
|
34
|
+
this._detachAuto;
|
|
35
|
+
this._socket = new Socket(port, host);
|
|
36
|
+
this._pending = [];
|
|
37
|
+
this._isOpened = false;
|
|
38
|
+
this._isClosed = false;
|
|
39
|
+
this._isDetach = false;
|
|
40
|
+
this._isUsed = false;
|
|
41
|
+
this._pooled = options.isPool||false;
|
|
42
|
+
if (options && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
|
|
43
|
+
this.options = options;
|
|
44
|
+
this._bind_events(host, port, callback);
|
|
45
|
+
this.error;
|
|
46
|
+
this._retry_connection_id;
|
|
47
|
+
this._retry_connection_interval = options.retryConnectionInterval || 1000;
|
|
48
|
+
this._max_cached_query = options.maxCachedQuery || -1;
|
|
49
|
+
this._cache_query = options.cacheQuery?{}:null;
|
|
50
|
+
this._messageFile = options.messageFile || path.join(__dirname, 'firebird.msg');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_setcachedquery(query, statement) {
|
|
55
|
+
if (this._cache_query){
|
|
56
|
+
if (this._max_cached_query === -1 || this._max_cached_query > Object.keys(this._cache_query).length){
|
|
57
|
+
this._cache_query[query] = statement;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
getCachedQuery(query) {
|
|
66
|
+
return this._cache_query ? this._cache_query[query] : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_bind_events(host, port, callback) {
|
|
71
|
+
|
|
72
|
+
var self = this;
|
|
73
|
+
|
|
74
|
+
self._socket.on('close', function() {
|
|
75
|
+
|
|
76
|
+
if (!self._isOpened || self._isDetach) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
self._isOpened = false;
|
|
81
|
+
|
|
82
|
+
if (!self.db) {
|
|
83
|
+
if (callback)
|
|
84
|
+
callback(self.error);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
self._retry_connection_id = setTimeout(function() {
|
|
89
|
+
self._socket.removeAllListeners();
|
|
90
|
+
self._socket = null;
|
|
91
|
+
|
|
92
|
+
var ctx = new Connection(host, port, function(err) {
|
|
93
|
+
ctx.connect(self.options, function(err) {
|
|
94
|
+
|
|
95
|
+
if (err) {
|
|
96
|
+
self.db.emit('error', err);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ctx.attach(self.options, function(err) {
|
|
101
|
+
|
|
102
|
+
if (err) {
|
|
103
|
+
self.db.emit('error', err);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
ctx._queue = ctx._queue.concat(self._queue);
|
|
108
|
+
ctx._pending = ctx._pending.concat(self._pending);
|
|
109
|
+
self.db.emit('reconnect');
|
|
110
|
+
|
|
111
|
+
}, self.db);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
Object.assign(self, ctx);
|
|
115
|
+
|
|
116
|
+
}, self.options, self.db);
|
|
117
|
+
}, self._retry_connection_interval);
|
|
118
|
+
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
self._socket.on('error', function(e) {
|
|
122
|
+
|
|
123
|
+
self.error = e;
|
|
124
|
+
|
|
125
|
+
if (self.db)
|
|
126
|
+
self.db.emit('error', e)
|
|
127
|
+
|
|
128
|
+
if (callback)
|
|
129
|
+
callback(e);
|
|
130
|
+
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
self._socket.on('connect', function() {
|
|
134
|
+
self._isClosed = false;
|
|
135
|
+
self._isOpened = true;
|
|
136
|
+
if (callback)
|
|
137
|
+
callback();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
self._socket.on('data', function (data) {
|
|
141
|
+
var xdr;
|
|
142
|
+
var hadSavedBuffer = Boolean(self._xdr);
|
|
143
|
+
|
|
144
|
+
if (!self._xdr) {
|
|
145
|
+
xdr = new XdrReader(data);
|
|
146
|
+
} else {
|
|
147
|
+
xdr = new XdrReader(Buffer.concat([self._xdr.buffer, data], self._xdr.buffer.length + data.length));
|
|
148
|
+
delete (self._xdr);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
152
|
+
console.log('[fb-debug] data event: bytes=%d queue=%d pending=%d xdr.pos=%d savedBuf=%s',
|
|
153
|
+
xdr.buffer.length, self._queue.length, self._pending.length, xdr.pos, hadSavedBuffer);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
while (xdr.pos < xdr.buffer.length) {
|
|
157
|
+
var cb = self._queue[0], pos = xdr.pos;
|
|
158
|
+
|
|
159
|
+
decodeResponse(xdr, cb, self, self._lowercase_keys, function (err, obj) {
|
|
160
|
+
|
|
161
|
+
if (err) {
|
|
162
|
+
if (err instanceof RangeError) {
|
|
163
|
+
// Genuinely incomplete packet – buffer the remaining bytes
|
|
164
|
+
// and wait for the next 'data' event to reassemble.
|
|
165
|
+
xdr.buffer = xdr.buffer.slice(pos);
|
|
166
|
+
xdr.pos = 0;
|
|
167
|
+
self._xdr = xdr;
|
|
168
|
+
|
|
169
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
170
|
+
console.log('[fb-debug] incomplete packet: saved %d bytes at pos=%d queue=%d',
|
|
171
|
+
xdr.buffer.length, pos, self._queue.length);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (self.accept && self.accept.protocolMinimumType === Const.ptype_lazy_send && self._queue.length > 0) {
|
|
175
|
+
self._queue[0].lazy_count = 2;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// Any other error (truly unknown opcode not handled above).
|
|
179
|
+
// Save the buffer so it can be retried, but log a warning.
|
|
180
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
181
|
+
console.warn(`[fb-debug] unhandled protocol error: ${err.message} pos=${pos} bytes=${xdr.buffer.length} queue=${self._queue.length}`);
|
|
182
|
+
}
|
|
183
|
+
xdr.buffer = xdr.buffer.slice(pos);
|
|
184
|
+
xdr.pos = 0;
|
|
185
|
+
self._xdr = xdr;
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// remove the op flag, needed for partial packet
|
|
191
|
+
if (xdr.r) {
|
|
192
|
+
delete (xdr.r);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// op_event / op_response_piggyback received on the main connection:
|
|
196
|
+
// data has been consumed by decodeResponse but it does not belong to
|
|
197
|
+
// any queued request – do NOT shift the queue or invoke any pending
|
|
198
|
+
// callback.
|
|
199
|
+
if (obj && obj._isOpEvent) {
|
|
200
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
201
|
+
console.log('[fb-debug] async opcode consumed (ignored): queue=%d xdr.pos=%d remaining=%d',
|
|
202
|
+
self._queue.length, xdr.pos, xdr.buffer.length - xdr.pos);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
self._queue.shift();
|
|
208
|
+
self._pending.shift();
|
|
209
|
+
|
|
210
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
211
|
+
console.log('[fb-debug] response dispatched: queue remaining=%d pending remaining=%d xdr.pos=%d',
|
|
212
|
+
self._queue.length, self._pending.length, xdr.pos);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (obj && obj.status) {
|
|
216
|
+
obj.message = lookupMessages(obj.status);
|
|
217
|
+
doCallback(obj, cb);
|
|
218
|
+
} else {
|
|
219
|
+
doCallback(obj, cb);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (xdr.pos === 0) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!self._detachAuto || self._pending.length !== 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
clearTimeout(self._detachTimeout);
|
|
234
|
+
self._detachTimeout = setTimeout(function () {
|
|
235
|
+
self.db.detach(self._detachCallback);
|
|
236
|
+
self._detachAuto = false;
|
|
237
|
+
}, 100);
|
|
238
|
+
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
disconnect() {
|
|
244
|
+
this._socket.end();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
sendOpContAuth(authData, authDataEnc, pluginName) {
|
|
252
|
+
var msg = this._msg;
|
|
253
|
+
msg.pos = 0;
|
|
254
|
+
|
|
255
|
+
msg.addInt(Const.op_cont_auth);
|
|
256
|
+
msg.addString(authData, authDataEnc);
|
|
257
|
+
msg.addString(pluginName, Const.DEFAULT_ENCODING)
|
|
258
|
+
msg.addString(Const.AUTH_PLUGIN_LIST.join(','), Const.DEFAULT_ENCODING);
|
|
259
|
+
// msg.addInt(0); // p_list
|
|
260
|
+
msg.addInt(0); // keys
|
|
261
|
+
|
|
262
|
+
this._socket.write(msg.getData());
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
sendOpCrypt(encryptPlugin) {
|
|
267
|
+
var msg = this._msg;
|
|
268
|
+
msg.pos = 0;
|
|
269
|
+
|
|
270
|
+
msg.addInt(Const.op_crypt);
|
|
271
|
+
msg.addString(encryptPlugin || 'Arc4', Const.DEFAULT_ENCODING);
|
|
272
|
+
msg.addString('Symmetric', Const.DEFAULT_ENCODING);
|
|
273
|
+
|
|
274
|
+
this._socket.write(msg.getData());
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
sendOpCryptKeyCallback(pluginData) {
|
|
279
|
+
var msg = this._msg;
|
|
280
|
+
msg.pos = 0;
|
|
281
|
+
|
|
282
|
+
msg.addInt(Const.op_crypt_key_callback);
|
|
283
|
+
msg.addBlr(pluginData); // Send the callback response data as a buffer
|
|
284
|
+
|
|
285
|
+
this._socket.write(msg.getData());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
_queueEvent(callback, defer = false) {
|
|
290
|
+
var self = this;
|
|
291
|
+
|
|
292
|
+
if (self._isClosed) {
|
|
293
|
+
if (callback)
|
|
294
|
+
callback(new Error('Connection is closed.'));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const canDefer = defer && this.accept.protocolVersion >= Const.PROTOCOL_VERSION11;
|
|
299
|
+
|
|
300
|
+
self._socket.write(self._msg.getData(), canDefer);
|
|
301
|
+
if (canDefer && callback) {
|
|
302
|
+
callback();
|
|
303
|
+
} else {
|
|
304
|
+
self._queue.push(callback);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
connect(options, callback) {
|
|
310
|
+
var pluginName = options.manager ? Const.AUTH_PLUGIN_LEGACY : options.pluginName || Const.AUTH_PLUGIN_LIST[0]; // TODO Srp for service
|
|
311
|
+
var msg = this._msg;
|
|
312
|
+
var blr = this._blr;
|
|
313
|
+
|
|
314
|
+
this._pending.push('connect');
|
|
315
|
+
this._authStartTime = Date.now();
|
|
316
|
+
|
|
317
|
+
msg.pos = 0;
|
|
318
|
+
blr.pos = 0;
|
|
319
|
+
|
|
320
|
+
blr.addString(Const.CNCT_login, options.user, Const.DEFAULT_ENCODING);
|
|
321
|
+
blr.addString(Const.CNCT_plugin_name, pluginName, Const.DEFAULT_ENCODING);
|
|
322
|
+
blr.addString(Const.CNCT_plugin_list, Const.AUTH_PLUGIN_LIST.join(','), Const.DEFAULT_ENCODING);
|
|
323
|
+
|
|
324
|
+
var specificData = '';
|
|
325
|
+
if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(pluginName) > -1) {
|
|
326
|
+
const _t0 = Date.now();
|
|
327
|
+
this.clientKeys = srp.clientSeed();
|
|
328
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
329
|
+
console.log('[fb-debug] srp.clientSeed: %dms', Date.now() - _t0);
|
|
330
|
+
}
|
|
331
|
+
specificData = this.clientKeys.public.toString(16);
|
|
332
|
+
blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
|
|
333
|
+
} else if (pluginName === Const.AUTH_PLUGIN_LEGACY) {
|
|
334
|
+
specificData = crypt.crypt(options.password, Const.LEGACY_AUTH_SALT).substring(2);
|
|
335
|
+
blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
|
|
336
|
+
} else {
|
|
337
|
+
doError(new Error('Invalide auth plugin \'' + pluginName + '\''), callback);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
blr.addBytes([Const.CNCT_client_crypt, 4, options.wireCrypt !== undefined ? options.wireCrypt : Const.WIRE_CRYPT_ENABLE, 0, 0, 0]);
|
|
341
|
+
blr.addString(Const.CNCT_user, os.userInfo().username || 'Unknown', Const.DEFAULT_ENCODING);
|
|
342
|
+
blr.addString(Const.CNCT_host, os.hostname(), Const.DEFAULT_ENCODING);
|
|
343
|
+
blr.addBytes([Const.CNCT_user_verification, 0]);
|
|
344
|
+
|
|
345
|
+
msg.addInt(Const.op_connect);
|
|
346
|
+
msg.addInt(Const.op_attach);
|
|
347
|
+
msg.addInt(Const.CONNECT_VERSION3);
|
|
348
|
+
msg.addInt(Const.ARCHITECTURE_GENERIC);
|
|
349
|
+
msg.addString(options.database || options.filename, Const.DEFAULT_ENCODING);
|
|
350
|
+
msg.addInt(Const.SUPPORTED_PROTOCOL.length); // Count of Protocol version understood count.
|
|
351
|
+
msg.addBlr(this._blr);
|
|
352
|
+
|
|
353
|
+
for (var protocol of Const.SUPPORTED_PROTOCOL) {
|
|
354
|
+
msg.addInt(protocol[0]); // Version
|
|
355
|
+
msg.addInt(protocol[1]); // Architecture
|
|
356
|
+
msg.addInt(protocol[2]); // Min type
|
|
357
|
+
if (protocol[0] >= Const.PROTOCOL_VERSION13 && options.wireCompression) {
|
|
358
|
+
msg.addInt(protocol[3] | Const.pflag_compress); // Max type with compress flag
|
|
359
|
+
} else {
|
|
360
|
+
msg.addInt(protocol[3]); // Max type
|
|
361
|
+
}
|
|
362
|
+
msg.addInt(protocol[4]); // Preference weight
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
var self = this;
|
|
366
|
+
function cb(err, ret) {
|
|
367
|
+
if (err) {
|
|
368
|
+
doError(err, callback);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check for pending accept from op_cond_accept flow
|
|
373
|
+
if (self._pendingAccept) {
|
|
374
|
+
ret = self._pendingAccept;
|
|
375
|
+
delete self._pendingAccept;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
self.accept = ret;
|
|
379
|
+
|
|
380
|
+
// Wire encryption: send op_crypt if SRP session key is available
|
|
381
|
+
if (ret.sessionKey && ret.protocolVersion >= Const.PROTOCOL_VERSION13 &&
|
|
382
|
+
options.wireCrypt !== Const.WIRE_CRYPT_DISABLE) {
|
|
383
|
+
var keyBuf = Buffer.from(ret.sessionKey.toString(16).padStart(40, '0'), 'hex');
|
|
384
|
+
self.sendOpCrypt('Arc4');
|
|
385
|
+
self._socket.enableEncryption(keyBuf);
|
|
386
|
+
self._pending.push('crypt');
|
|
387
|
+
self._queue.push(function(cryptErr) {
|
|
388
|
+
if (cryptErr) {
|
|
389
|
+
doError(cryptErr, callback);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (callback)
|
|
393
|
+
callback(undefined, ret);
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (callback)
|
|
399
|
+
callback(undefined, ret);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
403
|
+
console.log('[fb-debug] auth: op_connect sent plugin=%s t=%dms', pluginName, Date.now() - this._authStartTime);
|
|
404
|
+
}
|
|
405
|
+
this._queueEvent(cb);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
attach(options, callback, db) {
|
|
410
|
+
this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
|
|
411
|
+
|
|
412
|
+
var database = options.database || options.filename;
|
|
413
|
+
if (database == null || database.length === 0) {
|
|
414
|
+
doError(new Error('No database specified'), callback);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
var user = options.user || Const.DEFAULT_USER;
|
|
419
|
+
var password = options.password || Const.DEFAULT_PASSWORD;
|
|
420
|
+
var role = options.role;
|
|
421
|
+
var self = this;
|
|
422
|
+
var msg = this._msg;
|
|
423
|
+
var blr = this._blr;
|
|
424
|
+
msg.pos = 0;
|
|
425
|
+
blr.pos = 0;
|
|
426
|
+
|
|
427
|
+
blr.addByte(Const.isc_dpb_version1);
|
|
428
|
+
blr.addString(Const.isc_dpb_lc_ctype, options.encoding || 'UTF8', Const.DEFAULT_ENCODING);
|
|
429
|
+
|
|
430
|
+
// For Firebird 3+ (protocol 13+), add UTF-8 filename flag to ensure all DPB strings are handled with UTF-8
|
|
431
|
+
if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION13) {
|
|
432
|
+
blr.addByte(Const.isc_dpb_utf8_filename);
|
|
433
|
+
blr.addByte(0);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
blr.addString(Const.isc_dpb_user_name, user, Const.DEFAULT_ENCODING);
|
|
437
|
+
if (options.password && !this.accept.authData) {
|
|
438
|
+
if (this.accept.protocolVersion < Const.PROTOCOL_VERSION13) {
|
|
439
|
+
if (this.accept.protocolVersion === Const.PROTOCOL_VERSION10) {
|
|
440
|
+
blr.addString(Const.isc_dpb_password, password, Const.DEFAULT_ENCODING);
|
|
441
|
+
} else {
|
|
442
|
+
blr.addString(Const.isc_dpb_password_enc, crypt.crypt(password, Const.LEGACY_AUTH_SALT).substring(2), Const.DEFAULT_ENCODING);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (role)
|
|
448
|
+
blr.addString(Const.isc_dpb_sql_role_name, role, Const.DEFAULT_ENCODING);
|
|
449
|
+
|
|
450
|
+
blr.addBytes([Const.isc_dpb_process_id, 4]);
|
|
451
|
+
blr.addInt32(process.pid);
|
|
452
|
+
|
|
453
|
+
let processName = process.title || "";
|
|
454
|
+
blr.addString(Const.isc_dpb_process_name, processName.length > 255 ? processName.substring(processName.length - 255, processName.length) : processName, Const.DEFAULT_ENCODING);
|
|
455
|
+
|
|
456
|
+
if (this.accept.authData) {
|
|
457
|
+
blr.addString(Const.isc_dpb_specific_auth_data, this.accept.authData, Const.DEFAULT_ENCODING);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (options.sessionTimeZone) {
|
|
461
|
+
blr.addString(Const.isc_dpb_session_time_zone, options.sessionTimeZone, Const.DEFAULT_ENCODING);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
msg.addInt(Const.op_attach);
|
|
465
|
+
msg.addInt(0); // Database Object ID
|
|
466
|
+
msg.addString(database, Const.DEFAULT_ENCODING);
|
|
467
|
+
msg.addBlr(this._blr);
|
|
468
|
+
|
|
469
|
+
function cb(err, ret) {
|
|
470
|
+
if (err) {
|
|
471
|
+
doError(err, callback);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
self.dbhandle = ret.handle;
|
|
476
|
+
if (callback)
|
|
477
|
+
callback(undefined, ret);
|
|
478
|
+
if (!db)
|
|
479
|
+
ret.emit('attach', ret);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// For reconnect
|
|
483
|
+
if (db) {
|
|
484
|
+
db.connection = this;
|
|
485
|
+
cb.response = db;
|
|
486
|
+
} else {
|
|
487
|
+
cb.response = new Database(this);
|
|
488
|
+
cb.response.removeAllListeners('error');
|
|
489
|
+
cb.response.on('error', noop);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
this._queueEvent(cb);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
detach(callback) {
|
|
497
|
+
|
|
498
|
+
var self = this;
|
|
499
|
+
|
|
500
|
+
if (self._isClosed)
|
|
501
|
+
return;
|
|
502
|
+
|
|
503
|
+
self._isUsed = false;
|
|
504
|
+
self._isDetach = true;
|
|
505
|
+
|
|
506
|
+
var msg = self._msg;
|
|
507
|
+
|
|
508
|
+
msg.pos = 0;
|
|
509
|
+
msg.addInt(Const.op_detach);
|
|
510
|
+
msg.addInt(0); // Database Object ID
|
|
511
|
+
|
|
512
|
+
self._queueEvent(function(err, ret) {
|
|
513
|
+
clearTimeout(self._retry_connection_id);
|
|
514
|
+
delete(self.dbhandle);
|
|
515
|
+
if (callback)
|
|
516
|
+
callback(err, ret);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
createDatabase(options, callback) {
|
|
522
|
+
var database = options.database || options.filename;
|
|
523
|
+
if (database == null || database.length === 0) {
|
|
524
|
+
doError(new Error('No database specified'), callback);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
var user = options.user || Const.DEFAULT_USER;
|
|
529
|
+
var password = options.password || Const.DEFAULT_PASSWORD;
|
|
530
|
+
var pageSize = options.pageSize || Const.DEFAULT_PAGE_SIZE;
|
|
531
|
+
var role = options.role;
|
|
532
|
+
var blr = this._blr;
|
|
533
|
+
|
|
534
|
+
blr.pos = 0;
|
|
535
|
+
blr.addByte(Const.isc_dpb_version1);
|
|
536
|
+
blr.addString(Const.isc_dpb_set_db_charset, 'UTF8', Const.DEFAULT_ENCODING);
|
|
537
|
+
blr.addString(Const.isc_dpb_lc_ctype, 'UTF8', Const.DEFAULT_ENCODING);
|
|
538
|
+
|
|
539
|
+
// For Firebird 3+ (protocol 13+), add UTF-8 filename flag to ensure all DPB strings are handled with UTF-8
|
|
540
|
+
if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION13) {
|
|
541
|
+
blr.addByte(Const.isc_dpb_utf8_filename);
|
|
542
|
+
blr.addByte(0);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
blr.addString(Const.isc_dpb_user_name, user, Const.DEFAULT_ENCODING);
|
|
546
|
+
if (this.accept.protocolVersion < Const.PROTOCOL_VERSION13) {
|
|
547
|
+
if (this.accept.protocolVersion === Const.PROTOCOL_VERSION10) {
|
|
548
|
+
blr.addString(Const.isc_dpb_password, password, Const.DEFAULT_ENCODING);
|
|
549
|
+
} else {
|
|
550
|
+
blr.addString(Const.isc_dpb_password_enc, crypt.crypt(password, Const.LEGACY_AUTH_SALT).substring(2), Const.DEFAULT_ENCODING);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (role)
|
|
554
|
+
blr.addString(Const.isc_dpb_sql_role_name, role, Const.DEFAULT_ENCODING);
|
|
555
|
+
|
|
556
|
+
blr.addBytes([Const.isc_dpb_process_id, 4]);
|
|
557
|
+
blr.addInt32(process.pid);
|
|
558
|
+
|
|
559
|
+
let processName = process.title || "";
|
|
560
|
+
blr.addString(Const.isc_dpb_process_name, processName.length > 255 ? processName.substring(processName.length - 255, processName.length) : processName, Const.DEFAULT_ENCODING);
|
|
561
|
+
|
|
562
|
+
if (this.accept.authData) {
|
|
563
|
+
blr.addString(Const.isc_dpb_specific_auth_data, this.accept.authData, Const.DEFAULT_ENCODING);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (options.sessionTimeZone) {
|
|
567
|
+
blr.addString(Const.isc_dpb_session_time_zone, options.sessionTimeZone, Const.DEFAULT_ENCODING);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
blr.addNumeric(Const.isc_dpb_sql_dialect, 3);
|
|
571
|
+
blr.addNumeric(Const.isc_dpb_force_write, 1);
|
|
572
|
+
blr.addNumeric(Const.isc_dpb_overwrite, 1);
|
|
573
|
+
blr.addNumeric(Const.isc_dpb_page_size, pageSize);
|
|
574
|
+
|
|
575
|
+
var msg = this._msg;
|
|
576
|
+
msg.pos = 0;
|
|
577
|
+
msg.addInt(Const.op_create); // op_create
|
|
578
|
+
msg.addInt(0); // Database Object ID
|
|
579
|
+
msg.addString(database, Const.DEFAULT_ENCODING);
|
|
580
|
+
msg.addBlr(blr);
|
|
581
|
+
|
|
582
|
+
var self = this;
|
|
583
|
+
|
|
584
|
+
function cb(err, ret) {
|
|
585
|
+
|
|
586
|
+
if (ret)
|
|
587
|
+
self.dbhandle = ret.handle;
|
|
588
|
+
|
|
589
|
+
if (callback)
|
|
590
|
+
callback(err, ret);
|
|
591
|
+
|
|
592
|
+
if (!err && ret)
|
|
593
|
+
ret.emit('attach', ret);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
cb.response = new Database(this);
|
|
597
|
+
this._queueEvent(cb);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
dropDatabase(callback) {
|
|
602
|
+
var msg = this._msg;
|
|
603
|
+
msg.pos = 0;
|
|
604
|
+
|
|
605
|
+
msg.addInt(Const.op_drop_database);
|
|
606
|
+
msg.addInt(this.dbhandle);
|
|
607
|
+
|
|
608
|
+
var self = this;
|
|
609
|
+
this._queueEvent(function(err) {
|
|
610
|
+
self.detach(function() {
|
|
611
|
+
self.disconnect();
|
|
612
|
+
|
|
613
|
+
if (callback)
|
|
614
|
+
callback(err);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
throwClosed(callback) {
|
|
621
|
+
var err = new Error('Connection is closed.');
|
|
622
|
+
this.db.emit('error', err);
|
|
623
|
+
if (callback)
|
|
624
|
+
callback(err);
|
|
625
|
+
return this;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
startTransaction(options, callback) {
|
|
630
|
+
|
|
631
|
+
if (typeof(options) === 'function') {
|
|
632
|
+
var tmp = options;
|
|
633
|
+
options = callback;
|
|
634
|
+
callback = tmp;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Compatibility
|
|
638
|
+
if (Array.isArray(options)) {
|
|
639
|
+
options = {
|
|
640
|
+
isolation: options,
|
|
641
|
+
readOnly: (options === Const.ISOLATION_READ_COMMITTED_READ_ONLY),
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Default options
|
|
646
|
+
options = Object.assign({
|
|
647
|
+
autoCommit: false,
|
|
648
|
+
autoUndo: true,
|
|
649
|
+
isolation: Const.ISOLATION_READ_COMMITTED,
|
|
650
|
+
ignoreLimbo: false,
|
|
651
|
+
//lock: [],
|
|
652
|
+
readOnly: false,
|
|
653
|
+
wait: true,
|
|
654
|
+
waitTimeout: 0,
|
|
655
|
+
}, options);
|
|
656
|
+
|
|
657
|
+
if (this._isClosed)
|
|
658
|
+
return this.throwClosed(callback);
|
|
659
|
+
|
|
660
|
+
// for auto detach
|
|
661
|
+
this._pending.push('startTransaction');
|
|
662
|
+
|
|
663
|
+
var blr = this._blr;
|
|
664
|
+
var msg = this._msg;
|
|
665
|
+
|
|
666
|
+
blr.pos = 0;
|
|
667
|
+
msg.pos = 0;
|
|
668
|
+
|
|
669
|
+
blr.addByte(Const.isc_tpb_version3);
|
|
670
|
+
blr.addBytes(options.isolation);
|
|
671
|
+
blr.addByte(options.readOnly ? Const.isc_tpb_read : Const.isc_tpb_write);
|
|
672
|
+
if (options.wait) {
|
|
673
|
+
blr.addByte(Const.isc_tpb_wait);
|
|
674
|
+
|
|
675
|
+
if (options.waitTimeout) {
|
|
676
|
+
blr.addNumeric(Const.isc_tpb_lock_timeout, options.waitTimeout);
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
blr.addByte(Const.isc_tpb_nowait);
|
|
680
|
+
}
|
|
681
|
+
if (!options.autoUndo) {
|
|
682
|
+
blr.addByte(Const.isc_tpb_no_auto_undo);
|
|
683
|
+
}
|
|
684
|
+
if (options.autoCommit) {
|
|
685
|
+
blr.addByte(Const.isc_tpb_autocommit);
|
|
686
|
+
}
|
|
687
|
+
if (options.ignoreLimbo) {
|
|
688
|
+
blr.addByte(Const.isc_tpb_ignore_limbo);
|
|
689
|
+
}
|
|
690
|
+
// TODO
|
|
691
|
+
/*if (options.lock.length) {
|
|
692
|
+
for (let table of options.lock) {
|
|
693
|
+
const lockMode = table.write ? Const.isc_tpb_lock_write : Const.isc_tpb_lock_read;
|
|
694
|
+
const lockType = table.protected ? Const.isc_tpb_protected : Const.isc_tpb_shared;
|
|
695
|
+
|
|
696
|
+
blr.addString(lockMode, table.table || table, Const.DEFAULT_ENCODING);
|
|
697
|
+
blr.addByte(lockType);
|
|
698
|
+
}
|
|
699
|
+
}*/
|
|
700
|
+
|
|
701
|
+
msg.addInt(Const.op_transaction);
|
|
702
|
+
msg.addInt(this.dbhandle);
|
|
703
|
+
msg.addBlr(blr);
|
|
704
|
+
callback.response = new Transaction(this);
|
|
705
|
+
|
|
706
|
+
this.db.emit('transaction', options);
|
|
707
|
+
this._queueEvent(callback);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
commit(transaction, callback) {
|
|
712
|
+
|
|
713
|
+
if (this._isClosed)
|
|
714
|
+
return this.throwClosed(callback);
|
|
715
|
+
|
|
716
|
+
// for auto detach
|
|
717
|
+
this._pending.push('commit');
|
|
718
|
+
|
|
719
|
+
var msg = this._msg;
|
|
720
|
+
msg.pos = 0;
|
|
721
|
+
msg.addInt(Const.op_commit);
|
|
722
|
+
msg.addInt(transaction.handle);
|
|
723
|
+
this.db.emit('commit');
|
|
724
|
+
this._queueEvent(callback);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
rollback(transaction, callback) {
|
|
729
|
+
|
|
730
|
+
if (this._isClosed)
|
|
731
|
+
return this.throwClosed(callback);
|
|
732
|
+
|
|
733
|
+
// for auto detach
|
|
734
|
+
this._pending.push('rollback');
|
|
735
|
+
|
|
736
|
+
var msg = this._msg;
|
|
737
|
+
msg.pos = 0;
|
|
738
|
+
msg.addInt(Const.op_rollback);
|
|
739
|
+
msg.addInt(transaction.handle);
|
|
740
|
+
this.db.emit('rollback');
|
|
741
|
+
this._queueEvent(callback);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
commitRetaining(transaction, callback) {
|
|
746
|
+
|
|
747
|
+
if (this._isClosed)
|
|
748
|
+
throw new Error('Connection is closed.');
|
|
749
|
+
|
|
750
|
+
// for auto detach
|
|
751
|
+
this._pending.push('commitRetaining');
|
|
752
|
+
|
|
753
|
+
var msg = this._msg;
|
|
754
|
+
msg.pos = 0;
|
|
755
|
+
msg.addInt(Const.op_commit_retaining);
|
|
756
|
+
msg.addInt(transaction.handle);
|
|
757
|
+
this._queueEvent(callback);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
rollbackRetaining(transaction, callback) {
|
|
762
|
+
|
|
763
|
+
if (this._isClosed)
|
|
764
|
+
return this.throwClosed(callback);
|
|
765
|
+
|
|
766
|
+
// for auto detach
|
|
767
|
+
this._pending.push('rollbackRetaining');
|
|
768
|
+
|
|
769
|
+
var msg = this._msg;
|
|
770
|
+
msg.pos = 0;
|
|
771
|
+
msg.addInt(Const.op_rollback_retaining);
|
|
772
|
+
msg.addInt(transaction.handle);
|
|
773
|
+
this._queueEvent(callback);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
allocateStatement(callback) {
|
|
778
|
+
|
|
779
|
+
if (this._isClosed)
|
|
780
|
+
return this.throwClosed(callback);
|
|
781
|
+
|
|
782
|
+
// for auto detach
|
|
783
|
+
this._pending.push('allocateStatement');
|
|
784
|
+
|
|
785
|
+
var msg = this._msg;
|
|
786
|
+
msg.pos = 0;
|
|
787
|
+
msg.addInt(Const.op_allocate_statement);
|
|
788
|
+
msg.addInt(this.dbhandle);
|
|
789
|
+
callback.response = new Statement(this);
|
|
790
|
+
this._queueEvent(callback);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
dropStatement(statement, callback) {
|
|
795
|
+
|
|
796
|
+
if (this._isClosed)
|
|
797
|
+
return this.throwClosed(callback);
|
|
798
|
+
|
|
799
|
+
// for auto detach
|
|
800
|
+
this._pending.push('dropStatement');
|
|
801
|
+
|
|
802
|
+
var msg = this._msg;
|
|
803
|
+
msg.pos = 0;
|
|
804
|
+
msg.addInt(Const.op_free_statement);
|
|
805
|
+
msg.addInt(statement.handle);
|
|
806
|
+
msg.addInt(Const.DSQL_drop);
|
|
807
|
+
|
|
808
|
+
this._queueEvent(callback, true);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
closeStatement(statement, callback) {
|
|
813
|
+
|
|
814
|
+
if (this._isClosed)
|
|
815
|
+
return this.throwClosed(callback);
|
|
816
|
+
|
|
817
|
+
// for auto detach
|
|
818
|
+
this._pending.push('closeStatement');
|
|
819
|
+
|
|
820
|
+
var msg = this._msg;
|
|
821
|
+
msg.pos = 0;
|
|
822
|
+
msg.addInt(Const.op_free_statement);
|
|
823
|
+
msg.addInt(statement.handle);
|
|
824
|
+
msg.addInt(Const.DSQL_close);
|
|
825
|
+
|
|
826
|
+
this._queueEvent(callback, true);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
allocateAndPrepareStatement(transaction, query, plan, callback) {
|
|
831
|
+
var self = this;
|
|
832
|
+
var mainCallback = function(err, ret) {
|
|
833
|
+
if (!err) {
|
|
834
|
+
mainCallback.response.handle = ret.handle;
|
|
835
|
+
describe(ret.buffer, mainCallback.response);
|
|
836
|
+
mainCallback.response.query = query;
|
|
837
|
+
self.db.emit('query', query);
|
|
838
|
+
ret = mainCallback.response;
|
|
839
|
+
self._setcachedquery(query, ret);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (callback)
|
|
843
|
+
callback(err, ret);
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// for auto detach
|
|
847
|
+
this._pending.push('allocateAndPrepareStatement');
|
|
848
|
+
|
|
849
|
+
var msg = this._msg;
|
|
850
|
+
var blr = this._blr;
|
|
851
|
+
|
|
852
|
+
msg.pos = 0;
|
|
853
|
+
blr.pos = 0;
|
|
854
|
+
|
|
855
|
+
msg.addInt(Const.op_allocate_statement);
|
|
856
|
+
msg.addInt(this.dbhandle);
|
|
857
|
+
mainCallback.lazy_count = 1;
|
|
858
|
+
|
|
859
|
+
blr.addBytes(Const.DESCRIBE);
|
|
860
|
+
if (plan)
|
|
861
|
+
blr.addByte(Const.isc_info_sql_get_plan);
|
|
862
|
+
|
|
863
|
+
msg.addInt(Const.op_prepare_statement);
|
|
864
|
+
msg.addInt(transaction.handle);
|
|
865
|
+
msg.addInt(0xFFFF);
|
|
866
|
+
msg.addInt(3); // dialect = 3
|
|
867
|
+
msg.addString(query, Const.DEFAULT_ENCODING);
|
|
868
|
+
msg.addBlr(blr);
|
|
869
|
+
msg.addInt(65535); // buffer_length
|
|
870
|
+
mainCallback.lazy_count += 1;
|
|
871
|
+
|
|
872
|
+
mainCallback.response = new Statement(this);
|
|
873
|
+
this._queueEvent(mainCallback);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
prepare(transaction, query, plan, callback) {
|
|
878
|
+
var self = this;
|
|
879
|
+
|
|
880
|
+
if (this.accept.protocolMinimumType === Const.ptype_lazy_send) { // V11 Statement or higher
|
|
881
|
+
self.allocateAndPrepareStatement(transaction, query, plan, callback);
|
|
882
|
+
} else { // V10 Statement
|
|
883
|
+
self.allocateStatement(function (err, statement) {
|
|
884
|
+
if (err) {
|
|
885
|
+
doError(err, callback);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
self.prepareStatement(transaction, statement, query, plan, callback);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
prepareStatement(transaction, statement, query, plan, callback) {
|
|
897
|
+
|
|
898
|
+
if (this._isClosed)
|
|
899
|
+
return this.throwClosed(callback);
|
|
900
|
+
|
|
901
|
+
var msg = this._msg;
|
|
902
|
+
var blr = this._blr;
|
|
903
|
+
|
|
904
|
+
msg.pos = 0;
|
|
905
|
+
blr.pos = 0;
|
|
906
|
+
|
|
907
|
+
if (plan instanceof Function) {
|
|
908
|
+
callback = plan;
|
|
909
|
+
plan = false;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
blr.addBytes(Const.DESCRIBE);
|
|
913
|
+
|
|
914
|
+
if (plan)
|
|
915
|
+
blr.addByte(Const.isc_info_sql_get_plan);
|
|
916
|
+
|
|
917
|
+
msg.addInt(Const.op_prepare_statement);
|
|
918
|
+
msg.addInt(transaction.handle);
|
|
919
|
+
msg.addInt(statement.handle);
|
|
920
|
+
msg.addInt(3); // dialect = 3
|
|
921
|
+
msg.addString(query, Const.DEFAULT_ENCODING);
|
|
922
|
+
msg.addBlr(blr);
|
|
923
|
+
msg.addInt(65535); // buffer_length
|
|
924
|
+
|
|
925
|
+
var self = this;
|
|
926
|
+
this._queueEvent(function(err, ret) {
|
|
927
|
+
|
|
928
|
+
if (!err) {
|
|
929
|
+
describe(ret.buffer, statement);
|
|
930
|
+
statement.query = query;
|
|
931
|
+
self.db.emit('query', query);
|
|
932
|
+
ret = statement;
|
|
933
|
+
self._setcachedquery(query, ret);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (callback)
|
|
937
|
+
callback(err, ret);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
executeStatement(transaction, statement, params, callback, custom) {
|
|
945
|
+
|
|
946
|
+
if (this._isClosed)
|
|
947
|
+
return this.throwClosed(callback);
|
|
948
|
+
|
|
949
|
+
// for auto detach
|
|
950
|
+
this._pending.push('executeStatement');
|
|
951
|
+
|
|
952
|
+
if (params instanceof Function) {
|
|
953
|
+
callback = params;
|
|
954
|
+
params = undefined;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
var self = this;
|
|
958
|
+
|
|
959
|
+
var op = Const.op_execute;
|
|
960
|
+
if (
|
|
961
|
+
this.accept.protocolVersion >= Const.PROTOCOL_VERSION13 &&
|
|
962
|
+
statement.type === Const.isc_info_sql_stmt_exec_procedure &&
|
|
963
|
+
statement.output.length
|
|
964
|
+
) {
|
|
965
|
+
op = Const.op_execute2;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function PrepareParams(params, input, callback) {
|
|
969
|
+
|
|
970
|
+
var value, meta;
|
|
971
|
+
var ret = new Array(params.length);
|
|
972
|
+
|
|
973
|
+
function putBlobData(index, value, callback) {
|
|
974
|
+
|
|
975
|
+
self.createBlob2(transaction, function(err, blob) {
|
|
976
|
+
|
|
977
|
+
var b;
|
|
978
|
+
var isStream = value.readable;
|
|
979
|
+
|
|
980
|
+
if (Buffer.isBuffer(value))
|
|
981
|
+
b = value;
|
|
982
|
+
else if (typeof(value) === 'string')
|
|
983
|
+
b = Buffer.from(value, Const.DEFAULT_ENCODING);
|
|
984
|
+
else if (!isStream)
|
|
985
|
+
b = Buffer.from(JSON.stringify(value), Const.DEFAULT_ENCODING);
|
|
986
|
+
|
|
987
|
+
// Use configured transfer size or default to 1024
|
|
988
|
+
var chunkSize = self.options.blobChunkSize || 1024;
|
|
989
|
+
|
|
990
|
+
if (Buffer.isBuffer(b)) {
|
|
991
|
+
bufferReader(b, chunkSize, function (b, next) {
|
|
992
|
+
self.batchSegments(blob, b, next);
|
|
993
|
+
}, function() {
|
|
994
|
+
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
995
|
+
self.closeBlob(blob, callback, false);
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
var isReading = false;
|
|
1001
|
+
var isEnd = false;
|
|
1002
|
+
|
|
1003
|
+
value.on('data', function(chunk) {
|
|
1004
|
+
// Optimization: If chunk is smaller than transfer size, send directly
|
|
1005
|
+
if (chunk.length <= chunkSize) {
|
|
1006
|
+
self.batchSegments(blob, chunk, function () {
|
|
1007
|
+
if (isEnd && !isReading) {
|
|
1008
|
+
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
1009
|
+
self.closeBlob(blob, callback, false);
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
value.pause();
|
|
1016
|
+
isReading = true;
|
|
1017
|
+
bufferReader(chunk, chunkSize, function (b, next) {
|
|
1018
|
+
self.batchSegments(blob, b, next);
|
|
1019
|
+
}, function() {
|
|
1020
|
+
isReading = false;
|
|
1021
|
+
|
|
1022
|
+
if (isEnd) {
|
|
1023
|
+
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
1024
|
+
self.closeBlob(blob, callback, false);
|
|
1025
|
+
} else
|
|
1026
|
+
value.resume();
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
value.on('end', function() {
|
|
1031
|
+
isEnd = true;
|
|
1032
|
+
if (isReading)
|
|
1033
|
+
return;
|
|
1034
|
+
// If we are not currently reading (paused), close immediately
|
|
1035
|
+
// If we are reading, the callback in batchSegments/bufferReader will handle closure
|
|
1036
|
+
if (!isReading) {
|
|
1037
|
+
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
1038
|
+
self.closeBlob(blob, callback, false);
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function step(i) {
|
|
1045
|
+
if (i === params.length) {
|
|
1046
|
+
callback(ret);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
value = params[i];
|
|
1051
|
+
meta = input[i];
|
|
1052
|
+
|
|
1053
|
+
if (value === null || value === undefined) {
|
|
1054
|
+
switch (meta.type) {
|
|
1055
|
+
case Const.SQL_VARYING:
|
|
1056
|
+
case Const.SQL_NULL:
|
|
1057
|
+
case Const.SQL_TEXT:
|
|
1058
|
+
ret[i] = new Xsql.SQLParamString(null);
|
|
1059
|
+
break;
|
|
1060
|
+
case Const.SQL_DOUBLE:
|
|
1061
|
+
case Const.SQL_FLOAT:
|
|
1062
|
+
case Const.SQL_D_FLOAT:
|
|
1063
|
+
ret[i] = new Xsql.SQLParamDouble(null);
|
|
1064
|
+
break;
|
|
1065
|
+
case Const.SQL_TYPE_DATE:
|
|
1066
|
+
case Const.SQL_TYPE_TIME:
|
|
1067
|
+
case Const.SQL_TIMESTAMP:
|
|
1068
|
+
case Const.SQL_TIME_TZ:
|
|
1069
|
+
case Const.SQL_TIME_TZ_EX:
|
|
1070
|
+
case Const.SQL_TIMESTAMP_TZ:
|
|
1071
|
+
case Const.SQL_TIMESTAMP_TZ_EX:
|
|
1072
|
+
ret[i] = new Xsql.SQLParamDate(null);
|
|
1073
|
+
break;
|
|
1074
|
+
case Const.SQL_BLOB:
|
|
1075
|
+
case Const.SQL_ARRAY:
|
|
1076
|
+
case Const.SQL_QUAD:
|
|
1077
|
+
ret[i] = new Xsql.SQLParamQuad(null);
|
|
1078
|
+
break;
|
|
1079
|
+
case Const.SQL_LONG:
|
|
1080
|
+
case Const.SQL_SHORT:
|
|
1081
|
+
case Const.SQL_INT64:
|
|
1082
|
+
case Const.SQL_BOOLEAN:
|
|
1083
|
+
ret[i] = new Xsql.SQLParamInt(null);
|
|
1084
|
+
break;
|
|
1085
|
+
default:
|
|
1086
|
+
ret[i] = null;
|
|
1087
|
+
}
|
|
1088
|
+
step(i + 1);
|
|
1089
|
+
} else {
|
|
1090
|
+
switch (meta.type) {
|
|
1091
|
+
case Const.SQL_BLOB:
|
|
1092
|
+
putBlobData(i, value, function() { step(i + 1); });
|
|
1093
|
+
break;
|
|
1094
|
+
|
|
1095
|
+
case Const.SQL_TIMESTAMP:
|
|
1096
|
+
case Const.SQL_TYPE_DATE:
|
|
1097
|
+
case Const.SQL_TYPE_TIME:
|
|
1098
|
+
case Const.SQL_TIME_TZ:
|
|
1099
|
+
case Const.SQL_TIME_TZ_EX:
|
|
1100
|
+
case Const.SQL_TIMESTAMP_TZ:
|
|
1101
|
+
case Const.SQL_TIMESTAMP_TZ_EX:
|
|
1102
|
+
if (value instanceof Date)
|
|
1103
|
+
ret[i] = new Xsql.SQLParamDate(value);
|
|
1104
|
+
else if (typeof(value) === 'string')
|
|
1105
|
+
ret[i] = new Xsql.SQLParamDate(parseDate(value));
|
|
1106
|
+
else
|
|
1107
|
+
ret[i] = new Xsql.SQLParamDate(new Date(value));
|
|
1108
|
+
|
|
1109
|
+
step(i + 1);
|
|
1110
|
+
break;
|
|
1111
|
+
|
|
1112
|
+
default:
|
|
1113
|
+
switch (typeof value) {
|
|
1114
|
+
case 'bigint':
|
|
1115
|
+
ret[i] = new Xsql.SQLParamInt128(value);
|
|
1116
|
+
break;
|
|
1117
|
+
case 'number':
|
|
1118
|
+
if (value % 1 === 0) {
|
|
1119
|
+
if (value >= Const.MIN_INT && value <= Const.MAX_INT)
|
|
1120
|
+
ret[i] = new Xsql.SQLParamInt(value);
|
|
1121
|
+
else
|
|
1122
|
+
ret[i] = new Xsql.SQLParamInt64(value);
|
|
1123
|
+
} else
|
|
1124
|
+
ret[i] = new Xsql.SQLParamDouble(value);
|
|
1125
|
+
break;
|
|
1126
|
+
case 'string':
|
|
1127
|
+
ret[i] = new Xsql.SQLParamString(value);
|
|
1128
|
+
break;
|
|
1129
|
+
case 'boolean':
|
|
1130
|
+
ret[i] = new Xsql.SQLParamBool(value);
|
|
1131
|
+
break;
|
|
1132
|
+
default:
|
|
1133
|
+
//throw new Error('Unexpected parametter: ' + JSON.stringify(params) + ' - ' + JSON.stringify(input));
|
|
1134
|
+
ret[i] = new Xsql.SQLParamString(value.toString());
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
step(i + 1);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
step(0);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
var input = statement.input;
|
|
1146
|
+
|
|
1147
|
+
if (input.length) {
|
|
1148
|
+
|
|
1149
|
+
if (!(params instanceof Array)) {
|
|
1150
|
+
if (params !== undefined)
|
|
1151
|
+
params = [params];
|
|
1152
|
+
else
|
|
1153
|
+
params = [];
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (params.length !== input.length) {
|
|
1157
|
+
self._pending.pop();
|
|
1158
|
+
callback(new Error('Expected parameters: (params=' + params.length + ' vs. expected=' + input.length + ') - ' + statement.query));
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
PrepareParams(params, input, function(prms) {
|
|
1163
|
+
self.sendExecute(op, statement, transaction, callback, prms);
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
this.sendExecute(op, statement, transaction, callback);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
sendExecute(op, statement, transaction, callback, parameters) {
|
|
1174
|
+
var msg = this._msg;
|
|
1175
|
+
var blr = this._blr;
|
|
1176
|
+
msg.pos = 0;
|
|
1177
|
+
blr.pos = 0;
|
|
1178
|
+
|
|
1179
|
+
msg.addInt(op);
|
|
1180
|
+
msg.addInt(statement.handle);
|
|
1181
|
+
msg.addInt(transaction.handle);
|
|
1182
|
+
|
|
1183
|
+
if (parameters && parameters.length) {
|
|
1184
|
+
CalcBlr(blr, parameters);
|
|
1185
|
+
msg.addBlr(blr); // params blr
|
|
1186
|
+
msg.addInt(0); // message number
|
|
1187
|
+
msg.addInt(1); // param count
|
|
1188
|
+
|
|
1189
|
+
if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION13) {
|
|
1190
|
+
// start with null indicator bitmap
|
|
1191
|
+
var nullBits = new BitSet();
|
|
1192
|
+
|
|
1193
|
+
for (var i = 0; i < parameters.length; i++) {
|
|
1194
|
+
nullBits.set(i, (parameters[i].value === null) & 1);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
var nullBuffer = nullBits.toBuffer();
|
|
1198
|
+
var requireBytes = Math.floor((parameters.length + 7) / 8);
|
|
1199
|
+
var remainingBytes = requireBytes - nullBuffer.length;
|
|
1200
|
+
|
|
1201
|
+
if (nullBuffer.length) {
|
|
1202
|
+
msg.addBuffer(nullBuffer);
|
|
1203
|
+
}
|
|
1204
|
+
if (remainingBytes > 0) {
|
|
1205
|
+
msg.addBuffer(Buffer.alloc(remainingBytes));
|
|
1206
|
+
}
|
|
1207
|
+
msg.addAlignment(requireBytes);
|
|
1208
|
+
|
|
1209
|
+
for(var i = 0; i < parameters.length; i++) {
|
|
1210
|
+
if (parameters[i].value !== null) {
|
|
1211
|
+
parameters[i].encode(msg);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
} else {
|
|
1215
|
+
for(var i = 0; i < parameters.length; i++) {
|
|
1216
|
+
parameters[i].encode(msg);
|
|
1217
|
+
if (parameters[i].value !== null) {
|
|
1218
|
+
msg.addInt(0);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
} else {
|
|
1223
|
+
msg.addBlr(blr); // empty
|
|
1224
|
+
msg.addInt(0); // message number
|
|
1225
|
+
msg.addInt(0); // param count
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (op === Const.op_execute2) {
|
|
1229
|
+
var outputBlr = new BlrWriter(32);
|
|
1230
|
+
|
|
1231
|
+
if (statement.output && statement.output.length) {
|
|
1232
|
+
CalcBlr(outputBlr, statement.output);
|
|
1233
|
+
msg.addBlr(outputBlr);
|
|
1234
|
+
} else {
|
|
1235
|
+
msg.addBlr(outputBlr); // empty
|
|
1236
|
+
}
|
|
1237
|
+
msg.addInt(0); // out_message_number = out_message_type
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION16) {
|
|
1241
|
+
// TODO impl statement timout
|
|
1242
|
+
msg.addInt(statement.options?.timeout || 0); // p_sqldata_timeout
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
callback.statement = statement;
|
|
1246
|
+
this._queueEvent(callback);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
fetch(statement, transaction, count, callback) {
|
|
1253
|
+
|
|
1254
|
+
var msg = this._msg;
|
|
1255
|
+
var blr = this._blr;
|
|
1256
|
+
|
|
1257
|
+
msg.pos = 0;
|
|
1258
|
+
blr.pos = 0;
|
|
1259
|
+
|
|
1260
|
+
if (count instanceof Function) {
|
|
1261
|
+
callback = count;
|
|
1262
|
+
count = Const.DEFAULT_FETCHSIZE;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
msg.addInt(Const.op_fetch);
|
|
1266
|
+
msg.addInt(statement.handle);
|
|
1267
|
+
CalcBlr(blr, statement.output);
|
|
1268
|
+
msg.addBlr(blr);
|
|
1269
|
+
msg.addInt(0); // message number
|
|
1270
|
+
msg.addInt(count || Const.DEFAULT_FETCHSIZE); // fetch count
|
|
1271
|
+
|
|
1272
|
+
callback.statement = statement;
|
|
1273
|
+
this._queueEvent(callback);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
fetchAll(statement, transaction, callback) {
|
|
1278
|
+
const self = this;
|
|
1279
|
+
const custom = statement.options || {};
|
|
1280
|
+
const asStream = custom.asStream && custom.on;
|
|
1281
|
+
const data = asStream ? null : [];
|
|
1282
|
+
let streamIndex = 0;
|
|
1283
|
+
const loop = (err, ret) => {
|
|
1284
|
+
if (err) {
|
|
1285
|
+
callback(err);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (ret && ret.data && ret.data.length) {
|
|
1290
|
+
const arrPromise = (ret.arrBlob || []).map((value) => value(transaction));
|
|
1291
|
+
|
|
1292
|
+
Promise.all(arrPromise).then((arrBlob) => {
|
|
1293
|
+
for (let i = 0; i < arrBlob.length; i++) {
|
|
1294
|
+
const blob = arrBlob[i];
|
|
1295
|
+
ret.data[blob.row][blob.column] = blob.value;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
doSynchronousLoop(ret.data, (row, _i, next) => {
|
|
1299
|
+
const pos = asStream ? streamIndex++ : (data.push(row) - 1);
|
|
1300
|
+
if (asStream) {
|
|
1301
|
+
executeStreamRow(custom, row, pos, statement.output, next);
|
|
1302
|
+
} else {
|
|
1303
|
+
next();
|
|
1304
|
+
}
|
|
1305
|
+
}, (streamErr) => {
|
|
1306
|
+
if (streamErr) {
|
|
1307
|
+
callback(streamErr);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (ret.fetched) {
|
|
1312
|
+
callback(undefined, data || []);
|
|
1313
|
+
} else {
|
|
1314
|
+
self.fetch(statement, transaction, Const.DEFAULT_FETCHSIZE, loop);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
}).catch(callback);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (ret && ret.fetched) {
|
|
1322
|
+
callback(undefined, data || []);
|
|
1323
|
+
} else {
|
|
1324
|
+
self.fetch(statement, transaction, Const.DEFAULT_FETCHSIZE, loop);
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
this.fetch(statement, transaction, Const.DEFAULT_FETCHSIZE, loop);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
openBlob(blob, transaction, callback) {
|
|
1334
|
+
var msg = this._msg;
|
|
1335
|
+
msg.pos = 0;
|
|
1336
|
+
msg.addInt(Const.op_open_blob);
|
|
1337
|
+
msg.addInt(transaction.handle);
|
|
1338
|
+
msg.addQuad(blob);
|
|
1339
|
+
this._queueEvent(callback);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
closeBlob(blob, callback, defer = true) {
|
|
1344
|
+
var msg = this._msg;
|
|
1345
|
+
msg.pos = 0;
|
|
1346
|
+
msg.addInt(Const.op_close_blob);
|
|
1347
|
+
msg.addInt(blob.handle);
|
|
1348
|
+
this._queueEvent(callback, defer);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
getSegment(blob, callback) {
|
|
1353
|
+
var msg = this._msg;
|
|
1354
|
+
msg.pos = 0;
|
|
1355
|
+
msg.addInt(Const.op_get_segment);
|
|
1356
|
+
msg.addInt(blob.handle);
|
|
1357
|
+
msg.addInt(1024); // buffer length
|
|
1358
|
+
msg.addInt(0); // ???
|
|
1359
|
+
this._queueEvent(callback);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
createBlob2(transaction, callback) {
|
|
1364
|
+
var msg = this._msg;
|
|
1365
|
+
msg.pos = 0;
|
|
1366
|
+
msg.addInt(Const.op_create_blob2);
|
|
1367
|
+
msg.addInt(0);
|
|
1368
|
+
msg.addInt(transaction.handle);
|
|
1369
|
+
msg.addInt(0);
|
|
1370
|
+
msg.addInt(0);
|
|
1371
|
+
this._queueEvent(callback);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
batchSegments(blob, buffer, callback) {
|
|
1376
|
+
var msg = this._msg;
|
|
1377
|
+
var blr = this._blr;
|
|
1378
|
+
msg.pos = 0;
|
|
1379
|
+
blr.pos = 0;
|
|
1380
|
+
msg.addInt(Const.op_batch_segments);
|
|
1381
|
+
msg.addInt(blob.handle);
|
|
1382
|
+
msg.addInt(buffer.length + 2);
|
|
1383
|
+
blr.addBuffer(buffer);
|
|
1384
|
+
msg.addBlr(blr);
|
|
1385
|
+
this._queueEvent(callback);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
svcattach(options, callback, svc) {
|
|
1390
|
+
this._lowercase_keys = options.lowercase_keys || Const.DEFAULT_LOWERCASE_KEYS;
|
|
1391
|
+
var database = options.database || options.filename;
|
|
1392
|
+
var user = options.user || Const.DEFAULT_USER;
|
|
1393
|
+
var password = options.password || Const.DEFAULT_PASSWORD;
|
|
1394
|
+
var role = options.role;
|
|
1395
|
+
var msg = this._msg;
|
|
1396
|
+
var blr = this._blr;
|
|
1397
|
+
msg.pos = 0;
|
|
1398
|
+
blr.pos = 0;
|
|
1399
|
+
|
|
1400
|
+
blr.addBytes([Const.isc_dpb_version2, Const.isc_dpb_version2]);
|
|
1401
|
+
blr.addString(Const.isc_dpb_lc_ctype, 'UTF8', Const.DEFAULT_ENCODING);
|
|
1402
|
+
|
|
1403
|
+
// For Firebird 3+ (protocol 13+), add UTF-8 filename flag to ensure all DPB strings are handled with UTF-8
|
|
1404
|
+
if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION13) {
|
|
1405
|
+
blr.addByte(Const.isc_dpb_utf8_filename);
|
|
1406
|
+
blr.addByte(0);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
blr.addString(Const.isc_dpb_user_name, user, Const.DEFAULT_ENCODING);
|
|
1410
|
+
blr.addString(Const.isc_dpb_password, password, Const.DEFAULT_ENCODING);
|
|
1411
|
+
blr.addByte(Const.isc_dpb_dummy_packet_interval);
|
|
1412
|
+
blr.addByte(4);
|
|
1413
|
+
blr.addBytes([120, 10, 0, 0]); // FROM DOT NET PROVIDER
|
|
1414
|
+
if (role)
|
|
1415
|
+
blr.addString(Const.isc_dpb_sql_role_name, role, Const.DEFAULT_ENCODING);
|
|
1416
|
+
|
|
1417
|
+
msg.addInt(Const.op_service_attach);
|
|
1418
|
+
msg.addInt(0);
|
|
1419
|
+
msg.addString(Const.DEFAULT_SVC_NAME, Const.DEFAULT_ENCODING); // only local for moment
|
|
1420
|
+
msg.addBlr(this._blr);
|
|
1421
|
+
|
|
1422
|
+
var self = this;
|
|
1423
|
+
|
|
1424
|
+
function cb(err, ret) {
|
|
1425
|
+
|
|
1426
|
+
if (err) {
|
|
1427
|
+
doError(err, callback);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
self.svchandle = ret.handle;
|
|
1432
|
+
if (callback)
|
|
1433
|
+
callback(undefined, ret);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// For reconnect
|
|
1437
|
+
if (svc) {
|
|
1438
|
+
svc.connection = this;
|
|
1439
|
+
cb.response = svc;
|
|
1440
|
+
} else {
|
|
1441
|
+
cb.response = new ServiceManager(this);
|
|
1442
|
+
cb.response.removeAllListeners('error');
|
|
1443
|
+
cb.response.on('error', noop);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
this._queueEvent(cb);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
svcstart(spbaction, callback) {
|
|
1451
|
+
var msg = this._msg;
|
|
1452
|
+
var blr = this._blr;
|
|
1453
|
+
msg.pos = 0;
|
|
1454
|
+
msg.addInt(Const.op_service_start);
|
|
1455
|
+
msg.addInt(this.svchandle);
|
|
1456
|
+
msg.addInt(0)
|
|
1457
|
+
msg.addBlr(spbaction);
|
|
1458
|
+
this._queueEvent(callback);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
svcquery(spbquery, resultbuffersize, timeout,callback) {
|
|
1463
|
+
if (resultbuffersize > Const.MAX_BUFFER_SIZE) {
|
|
1464
|
+
doError(new Error('Buffer is too big'), callback);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
var msg = this._msg;
|
|
1469
|
+
var blr = this._blr;
|
|
1470
|
+
msg.pos = 0;
|
|
1471
|
+
blr.pos = 0;
|
|
1472
|
+
blr.addByte(Const.isc_spb_current_version);
|
|
1473
|
+
//blr.addByteInt32(Const.isc_info_svc_timeout, timeout);
|
|
1474
|
+
msg.addInt(Const.op_service_info);
|
|
1475
|
+
msg.addInt(this.svchandle);
|
|
1476
|
+
msg.addInt(0);
|
|
1477
|
+
msg.addBlr(blr);
|
|
1478
|
+
blr.pos = 0
|
|
1479
|
+
blr.addBytes(spbquery);
|
|
1480
|
+
msg.addBlr(blr);
|
|
1481
|
+
msg.addInt(resultbuffersize);
|
|
1482
|
+
this._queueEvent(callback);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
|
|
1486
|
+
svcdetach(callback) {
|
|
1487
|
+
var self = this;
|
|
1488
|
+
|
|
1489
|
+
if (self._isClosed)
|
|
1490
|
+
return;
|
|
1491
|
+
|
|
1492
|
+
self._isUsed = false;
|
|
1493
|
+
self._isDetach = true;
|
|
1494
|
+
|
|
1495
|
+
var msg = self._msg;
|
|
1496
|
+
|
|
1497
|
+
msg.pos = 0;
|
|
1498
|
+
msg.addInt(Const.op_service_detach);
|
|
1499
|
+
msg.addInt(this.svchandle); // Database Object ID
|
|
1500
|
+
|
|
1501
|
+
self._queueEvent(function (err, ret) {
|
|
1502
|
+
delete (self.svchandle);
|
|
1503
|
+
if (callback)
|
|
1504
|
+
callback(err, ret);
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
auxConnection(callback) {
|
|
1511
|
+
var self = this;
|
|
1512
|
+
if (self._isClosed)
|
|
1513
|
+
return this.throwClosed(callback);
|
|
1514
|
+
var msg = self._msg;
|
|
1515
|
+
msg.pos = 0;
|
|
1516
|
+
msg.addInt(Const.op_connect_request);
|
|
1517
|
+
msg.addInt(1); // async
|
|
1518
|
+
msg.addInt(self.dbhandle);
|
|
1519
|
+
msg.addInt(0);
|
|
1520
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1521
|
+
console.log('[fb-debug] auxConnection: sending op_connect_request(53) dbhandle=%d queue_before=%d xdr_saved=%s',
|
|
1522
|
+
self.dbhandle, self._queue.length, Boolean(self._xdr));
|
|
1523
|
+
}
|
|
1524
|
+
function cb(err, ret) {
|
|
1525
|
+
|
|
1526
|
+
if (err) {
|
|
1527
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1528
|
+
console.log('[fb-debug] auxConnection: op_connect_request error: %s queue=%d', err.message, self._queue.length);
|
|
1529
|
+
}
|
|
1530
|
+
doError(err, callback);
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
var socket_info = {
|
|
1535
|
+
family: ret.buffer.readInt16BE(0),
|
|
1536
|
+
port: ret.buffer.readUInt16BE(2),
|
|
1537
|
+
host: ret.buffer.readUInt8(4) + '.' + ret.buffer.readUInt8(5) + '.' + ret.buffer.readUInt8(6) + '.' + ret.buffer.readUInt8(7)
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1541
|
+
console.log('[fb-debug] auxConnection: op_response ok → aux family=%d port=%d host=%s queue=%d',
|
|
1542
|
+
socket_info.family, socket_info.port, socket_info.host, self._queue.length);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
callback(undefined, socket_info);
|
|
1546
|
+
}
|
|
1547
|
+
this._queueEvent(cb);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
queEvents(events, eventid, callback) {
|
|
1552
|
+
var self = this;
|
|
1553
|
+
if (this._isClosed)
|
|
1554
|
+
return this.throwClosed(callback);
|
|
1555
|
+
var msg = this._msg;
|
|
1556
|
+
var blr = this._blr;
|
|
1557
|
+
blr.pos = 0;
|
|
1558
|
+
msg.pos = 0;
|
|
1559
|
+
msg.addInt(Const.op_que_events);
|
|
1560
|
+
msg.addInt(this.dbhandle);
|
|
1561
|
+
// prepare EPB
|
|
1562
|
+
blr.addByte(1) // epb_version
|
|
1563
|
+
for (var event in events) {
|
|
1564
|
+
var event_buffer = Buffer.from(event, 'UTF8');
|
|
1565
|
+
blr.addByte(event_buffer.length);
|
|
1566
|
+
blr.addBytes(event_buffer);
|
|
1567
|
+
blr.addInt32(events[event]);
|
|
1568
|
+
}
|
|
1569
|
+
msg.addBlr(blr); // epb
|
|
1570
|
+
msg.addInt(0); // ast
|
|
1571
|
+
msg.addInt(0); // args
|
|
1572
|
+
msg.addInt(eventid);
|
|
1573
|
+
|
|
1574
|
+
function cb(err, ret) {
|
|
1575
|
+
if (err) {
|
|
1576
|
+
doError(err, callback);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
callback(null, ret);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
this._queueEvent(cb);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
closeEvents(eventid, callback) {
|
|
1588
|
+
var self = this;
|
|
1589
|
+
if (this._isClosed)
|
|
1590
|
+
return this.throwClosed(callback);
|
|
1591
|
+
var msg = self._msg;
|
|
1592
|
+
msg.pos = 0;
|
|
1593
|
+
msg.addInt(Const.op_cancel_events);
|
|
1594
|
+
msg.addInt(self.dbhandle);
|
|
1595
|
+
msg.addInt(eventid);
|
|
1596
|
+
|
|
1597
|
+
function cb(err, ret) {
|
|
1598
|
+
if (err) {
|
|
1599
|
+
doError(err, callback);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
callback(null);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
this._queueEvent(cb);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// Reverse-lookup table: opcode number → name for FIREBIRD_DEBUG trace logging.
|
|
1612
|
+
const opcodeNames = Object.fromEntries(
|
|
1613
|
+
Object.entries(Const).filter(([k]) => k.startsWith('op_')).map(([k, v]) => [v, k])
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
1617
|
+
try {
|
|
1618
|
+
do {
|
|
1619
|
+
var r = data.r || data.readInt();
|
|
1620
|
+
} while (r === Const.op_dummy);
|
|
1621
|
+
|
|
1622
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1623
|
+
console.log('[fb-debug] decodeResponse: opcode=%d(%s) pos=%d buflen=%d',
|
|
1624
|
+
r, opcodeNames[r] || 'unknown', data.pos, data.buffer.length);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
var item, op, response;
|
|
1628
|
+
|
|
1629
|
+
switch (r) {
|
|
1630
|
+
case Const.op_response:
|
|
1631
|
+
|
|
1632
|
+
if (callback) {
|
|
1633
|
+
response = callback.response || {};
|
|
1634
|
+
} else {
|
|
1635
|
+
response = {};
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
let loop = function (err) {
|
|
1639
|
+
if (err) {
|
|
1640
|
+
return cb(err);
|
|
1641
|
+
} else {
|
|
1642
|
+
if (callback && callback.lazy_count) {
|
|
1643
|
+
callback.lazy_count--;
|
|
1644
|
+
if (callback.lazy_count > 0) {
|
|
1645
|
+
r = data.readInt(); // Read new op
|
|
1646
|
+
parseOpResponse(data, response, loop);
|
|
1647
|
+
} else {
|
|
1648
|
+
cb(null, response);
|
|
1649
|
+
}
|
|
1650
|
+
} else {
|
|
1651
|
+
cb(null, response);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
};
|
|
1655
|
+
// Parse normal and lazy response
|
|
1656
|
+
return parseOpResponse(data, response, loop);
|
|
1657
|
+
case Const.op_fetch_response:
|
|
1658
|
+
case Const.op_sql_response:
|
|
1659
|
+
var statement = callback.statement;
|
|
1660
|
+
var output = statement.output;
|
|
1661
|
+
var custom = statement.options || {};
|
|
1662
|
+
var isOpFetch = r === Const.op_fetch_response;
|
|
1663
|
+
var _xdrpos;
|
|
1664
|
+
statement.nbrowsfetched = statement.nbrowsfetched || 0;
|
|
1665
|
+
|
|
1666
|
+
if (isOpFetch && data.fop) { // could be set when a packet is not complete
|
|
1667
|
+
data.readBuffer(68); // ??
|
|
1668
|
+
op = data.readInt(); // ??
|
|
1669
|
+
data.fop = false;
|
|
1670
|
+
if (op === Const.op_response) {
|
|
1671
|
+
return parseOpResponse(data, {}, cb);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
if (!isOpFetch) {
|
|
1676
|
+
data.fstatus = 0;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
data.fstatus = data.fstatus !== undefined ? data.fstatus : data.readInt();
|
|
1680
|
+
data.fcount = data.fcount !== undefined ? data.fcount : data.readInt();
|
|
1681
|
+
data.fcolumn = data.fcolumn || 0;
|
|
1682
|
+
data.frow = data.frow || (custom.asObject ? {} : new Array(output.length));
|
|
1683
|
+
data.frows = data.frows || [];
|
|
1684
|
+
|
|
1685
|
+
if (custom.asObject && !data.fcols) {
|
|
1686
|
+
if (lowercase_keys) {
|
|
1687
|
+
data.fcols = output.map((column) => column.alias.toLowerCase());
|
|
1688
|
+
} else {
|
|
1689
|
+
data.fcols = output.map((column) => column.alias);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
const arrBlob = [];
|
|
1694
|
+
const lowerV13 = statement.connection.accept.protocolVersion < Const.PROTOCOL_VERSION13;
|
|
1695
|
+
|
|
1696
|
+
while (data.fcount && (data.fstatus !== 100)) {
|
|
1697
|
+
let nullBitSet;
|
|
1698
|
+
if (!lowerV13) {
|
|
1699
|
+
const nullBitsLen = Math.floor((output.length + 7) / 8);
|
|
1700
|
+
nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false));
|
|
1701
|
+
data.readBuffer((4 - nullBitsLen) & 3, false); // Skip padding
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
for (let length = output.length; data.fcolumn < length; data.fcolumn++) {
|
|
1705
|
+
item = output[data.fcolumn];
|
|
1706
|
+
|
|
1707
|
+
if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
|
|
1708
|
+
if (custom.asObject) {
|
|
1709
|
+
data.frow[data.fcols[data.fcolumn]] = null;
|
|
1710
|
+
} else {
|
|
1711
|
+
data.frow[data.fcolumn] = null;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
try {
|
|
1718
|
+
_xdrpos = data.pos;
|
|
1719
|
+
const key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
1720
|
+
const row = data.frows.length;
|
|
1721
|
+
let value = item.decode(data, lowerV13);
|
|
1722
|
+
|
|
1723
|
+
if (item.type === Const.SQL_BLOB && value !== null) {
|
|
1724
|
+
if (item.subType === Const.isc_blob_text && cnx.options.blobAsText) {
|
|
1725
|
+
value = fetch_blob_async_transaction(statement, value, key, row);
|
|
1726
|
+
arrBlob.push(value);
|
|
1727
|
+
} else {
|
|
1728
|
+
value = fetch_blob_async(statement, value, key, row);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
data.frow[key] = value;
|
|
1733
|
+
} catch (e) {
|
|
1734
|
+
// uncomplete packet read
|
|
1735
|
+
data.pos = _xdrpos;
|
|
1736
|
+
data.r = r;
|
|
1737
|
+
return cb(new Error('Packet is not complete'));
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
data.fcolumn = 0;
|
|
1743
|
+
// ToDo: emit "row" with blob subtype string decoded
|
|
1744
|
+
// use: data.frow['fieldBlob'](transaction?).then(({ value }) => console.log(value))
|
|
1745
|
+
// arg "transaction" is optional
|
|
1746
|
+
statement.connection.db.emit('row', data.frow, statement.nbrowsfetched, custom.asObject);
|
|
1747
|
+
data.frows.push(data.frow);
|
|
1748
|
+
data.frow = custom.asObject ? {} : new Array(output.length);
|
|
1749
|
+
|
|
1750
|
+
try {
|
|
1751
|
+
_xdrpos = data.pos;
|
|
1752
|
+
if (isOpFetch) {
|
|
1753
|
+
delete data.fstatus;
|
|
1754
|
+
delete data.fcount;
|
|
1755
|
+
op = data.readInt(); // ??
|
|
1756
|
+
if (op === Const.op_response) {
|
|
1757
|
+
return parseOpResponse(data, {}, cb);
|
|
1758
|
+
}
|
|
1759
|
+
data.fstatus = data.readInt();
|
|
1760
|
+
data.fcount = data.readInt();
|
|
1761
|
+
} else {
|
|
1762
|
+
data.fcount--;
|
|
1763
|
+
if (r === Const.op_sql_response) {
|
|
1764
|
+
op = data.readInt();
|
|
1765
|
+
if (op === Const.op_response) {
|
|
1766
|
+
parseOpResponse(data, {});
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
} catch (e) {
|
|
1771
|
+
if (_xdrpos === data.pos) {
|
|
1772
|
+
data.fop = true;
|
|
1773
|
+
}
|
|
1774
|
+
data.r = r;
|
|
1775
|
+
return cb(new Error("Packet is not complete"));
|
|
1776
|
+
}
|
|
1777
|
+
statement.nbrowsfetched++;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// ToDo: emit "result" with blob subtype string decoded
|
|
1781
|
+
statement.connection.db.emit('result', data.frows, arrBlob);
|
|
1782
|
+
return cb(null, {data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100), arrBlob});
|
|
1783
|
+
case Const.op_accept:
|
|
1784
|
+
case Const.op_cond_accept:
|
|
1785
|
+
case Const.op_accept_data:
|
|
1786
|
+
let accept = {
|
|
1787
|
+
protocolVersion: data.readInt(),
|
|
1788
|
+
protocolArchitecture: data.readInt(),
|
|
1789
|
+
protocolMinimumType: data.readInt(),
|
|
1790
|
+
compress: false,
|
|
1791
|
+
pluginName: '',
|
|
1792
|
+
authData: '',
|
|
1793
|
+
sessionKey: ''
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
accept.compress = (accept.protocolMinimumType & Const.pflag_compress) !== 0;
|
|
1797
|
+
accept.protocolMinimumType = accept.protocolMinimumType & Const.ptype_mask;
|
|
1798
|
+
//accept.compress = (accept.acceptType & pflag_compress) !== 0; // TODO Handle zlib compression
|
|
1799
|
+
if (accept.protocolVersion < 0) {
|
|
1800
|
+
accept.protocolVersion = (accept.protocolVersion & Const.FB_PROTOCOL_MASK) | Const.FB_PROTOCOL_FLAG;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
if (r === Const.op_cond_accept || r === Const.op_accept_data) {
|
|
1804
|
+
var d = new BlrReader(data.readArray());
|
|
1805
|
+
accept.pluginName = data.readString(Const.DEFAULT_ENCODING);
|
|
1806
|
+
var is_authenticated = data.readInt();
|
|
1807
|
+
var keys = data.readString(Const.DEFAULT_ENCODING); // keys
|
|
1808
|
+
|
|
1809
|
+
if (is_authenticated === 0) {
|
|
1810
|
+
if (cnx.options.pluginName && cnx.options.pluginName !== accept.pluginName) {
|
|
1811
|
+
doError(new Error('Server don\'t accept plugin : ' + cnx.options.pluginName + ', but support : ' + accept.pluginName), callback);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
|
|
1815
|
+
var crypto = {
|
|
1816
|
+
Srp: 'sha1',
|
|
1817
|
+
Srp256: 'sha256'
|
|
1818
|
+
};
|
|
1819
|
+
accept.srpAlgo = crypto[accept.pluginName];
|
|
1820
|
+
|
|
1821
|
+
// TODO : Fallback Srp256 to Srp ?
|
|
1822
|
+
/*if (!d.buffer) {
|
|
1823
|
+
cnx.sendOpContAuth(
|
|
1824
|
+
cnx.clientKeys.public.toString(16),
|
|
1825
|
+
DEFAULT_ENCODING,
|
|
1826
|
+
accept.pluginName
|
|
1827
|
+
);
|
|
1828
|
+
|
|
1829
|
+
return cb(new Error('login'));
|
|
1830
|
+
}*/
|
|
1831
|
+
|
|
1832
|
+
// Check buffer contains salt
|
|
1833
|
+
var saltLen = d.buffer.readUInt16LE(0);
|
|
1834
|
+
if (saltLen > 32 * 2) {
|
|
1835
|
+
console.log('salt to long'); // TODO : Throw error
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// Check buffer contains key
|
|
1839
|
+
var keyLen = d.buffer.readUInt16LE(saltLen + 2);
|
|
1840
|
+
var keyStart = saltLen + 4;
|
|
1841
|
+
if (d.buffer.length - keyStart !== keyLen) {
|
|
1842
|
+
console.log('key error'); // TODO : Throw error
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// Server keys
|
|
1846
|
+
cnx.serverKeys = {
|
|
1847
|
+
salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
|
|
1848
|
+
public: BigInt('0x' + d.buffer.slice(keyStart, d.buffer.length).toString('utf8'))
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
const _t1 = Date.now();
|
|
1852
|
+
var proof = srp.clientProof(
|
|
1853
|
+
cnx.options.user.toUpperCase(),
|
|
1854
|
+
cnx.options.password,
|
|
1855
|
+
cnx.serverKeys.salt,
|
|
1856
|
+
cnx.clientKeys.public,
|
|
1857
|
+
cnx.serverKeys.public,
|
|
1858
|
+
cnx.clientKeys.private,
|
|
1859
|
+
accept.srpAlgo
|
|
1860
|
+
);
|
|
1861
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1862
|
+
console.log('[fb-debug] srp.clientProof(%s): %dms', accept.srpAlgo, Date.now() - _t1);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
accept.authData = proof.authData.toString(16);
|
|
1866
|
+
accept.sessionKey = proof.clientSessionKey;
|
|
1867
|
+
} else if (accept.pluginName === Const.AUTH_PLUGIN_LEGACY) {
|
|
1868
|
+
accept.authData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
|
|
1869
|
+
} else {
|
|
1870
|
+
return cb(new Error('Unknow auth plugin : ' + accept.pluginName));
|
|
1871
|
+
}
|
|
1872
|
+
} else {
|
|
1873
|
+
accept.authData = '';
|
|
1874
|
+
accept.sessionKey = '';
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (accept.compress) {
|
|
1879
|
+
cnx._socket.enableCompression();
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1883
|
+
console.log('[fb-debug] auth: %s received plugin=%s proto=%d t=%dms',
|
|
1884
|
+
r === Const.op_cond_accept ? 'op_cond_accept' : r === Const.op_accept_data ? 'op_accept_data' : 'op_accept',
|
|
1885
|
+
accept.pluginName, accept.protocolVersion,
|
|
1886
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// For op_cond_accept: send op_cont_auth and wait for response
|
|
1890
|
+
if (r === Const.op_cond_accept && accept.authData) {
|
|
1891
|
+
cnx._pendingAccept = accept;
|
|
1892
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1893
|
+
console.log('[fb-debug] auth: sending op_cont_auth plugin=%s t=%dms',
|
|
1894
|
+
accept.pluginName, cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1895
|
+
}
|
|
1896
|
+
cnx.sendOpContAuth(
|
|
1897
|
+
accept.authData,
|
|
1898
|
+
Const.DEFAULT_ENCODING,
|
|
1899
|
+
accept.pluginName
|
|
1900
|
+
);
|
|
1901
|
+
return; // Don't call cb - queue stays for op_response
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
return cb(undefined, accept);
|
|
1905
|
+
case Const.op_cont_auth:
|
|
1906
|
+
var d = new BlrReader(data.readArray());
|
|
1907
|
+
var pluginName = data.readString(Const.DEFAULT_ENCODING);
|
|
1908
|
+
data.readString(Const.DEFAULT_ENCODING); // plist
|
|
1909
|
+
data.readString(Const.DEFAULT_ENCODING); // pkey
|
|
1910
|
+
|
|
1911
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1912
|
+
console.log('[fb-debug] auth: op_cont_auth received plugin=%s pendingAccept=%s t=%dms',
|
|
1913
|
+
pluginName,
|
|
1914
|
+
cnx._pendingAccept ? cnx._pendingAccept.pluginName : 'none',
|
|
1915
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// During SRP mutual authentication, the server sends op_cont_auth
|
|
1919
|
+
// with its proof (M2) after receiving the client's proof (M1).
|
|
1920
|
+
// When we have an active auth exchange for this plugin, just wait
|
|
1921
|
+
// for the subsequent op_accept instead of treating it as an error.
|
|
1922
|
+
if (cnx._pendingAccept && cnx._pendingAccept.pluginName === pluginName) {
|
|
1923
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1924
|
+
console.log('[fb-debug] auth: server SRP proof (M2) received, waiting for op_accept t=%dms',
|
|
1925
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1926
|
+
}
|
|
1927
|
+
return; // Server SRP proof received - wait for op_accept
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// Firebird 4/5 (protocols 16/17) chained-auth: after the client sends
|
|
1931
|
+
// the SRP M1 proof, the server sends op_cont_auth with Legacy_Auth.
|
|
1932
|
+
// SRP has already established the session key; the server additionally
|
|
1933
|
+
// requires a Legacy_Auth verification step before sending op_accept.
|
|
1934
|
+
// Respond with Legacy_Auth credentials and wait for op_accept.
|
|
1935
|
+
if (cnx._pendingAccept && pluginName === Const.AUTH_PLUGIN_LEGACY) {
|
|
1936
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1937
|
+
console.log('[fb-debug] auth: SRP+Legacy_Auth chained-auth (proto %d), sending Legacy_Auth credentials t=%dms',
|
|
1938
|
+
cnx._pendingAccept.protocolVersion,
|
|
1939
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1940
|
+
}
|
|
1941
|
+
var legacyAuthData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
|
|
1942
|
+
cnx.sendOpContAuth(legacyAuthData, Const.DEFAULT_ENCODING, pluginName);
|
|
1943
|
+
return; // wait for op_accept
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
if (!cnx.options.pluginName) {
|
|
1947
|
+
if (cnx.accept && cnx.accept.pluginName === pluginName) {
|
|
1948
|
+
// Erreur plugin not able to connect
|
|
1949
|
+
return cb(new Error("Unable to connect with plugin " + cnx.accept.pluginName));
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
if (pluginName === Const.AUTH_PLUGIN_LEGACY) { // Fallback to LegacyAuth
|
|
1953
|
+
cnx.accept.pluginName = pluginName;
|
|
1954
|
+
cnx.accept.authData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
|
|
1955
|
+
|
|
1956
|
+
cnx.sendOpContAuth(
|
|
1957
|
+
cnx.accept.authData,
|
|
1958
|
+
Const.DEFAULT_ENCODING,
|
|
1959
|
+
pluginName
|
|
1960
|
+
);
|
|
1961
|
+
|
|
1962
|
+
return {error: new Error('login')};
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// Server sent op_cont_auth but we don't know how to handle it.
|
|
1967
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1968
|
+
console.warn('[fb-debug] auth: op_cont_auth unhandled plugin=%s pendingAccept=%s options.plugin=%s t=%dms',
|
|
1969
|
+
pluginName,
|
|
1970
|
+
cnx._pendingAccept ? cnx._pendingAccept.pluginName : 'none',
|
|
1971
|
+
cnx.options.pluginName || 'none',
|
|
1972
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1973
|
+
}
|
|
1974
|
+
return cb(new Error('Unhandled server op_cont_auth for plugin: ' + pluginName));
|
|
1975
|
+
case Const.op_crypt_key_callback:
|
|
1976
|
+
// Database encryption key callback
|
|
1977
|
+
// Read server data (plugin data sent by server)
|
|
1978
|
+
var serverPluginData = data.readArray();
|
|
1979
|
+
|
|
1980
|
+
// Get client response from dbCryptConfig option
|
|
1981
|
+
var clientPluginData = parseDbCryptConfig(cnx.options.dbCryptConfig);
|
|
1982
|
+
|
|
1983
|
+
// Create a BlrWriter to send the response
|
|
1984
|
+
// Note: BlrWriter needs initial buffer size allocation
|
|
1985
|
+
var responseBlr = new BlrWriter(clientPluginData.length + 4);
|
|
1986
|
+
responseBlr.addBytes(clientPluginData);
|
|
1987
|
+
|
|
1988
|
+
// Send the response back to the server
|
|
1989
|
+
cnx.sendOpCryptKeyCallback(responseBlr);
|
|
1990
|
+
|
|
1991
|
+
// Don't call cb - wait for next operation (likely op_response or another op_crypt_key_callback)
|
|
1992
|
+
return;
|
|
1993
|
+
case Const.op_event:
|
|
1994
|
+
// op_event may occasionally arrive on the main connection
|
|
1995
|
+
// (e.g. Firebird routing an async notification here instead of
|
|
1996
|
+
// the dedicated aux socket). Consume all its fields so the
|
|
1997
|
+
// buffer position advances correctly, then signal the data
|
|
1998
|
+
// handler to skip queue manipulation for this frame.
|
|
1999
|
+
//
|
|
2000
|
+
// Firebird wire protocol – op_event payload (remote protocol):
|
|
2001
|
+
// p_event_database : Int32 – database handle
|
|
2002
|
+
// p_event_items : Array – event parameter block (EPB)
|
|
2003
|
+
// p_event_ast : Int64 – AST routine pointer (0 for remote)
|
|
2004
|
+
// p_event_rid : Int32 – remote event ID
|
|
2005
|
+
{
|
|
2006
|
+
const evtDb = data.readInt(); // p_event_database
|
|
2007
|
+
data.readArray(); // p_event_items (EPB buffer)
|
|
2008
|
+
data.readInt64(); // p_event_ast
|
|
2009
|
+
const evtRid = data.readInt(); // p_event_rid
|
|
2010
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2011
|
+
console.log('[fb-debug] op_event on main connection: db=%d rid=%d (consumed, not queued)', evtDb, evtRid);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return cb(null, { _isOpEvent: true });
|
|
2015
|
+
case Const.op_response_piggyback:
|
|
2016
|
+
// Firebird 5 (Protocol 16/17) sends op_response_piggyback (72)
|
|
2017
|
+
// as an unsolicited cleanup notification after certain operations
|
|
2018
|
+
// (e.g. after the EventConnection aux socket is torn down).
|
|
2019
|
+
// It has the same wire layout as op_response but does NOT
|
|
2020
|
+
// correspond to any queued client request. Parse and discard it
|
|
2021
|
+
// so that the xdr buffer position advances correctly, then signal
|
|
2022
|
+
// the data handler to skip queue manipulation.
|
|
2023
|
+
//
|
|
2024
|
+
// Wire layout (identical to op_response):
|
|
2025
|
+
// handle : Int32
|
|
2026
|
+
// object : Quad (2x Int32)
|
|
2027
|
+
// data : Array
|
|
2028
|
+
// status : status-vector ending with isc_arg_end
|
|
2029
|
+
parseOpResponse(data, {}, function(err) {
|
|
2030
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2031
|
+
if (err) {
|
|
2032
|
+
console.warn('[fb-debug] op_response_piggyback parse error:', err.message);
|
|
2033
|
+
} else {
|
|
2034
|
+
console.log('[fb-debug] op_response_piggyback consumed (unsolicited Firebird 5 cleanup)');
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
return cb(null, { _isOpEvent: true });
|
|
2039
|
+
default:
|
|
2040
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2041
|
+
console.warn('[fb-debug] unknown opcode=%d at pos=%d buflen=%d queue=%d',
|
|
2042
|
+
r, data.pos, data.buffer.length, cnx && cnx._queue ? cnx._queue.length : 0);
|
|
2043
|
+
}
|
|
2044
|
+
return cb(new Error('Unexpected:' + r));
|
|
2045
|
+
}
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2048
|
+
console.warn('[fb-debug] decodeResponse exception: %s (RangeError=%s) pos=%d buflen=%d',
|
|
2049
|
+
err.message, err instanceof RangeError, data.pos, data.buffer.length);
|
|
2050
|
+
}
|
|
2051
|
+
if (err instanceof RangeError) {
|
|
2052
|
+
return cb(err);
|
|
2053
|
+
}
|
|
2054
|
+
throw err;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function parseOpResponse(data, response, cb) {
|
|
2059
|
+
var handle = data.readInt();
|
|
2060
|
+
|
|
2061
|
+
if (!response.handle) {
|
|
2062
|
+
response.handle = handle;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
var oid = data.readQuad();
|
|
2066
|
+
if (oid.low || oid.high) {
|
|
2067
|
+
response.oid = oid;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
var buf = data.readArray();
|
|
2071
|
+
if (buf) {
|
|
2072
|
+
response.buffer = buf;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
var num, op, item = {};
|
|
2076
|
+
while (true) {
|
|
2077
|
+
op = data.readInt();
|
|
2078
|
+
|
|
2079
|
+
switch (op) {
|
|
2080
|
+
case Const.isc_arg_end:
|
|
2081
|
+
return cb ? cb(undefined, response) : response;
|
|
2082
|
+
case Const.isc_arg_gds:
|
|
2083
|
+
num = data.readInt();
|
|
2084
|
+
if (!num) {
|
|
2085
|
+
break;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
item = {gdscode: num};
|
|
2089
|
+
|
|
2090
|
+
if (response.status) {
|
|
2091
|
+
response.status.push(item);
|
|
2092
|
+
} else {
|
|
2093
|
+
response.status = [item];
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
break;
|
|
2097
|
+
case Const.isc_arg_string:
|
|
2098
|
+
case Const.isc_arg_interpreted:
|
|
2099
|
+
case Const.isc_arg_sql_state:
|
|
2100
|
+
if (item.params) {
|
|
2101
|
+
var str = data.readString(Const.DEFAULT_ENCODING);
|
|
2102
|
+
item.params.push(str);
|
|
2103
|
+
} else {
|
|
2104
|
+
item.params = [data.readString(Const.DEFAULT_ENCODING)];
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
break;
|
|
2108
|
+
case Const.isc_arg_number:
|
|
2109
|
+
num = data.readInt();
|
|
2110
|
+
|
|
2111
|
+
if (item.params) {
|
|
2112
|
+
item.params.push(num);
|
|
2113
|
+
} else {
|
|
2114
|
+
item.params = [num];
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
if (item.gdscode === Const.isc_sqlerr) {
|
|
2118
|
+
response.sqlcode = num;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
break;
|
|
2122
|
+
default:
|
|
2123
|
+
if (cb) {
|
|
2124
|
+
cb(new Error('Unexpected: ' + op))
|
|
2125
|
+
} else {
|
|
2126
|
+
throw new Error('Unexpected: ' + op);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
function describe(buff, statement) {
|
|
2133
|
+
var br = new BlrReader(buff);
|
|
2134
|
+
var parameters = null;
|
|
2135
|
+
var type, param;
|
|
2136
|
+
|
|
2137
|
+
while (br.pos < br.buffer.length) {
|
|
2138
|
+
switch (br.readByteCode()) {
|
|
2139
|
+
case Const.isc_info_sql_stmt_type:
|
|
2140
|
+
statement.type = br.readInt();
|
|
2141
|
+
break;
|
|
2142
|
+
case Const.isc_info_sql_get_plan:
|
|
2143
|
+
statement.plan = br.readString(Const.DEFAULT_ENCODING);
|
|
2144
|
+
break;
|
|
2145
|
+
case Const.isc_info_sql_select:
|
|
2146
|
+
statement.output = parameters = [];
|
|
2147
|
+
break;
|
|
2148
|
+
case Const.isc_info_sql_bind:
|
|
2149
|
+
statement.input = parameters = [];
|
|
2150
|
+
break;
|
|
2151
|
+
case Const.isc_info_sql_num_variables:
|
|
2152
|
+
br.readInt(); // eat int
|
|
2153
|
+
break;
|
|
2154
|
+
case Const.isc_info_sql_describe_vars:
|
|
2155
|
+
if (!parameters) {return}
|
|
2156
|
+
br.readInt(); // eat int ?
|
|
2157
|
+
var finishDescribe = false;
|
|
2158
|
+
param = null;
|
|
2159
|
+
while (!finishDescribe){
|
|
2160
|
+
switch (br.readByteCode()) {
|
|
2161
|
+
case Const.isc_info_sql_describe_end:
|
|
2162
|
+
break;
|
|
2163
|
+
case Const.isc_info_sql_sqlda_seq:
|
|
2164
|
+
var num = br.readInt();
|
|
2165
|
+
break;
|
|
2166
|
+
case Const.isc_info_sql_type:
|
|
2167
|
+
type = br.readInt();
|
|
2168
|
+
switch (type&~1) {
|
|
2169
|
+
case Const.SQL_VARYING: param = new Xsql.SQLVarString(); break;
|
|
2170
|
+
case Const.SQL_NULL: param = new Xsql.SQLVarNull(); break;
|
|
2171
|
+
case Const.SQL_TEXT: param = new Xsql.SQLVarText(); break;
|
|
2172
|
+
case Const.SQL_DOUBLE: param = new Xsql.SQLVarDouble(); break;
|
|
2173
|
+
case Const.SQL_FLOAT:
|
|
2174
|
+
case Const.SQL_D_FLOAT: param = new Xsql.SQLVarFloat(); break;
|
|
2175
|
+
case Const.SQL_TYPE_DATE: param = new Xsql.SQLVarDate(); break;
|
|
2176
|
+
case Const.SQL_TYPE_TIME: param = new Xsql.SQLVarTime(); break;
|
|
2177
|
+
case Const.SQL_TIMESTAMP: param = new Xsql.SQLVarTimeStamp(); break;
|
|
2178
|
+
case Const.SQL_TIME_TZ: param = new Xsql.SQLVarTimeTz(); break;
|
|
2179
|
+
case Const.SQL_TIME_TZ_EX: param = new Xsql.SQLVarTimeTzEx(); break;
|
|
2180
|
+
case Const.SQL_TIMESTAMP_TZ: param = new Xsql.SQLVarTimeStampTz(); break;
|
|
2181
|
+
case Const.SQL_TIMESTAMP_TZ_EX: param = new Xsql.SQLVarTimeStampTzEx(); break;
|
|
2182
|
+
case Const.SQL_BLOB: param = new Xsql.SQLVarBlob(); break;
|
|
2183
|
+
case Const.SQL_ARRAY: param = new Xsql.SQLVarArray(); break;
|
|
2184
|
+
case Const.SQL_QUAD: param = new Xsql.SQLVarQuad(); break;
|
|
2185
|
+
case Const.SQL_LONG: param = new Xsql.SQLVarInt(); break;
|
|
2186
|
+
case Const.SQL_SHORT: param = new Xsql.SQLVarShort(); break;
|
|
2187
|
+
case Const.SQL_INT64: param = new Xsql.SQLVarInt64(); break;
|
|
2188
|
+
case Const.SQL_INT128: param = new Xsql.SQLVarInt128(); break;
|
|
2189
|
+
case Const.SQL_DEC16: param = new Xsql.SQLVarDecFloat16(); break;
|
|
2190
|
+
case Const.SQL_DEC34: param = new Xsql.SQLVarDecFloat34(); break;
|
|
2191
|
+
case Const.SQL_BOOLEAN: param = new Xsql.SQLVarBoolean(); break;
|
|
2192
|
+
default:
|
|
2193
|
+
throw new Error('Unexpected');
|
|
2194
|
+
}
|
|
2195
|
+
parameters[num-1] = param;
|
|
2196
|
+
param.type = type;
|
|
2197
|
+
param.nullable = Boolean(param.type & 1);
|
|
2198
|
+
param.type &= ~1;
|
|
2199
|
+
break;
|
|
2200
|
+
case Const.isc_info_sql_sub_type:
|
|
2201
|
+
param.subType = br.readInt();
|
|
2202
|
+
break;
|
|
2203
|
+
case Const.isc_info_sql_scale:
|
|
2204
|
+
param.scale = br.readInt();
|
|
2205
|
+
break;
|
|
2206
|
+
case Const.isc_info_sql_length:
|
|
2207
|
+
param.length = br.readInt();
|
|
2208
|
+
break;
|
|
2209
|
+
case Const.isc_info_sql_null_ind:
|
|
2210
|
+
param.nullable = Boolean(br.readInt());
|
|
2211
|
+
break;
|
|
2212
|
+
case Const.isc_info_sql_field:
|
|
2213
|
+
param.field = br.readString(Const.DEFAULT_ENCODING);
|
|
2214
|
+
break;
|
|
2215
|
+
case Const.isc_info_sql_relation:
|
|
2216
|
+
param.relation = br.readString(Const.DEFAULT_ENCODING);
|
|
2217
|
+
break;
|
|
2218
|
+
case Const.isc_info_sql_owner:
|
|
2219
|
+
param.owner = br.readString(Const.DEFAULT_ENCODING);
|
|
2220
|
+
break;
|
|
2221
|
+
case Const.isc_info_sql_alias:
|
|
2222
|
+
param.alias = br.readString(Const.DEFAULT_ENCODING);
|
|
2223
|
+
break;
|
|
2224
|
+
case Const.isc_info_sql_relation_alias:
|
|
2225
|
+
param.relationAlias = br.readString(Const.DEFAULT_ENCODING);
|
|
2226
|
+
break;
|
|
2227
|
+
case Const.isc_info_truncated:
|
|
2228
|
+
throw new Error('Truncated');
|
|
2229
|
+
default:
|
|
2230
|
+
finishDescribe = true;
|
|
2231
|
+
br.pos--;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
function CalcBlr(blr, xsqlda) {
|
|
2239
|
+
blr.addBytes([Const.blr_version5, Const.blr_begin, Const.blr_message, 0]); // + message number
|
|
2240
|
+
blr.addWord(xsqlda.length * 2);
|
|
2241
|
+
|
|
2242
|
+
for (var i = 0, length = xsqlda.length; i < length; i++) {
|
|
2243
|
+
xsqlda[i].calcBlr(blr);
|
|
2244
|
+
blr.addByte(Const.blr_short);
|
|
2245
|
+
blr.addByte(0);
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
blr.addByte(Const.blr_end);
|
|
2249
|
+
blr.addByte(Const.blr_eoc);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function fetch_blob_async_transaction(statement, id, column, row) {
|
|
2253
|
+
const infoValue = { row, column, value: '' };
|
|
2254
|
+
|
|
2255
|
+
return (transactionArg) => {
|
|
2256
|
+
const singleTransaction = transactionArg === undefined;
|
|
2257
|
+
|
|
2258
|
+
let promiseTransaction;
|
|
2259
|
+
if (singleTransaction) {
|
|
2260
|
+
promiseTransaction = new Promise((resolve, reject) => {
|
|
2261
|
+
statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
|
|
2262
|
+
if (err) {
|
|
2263
|
+
return reject(err);
|
|
2264
|
+
}
|
|
2265
|
+
resolve(transaction);
|
|
2266
|
+
});
|
|
2267
|
+
});
|
|
2268
|
+
} else {
|
|
2269
|
+
promiseTransaction = Promise.resolve(transactionArg);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
return promiseTransaction.then((transaction) => {
|
|
2273
|
+
return new Promise((resolve, reject) => {
|
|
2274
|
+
statement.connection._pending.push('openBlob');
|
|
2275
|
+
statement.connection.openBlob(id, transaction, (err, blob) => {
|
|
2276
|
+
|
|
2277
|
+
if (err) {
|
|
2278
|
+
reject(err);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
const read = () => {
|
|
2283
|
+
statement.connection.getSegment(blob, (err, ret) => {
|
|
2284
|
+
|
|
2285
|
+
if (err) {
|
|
2286
|
+
if (singleTransaction) {
|
|
2287
|
+
transaction.rollback(() => reject(err));
|
|
2288
|
+
} else {
|
|
2289
|
+
reject(err);
|
|
2290
|
+
}
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
if (ret.buffer) {
|
|
2295
|
+
const blr = new BlrReader(ret.buffer);
|
|
2296
|
+
const data = blr.readSegment();
|
|
2297
|
+
infoValue.value += data.toString(Const.DEFAULT_ENCODING);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
if (ret.handle !== 2) {
|
|
2301
|
+
read();
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
statement.connection.closeBlob(blob);
|
|
2306
|
+
if (singleTransaction) {
|
|
2307
|
+
transaction.commit((err) => {
|
|
2308
|
+
if (err) {
|
|
2309
|
+
reject(err);
|
|
2310
|
+
} else {
|
|
2311
|
+
resolve(infoValue);
|
|
2312
|
+
}
|
|
2313
|
+
});
|
|
2314
|
+
} else {
|
|
2315
|
+
resolve(infoValue);
|
|
2316
|
+
}
|
|
2317
|
+
});
|
|
2318
|
+
};
|
|
2319
|
+
|
|
2320
|
+
read();
|
|
2321
|
+
});
|
|
2322
|
+
});
|
|
2323
|
+
});
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function fetch_blob_async(statement, id, name, row) {
|
|
2328
|
+
const cbTransaction = (transaction, close, callback) => {
|
|
2329
|
+
statement.connection._pending.push('openBlob');
|
|
2330
|
+
statement.connection.openBlob(id, transaction, (err, blob) => {
|
|
2331
|
+
let e = new Events.EventEmitter();
|
|
2332
|
+
|
|
2333
|
+
e.pipe = (stream) => {
|
|
2334
|
+
e.on('data', (chunk) => {
|
|
2335
|
+
stream.write(chunk);
|
|
2336
|
+
});
|
|
2337
|
+
e.on('end', () => {
|
|
2338
|
+
stream.end();
|
|
2339
|
+
});
|
|
2340
|
+
};
|
|
2341
|
+
|
|
2342
|
+
if (err) {
|
|
2343
|
+
return callback(err, name, e, row);
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
const read = () => {
|
|
2347
|
+
statement.connection.getSegment(blob, (err, ret) => {
|
|
2348
|
+
|
|
2349
|
+
if (err) {
|
|
2350
|
+
transaction.rollback(() => {
|
|
2351
|
+
e.emit('error', err);
|
|
2352
|
+
});
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
if (ret.buffer) {
|
|
2357
|
+
const blr = new BlrReader(ret.buffer);
|
|
2358
|
+
const data = blr.readSegment();
|
|
2359
|
+
|
|
2360
|
+
e.emit('data', data);
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
if (ret.handle !== 2) {
|
|
2364
|
+
read();
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
statement.connection.closeBlob(blob);
|
|
2369
|
+
if (close) {
|
|
2370
|
+
transaction.commit((err) => {
|
|
2371
|
+
if (err) {
|
|
2372
|
+
e.emit('error', err);
|
|
2373
|
+
} else {
|
|
2374
|
+
e.emit('end');
|
|
2375
|
+
}
|
|
2376
|
+
e = null;
|
|
2377
|
+
});
|
|
2378
|
+
} else {
|
|
2379
|
+
e.emit('end');
|
|
2380
|
+
e = null;
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
};
|
|
2384
|
+
|
|
2385
|
+
callback(err, name, e, row);
|
|
2386
|
+
read();
|
|
2387
|
+
});
|
|
2388
|
+
};
|
|
2389
|
+
|
|
2390
|
+
return (transaction, callback) => {
|
|
2391
|
+
// callback(error, nameField, eventEmitter, row)
|
|
2392
|
+
const singleTransaction = callback === undefined;
|
|
2393
|
+
if (singleTransaction) {
|
|
2394
|
+
callback = transaction;
|
|
2395
|
+
statement.connection.startTransaction(Const.ISOLATION_READ_UNCOMMITTED, (err, transaction) => {
|
|
2396
|
+
if (err) {
|
|
2397
|
+
callback(err);
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
cbTransaction(transaction, singleTransaction, callback);
|
|
2401
|
+
});
|
|
2402
|
+
} else {
|
|
2403
|
+
cbTransaction(transaction, singleTransaction, callback);
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
function doSynchronousLoop(data, processData, done) {
|
|
2409
|
+
if (!data || !data.length) {
|
|
2410
|
+
done();
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
const loop = (index) => {
|
|
2415
|
+
processData(data[index], index, (err) => {
|
|
2416
|
+
if (err) {
|
|
2417
|
+
done(err);
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
const nextIndex = index + 1;
|
|
2422
|
+
if (nextIndex < data.length) {
|
|
2423
|
+
loop(nextIndex);
|
|
2424
|
+
} else {
|
|
2425
|
+
done();
|
|
2426
|
+
}
|
|
2427
|
+
});
|
|
2428
|
+
};
|
|
2429
|
+
|
|
2430
|
+
loop(0);
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
function executeStreamRow(custom, row, index, output, next) {
|
|
2434
|
+
let done = false;
|
|
2435
|
+
const finish = (err) => {
|
|
2436
|
+
if (done) {
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
done = true;
|
|
2440
|
+
next(err);
|
|
2441
|
+
};
|
|
2442
|
+
|
|
2443
|
+
try {
|
|
2444
|
+
const ret = custom.on(row, index, output, finish);
|
|
2445
|
+
if (custom.on.length < 4) {
|
|
2446
|
+
if (ret && typeof ret.then === 'function') {
|
|
2447
|
+
ret.then(() => finish()).catch(finish);
|
|
2448
|
+
} else {
|
|
2449
|
+
finish();
|
|
2450
|
+
}
|
|
2451
|
+
} else if (ret && typeof ret.then === 'function') {
|
|
2452
|
+
ret.catch(finish);
|
|
2453
|
+
}
|
|
2454
|
+
} catch (err) {
|
|
2455
|
+
finish(err);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function bufferReader(buffer, max, writer, cb, beg, end) {
|
|
2460
|
+
|
|
2461
|
+
if (!beg)
|
|
2462
|
+
beg = 0;
|
|
2463
|
+
|
|
2464
|
+
if (!end)
|
|
2465
|
+
end = max;
|
|
2466
|
+
|
|
2467
|
+
if (end >= buffer.length)
|
|
2468
|
+
end = undefined;
|
|
2469
|
+
|
|
2470
|
+
var b = buffer.slice(beg, end);
|
|
2471
|
+
|
|
2472
|
+
writer(b, function() {
|
|
2473
|
+
|
|
2474
|
+
if (end === undefined) {
|
|
2475
|
+
cb();
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
bufferReader(buffer, max, writer, cb, beg + max, end + max);
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
/**
|
|
2484
|
+
* Parse dbCryptConfig option and convert to bytes.
|
|
2485
|
+
* Supports:
|
|
2486
|
+
* - "base64:<value>" - decodes base64 to bytes
|
|
2487
|
+
* - plain string - encodes as UTF-8
|
|
2488
|
+
* - undefined/null/empty - returns empty buffer
|
|
2489
|
+
*/
|
|
2490
|
+
function parseDbCryptConfig(config) {
|
|
2491
|
+
if (!config) {
|
|
2492
|
+
return Buffer.alloc(0);
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// Check if it's a base64 encoded value
|
|
2496
|
+
if (config.startsWith('base64:')) {
|
|
2497
|
+
const base64Value = config.substring(7);
|
|
2498
|
+
try {
|
|
2499
|
+
return Buffer.from(base64Value, 'base64');
|
|
2500
|
+
} catch (e) {
|
|
2501
|
+
console.error('Failed to decode base64 dbCryptConfig, returning empty buffer:', e);
|
|
2502
|
+
return Buffer.alloc(0);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// Plain string - encode as UTF-8
|
|
2507
|
+
return Buffer.from(config, 'utf8');
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
module.exports = Connection;
|