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.
Files changed (49) hide show
  1. package/README.md +163 -31
  2. package/lib/index.d.ts +12 -0
  3. package/lib/pool.js +10 -1
  4. package/lib/srp.js +12 -1
  5. package/lib/wire/connection.js +39 -7
  6. package/lib/wire/socket.js +13 -1
  7. package/lib/wire/xsqlvar.js +69 -6
  8. package/package.json +1 -1
  9. package/poc/node_modules/.package-lock.json +14 -0
  10. package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
  11. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
  12. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
  13. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
  14. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
  15. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
  16. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
  17. package/poc/node_modules/node-firebird/LICENSE +373 -0
  18. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
  19. package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
  20. package/poc/node_modules/node-firebird/README.md +794 -0
  21. package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
  22. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
  23. package/poc/node_modules/node-firebird/lib/callback.js +38 -0
  24. package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
  25. package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
  26. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
  27. package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
  28. package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
  29. package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
  30. package/poc/node_modules/node-firebird/lib/index.js +128 -0
  31. package/poc/node_modules/node-firebird/lib/messages.js +162 -0
  32. package/poc/node_modules/node-firebird/lib/pool.js +108 -0
  33. package/poc/node_modules/node-firebird/lib/srp.js +299 -0
  34. package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
  35. package/poc/node_modules/node-firebird/lib/utils.js +164 -0
  36. package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
  37. package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
  38. package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
  39. package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
  40. package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
  41. package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
  42. package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
  43. package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
  44. package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
  45. package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
  46. package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
  47. package/poc/node_modules/node-firebird/package.json +38 -0
  48. package/poc/node_modules/node-firebird/vitest.config.js +24 -0
  49. 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;