node-firebird 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -4
- package/lib/pool.d.ts +43 -1
- package/lib/pool.js +124 -7
- package/lib/types.d.ts +63 -0
- package/lib/wire/connection.d.ts +15 -0
- package/lib/wire/connection.js +461 -0
- package/lib/wire/const.d.ts +15 -0
- package/lib/wire/const.js +21 -0
- package/lib/wire/database.d.ts +11 -0
- package/lib/wire/database.js +46 -0
- package/lib/wire/serialize.d.ts +2 -1
- package/lib/wire/serialize.js +9 -4
- package/lib/wire/statement.d.ts +6 -0
- package/lib/wire/statement.js +11 -0
- package/lib/wire/transaction.d.ts +11 -0
- package/lib/wire/transaction.js +27 -0
- package/lib/wire/xsqlvar.d.ts +9 -0
- package/lib/wire/xsqlvar.js +22 -11
- package/package.json +1 -1
- package/src/pool.ts +126 -6
- package/src/types.ts +68 -0
- package/src/wire/connection.ts +498 -0
- package/src/wire/const.ts +22 -0
- package/src/wire/database.ts +53 -0
- package/src/wire/serialize.ts +11 -5
- package/src/wire/statement.ts +13 -0
- package/src/wire/transaction.ts +30 -0
- package/src/wire/xsqlvar.ts +25 -14
package/lib/wire/serialize.js
CHANGED
|
@@ -226,10 +226,10 @@ class XdrWriter {
|
|
|
226
226
|
}
|
|
227
227
|
addInt64(value) {
|
|
228
228
|
this.ensure(8);
|
|
229
|
-
// Note: precision is limited to Number.MAX_SAFE_INTEGER
|
|
230
|
-
//
|
|
231
|
-
// Long.fromNumber() behaviour.
|
|
232
|
-
this.buffer.writeBigInt64BE(BigInt(Math.trunc(value)), this.pos);
|
|
229
|
+
// Note: for numbers, precision is limited to Number.MAX_SAFE_INTEGER
|
|
230
|
+
// (±2^53-1); values outside this range lose precision, which matches
|
|
231
|
+
// the previous Long.fromNumber() behaviour. BigInts keep full precision.
|
|
232
|
+
this.buffer.writeBigInt64BE(typeof value === 'bigint' ? value : BigInt(Math.trunc(value)), this.pos);
|
|
233
233
|
this.pos += 8;
|
|
234
234
|
}
|
|
235
235
|
addInt128(value) {
|
|
@@ -306,6 +306,11 @@ class XdrWriter {
|
|
|
306
306
|
this.buffer.writeDoubleBE(value, this.pos);
|
|
307
307
|
this.pos += 8;
|
|
308
308
|
}
|
|
309
|
+
addFloat(value) {
|
|
310
|
+
this.ensure(4);
|
|
311
|
+
this.buffer.writeFloatBE(value, this.pos);
|
|
312
|
+
this.pos += 4;
|
|
313
|
+
}
|
|
309
314
|
addQuad(quad) {
|
|
310
315
|
this.ensure(8);
|
|
311
316
|
var b = this.buffer;
|
package/lib/wire/statement.d.ts
CHANGED
|
@@ -21,7 +21,13 @@ declare class Statement {
|
|
|
21
21
|
fetch(transaction: any, count: number | string, callback: (err: any, result?: any) => void): void;
|
|
22
22
|
fetchScroll(transaction: any, direction: string | number, offset?: any, count?: any, callback?: any): void;
|
|
23
23
|
fetchAll(transaction: any, callback: (err: any, result?: any) => void): void;
|
|
24
|
+
/**
|
|
25
|
+
* Execute this statement once per row via the Firebird 4 batch API
|
|
26
|
+
* (protocol 16+). `rows` is an array of parameter arrays.
|
|
27
|
+
*/
|
|
28
|
+
executeBatch(transaction: any, rows: any[][], callback?: any, options?: any): void;
|
|
24
29
|
executeAsync(transaction: any, params?: any, options?: any): Promise<any>;
|
|
30
|
+
executeBatchAsync(transaction: any, rows: any[][], options?: any): Promise<any>;
|
|
25
31
|
fetchAsync(transaction: any, count: number | string): Promise<any>;
|
|
26
32
|
fetchScrollAsync(transaction: any, direction: string | number, offset?: any, count?: any): Promise<any>;
|
|
27
33
|
fetchAllAsync(transaction: any): Promise<any>;
|
package/lib/wire/statement.js
CHANGED
|
@@ -49,11 +49,22 @@ class Statement {
|
|
|
49
49
|
fetchAll(transaction, callback) {
|
|
50
50
|
this.connection.fetchAll(this, transaction, callback);
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Execute this statement once per row via the Firebird 4 batch API
|
|
54
|
+
* (protocol 16+). `rows` is an array of parameter arrays.
|
|
55
|
+
*/
|
|
56
|
+
executeBatch(transaction, rows, callback, options) {
|
|
57
|
+
this.connection.executeBatch(transaction, this, rows, callback, options);
|
|
58
|
+
}
|
|
52
59
|
/* Promise / async-await API — wrappers over the callback methods above. */
|
|
53
60
|
executeAsync(transaction, params, options) {
|
|
54
61
|
var self = this;
|
|
55
62
|
return (0, callback_1.fromCallback)(function (cb) { self.execute(transaction, params, cb, options); });
|
|
56
63
|
}
|
|
64
|
+
executeBatchAsync(transaction, rows, options) {
|
|
65
|
+
var self = this;
|
|
66
|
+
return (0, callback_1.fromCallback)(function (cb) { self.executeBatch(transaction, rows, cb, options); });
|
|
67
|
+
}
|
|
57
68
|
fetchAsync(transaction, count) {
|
|
58
69
|
var self = this;
|
|
59
70
|
return (0, callback_1.fromCallback)(function (cb) { self.fetch(transaction, count, cb); });
|
|
@@ -8,6 +8,17 @@ declare class Transaction {
|
|
|
8
8
|
execute(query: string, params?: any, callback?: any, options?: any): void;
|
|
9
9
|
sequentially(query: string, params?: any, on?: any, callback?: any, options?: any): this;
|
|
10
10
|
query(query: string, params?: any, callback?: any, options?: any): void;
|
|
11
|
+
/**
|
|
12
|
+
* Execute `query` once per row in `rows` using the Firebird 4 batch API
|
|
13
|
+
* (protocol 16+, single network flush). The callback receives a
|
|
14
|
+
* completion object: { recordCount, updateCounts, errors:
|
|
15
|
+
* [{recordNumber, error}], errorRecordNumbers, success }. Per-record
|
|
16
|
+
* failures do NOT roll anything back here — inspect the completion and
|
|
17
|
+
* commit or roll back yourself (or use db.executeBatch for
|
|
18
|
+
* all-or-nothing semantics).
|
|
19
|
+
*/
|
|
20
|
+
executeBatch(query: string, rows: any[][], callback?: any, options?: any): void;
|
|
21
|
+
executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any>;
|
|
11
22
|
commit(callback?: (err?: any) => void): void;
|
|
12
23
|
rollback(callback?: (err?: any) => void): void;
|
|
13
24
|
commitRetaining(callback?: (err?: any) => void): void;
|
package/lib/wire/transaction.js
CHANGED
|
@@ -200,6 +200,33 @@ class Transaction {
|
|
|
200
200
|
};
|
|
201
201
|
this.execute(query, params, callback, options);
|
|
202
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Execute `query` once per row in `rows` using the Firebird 4 batch API
|
|
205
|
+
* (protocol 16+, single network flush). The callback receives a
|
|
206
|
+
* completion object: { recordCount, updateCounts, errors:
|
|
207
|
+
* [{recordNumber, error}], errorRecordNumbers, success }. Per-record
|
|
208
|
+
* failures do NOT roll anything back here — inspect the completion and
|
|
209
|
+
* commit or roll back yourself (or use db.executeBatch for
|
|
210
|
+
* all-or-nothing semantics).
|
|
211
|
+
*/
|
|
212
|
+
executeBatch(query, rows, callback, options) {
|
|
213
|
+
var self = this;
|
|
214
|
+
this.newStatement(query, function (err, statement) {
|
|
215
|
+
if (err) {
|
|
216
|
+
(0, callback_1.doError)(err, callback);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
statement.executeBatch(self, rows, function (err, result) {
|
|
220
|
+
statement.release();
|
|
221
|
+
if (callback)
|
|
222
|
+
callback(err, result);
|
|
223
|
+
}, options);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
executeBatchAsync(query, rows, options) {
|
|
227
|
+
var self = this;
|
|
228
|
+
return (0, callback_1.fromCallback)(function (cb) { self.executeBatch(query, rows, cb, options); });
|
|
229
|
+
}
|
|
203
230
|
commit(callback) {
|
|
204
231
|
this.connection.commit(this, callback);
|
|
205
232
|
}
|
package/lib/wire/xsqlvar.d.ts
CHANGED
|
@@ -155,6 +155,15 @@ export declare class SQLParamBuffer {
|
|
|
155
155
|
encode(data: XdrWriter): void;
|
|
156
156
|
calcBlr(blr: BlrWriter): void;
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Split a JS Date into the Firebird wire representation used by
|
|
160
|
+
* TIMESTAMP/DATE/TIME columns: `date` is the modified-Julian day number and
|
|
161
|
+
* `time` the count of 100-microsecond units since midnight (local time).
|
|
162
|
+
*/
|
|
163
|
+
export declare function encodeDateTimeParts(value: Date): {
|
|
164
|
+
date: number;
|
|
165
|
+
time: number;
|
|
166
|
+
};
|
|
158
167
|
export declare class SQLParamQuad {
|
|
159
168
|
value: any;
|
|
160
169
|
constructor(value: any);
|
package/lib/wire/xsqlvar.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.SQLParamBool = exports.SQLParamDate = exports.SQLParamQuad = exports.SQLParamBuffer = exports.SQLParamString = exports.SQLParamDouble = exports.SQLParamDecFloat34 = exports.SQLParamDecFloat16 = exports.SQLParamInt128 = exports.SQLParamInt64 = exports.SQLParamInt = exports.SQLVarBoolean = exports.SQLVarTimeStampTzEx = exports.SQLVarTimeStampTz = exports.SQLVarTimeTzEx = exports.SQLVarTimeTz = exports.SQLVarTimeStamp = exports.SQLVarTime = exports.SQLVarDate = exports.SQLVarDouble = exports.SQLVarFloat = exports.SQLVarDecFloat34 = exports.SQLVarDecFloat16 = exports.SQLVarInt128 = exports.SQLVarInt64 = exports.SQLVarShort = exports.SQLVarInt = exports.SQLVarArray = exports.SQLVarBlob = exports.SQLVarQuad = exports.SQLVarString = exports.SQLVarNull = exports.SQLVarText = exports.SQLVarBase = void 0;
|
|
7
|
+
exports.encodeDateTimeParts = encodeDateTimeParts;
|
|
7
8
|
const const_1 = __importDefault(require("./const"));
|
|
8
9
|
/***************************************
|
|
9
10
|
*
|
|
@@ -593,6 +594,24 @@ class SQLParamBuffer {
|
|
|
593
594
|
}
|
|
594
595
|
exports.SQLParamBuffer = SQLParamBuffer;
|
|
595
596
|
//------------------------------------------------------
|
|
597
|
+
/**
|
|
598
|
+
* Split a JS Date into the Firebird wire representation used by
|
|
599
|
+
* TIMESTAMP/DATE/TIME columns: `date` is the modified-Julian day number and
|
|
600
|
+
* `time` the count of 100-microsecond units since midnight (local time).
|
|
601
|
+
*/
|
|
602
|
+
function encodeDateTimeParts(value) {
|
|
603
|
+
var ms = value.getTime() - value.getTimezoneOffset() * MsPerMinute;
|
|
604
|
+
var time = ms % TimeCoeff;
|
|
605
|
+
var date = (ms - time) / TimeCoeff + DateOffset;
|
|
606
|
+
time *= 10;
|
|
607
|
+
// check overflow (dates before the epoch)
|
|
608
|
+
if (time < 0) {
|
|
609
|
+
date--;
|
|
610
|
+
time = TimeCoeff * 10 + time;
|
|
611
|
+
}
|
|
612
|
+
return { date: date, time: time };
|
|
613
|
+
}
|
|
614
|
+
//------------------------------------------------------
|
|
596
615
|
class SQLParamQuad {
|
|
597
616
|
constructor(value) {
|
|
598
617
|
this.value = value;
|
|
@@ -621,17 +640,9 @@ class SQLParamDate {
|
|
|
621
640
|
}
|
|
622
641
|
encode(data) {
|
|
623
642
|
if (this.value != null) {
|
|
624
|
-
var
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
time *= 10;
|
|
628
|
-
// check overflow
|
|
629
|
-
if (time < 0) {
|
|
630
|
-
date--;
|
|
631
|
-
time = TimeCoeff * 10 + time;
|
|
632
|
-
}
|
|
633
|
-
data.addInt(date);
|
|
634
|
-
data.addUInt(time);
|
|
643
|
+
var parts = encodeDateTimeParts(this.value);
|
|
644
|
+
data.addInt(parts.date);
|
|
645
|
+
data.addUInt(parts.time);
|
|
635
646
|
}
|
|
636
647
|
else {
|
|
637
648
|
data.addInt(0);
|
package/package.json
CHANGED
package/src/pool.ts
CHANGED
|
@@ -4,32 +4,140 @@
|
|
|
4
4
|
*
|
|
5
5
|
***************************************/
|
|
6
6
|
|
|
7
|
+
import Events from 'events';
|
|
7
8
|
import { fromCallback } from './callback';
|
|
8
9
|
import type { Callback } from './callback';
|
|
9
10
|
|
|
10
11
|
type AttachFn = (options: any, callback: Callback) => void;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Connection pool with pg.Pool-style observability.
|
|
15
|
+
*
|
|
16
|
+
* Events (all optional to listen to):
|
|
17
|
+
* 'connect' (db) — a new physical connection was established
|
|
18
|
+
* 'acquire' (db) — a connection was handed to a caller
|
|
19
|
+
* 'release' (db) — a connection was returned to the idle pool
|
|
20
|
+
* 'remove' (db) — a physical connection left the pool for good
|
|
21
|
+
* 'error' (err, db) — a background error (idle eviction, reaper detach);
|
|
22
|
+
* only emitted when a listener is attached, so
|
|
23
|
+
* existing applications never crash on it
|
|
24
|
+
*
|
|
25
|
+
* Metrics: totalCount, idleCount, activeCount, waitingCount.
|
|
26
|
+
*
|
|
27
|
+
* Options: max (factory argument), options.min (floor the reaper never
|
|
28
|
+
* shrinks below), options.idleTimeoutMillis (close idle connections after
|
|
29
|
+
* this many ms; 0/absent = never), options.connectTimeout.
|
|
30
|
+
*/
|
|
31
|
+
class Pool extends Events.EventEmitter {
|
|
13
32
|
attach: AttachFn;
|
|
14
33
|
internaldb: any[];
|
|
15
34
|
pooldb: any[];
|
|
16
35
|
dbinuse: number;
|
|
17
36
|
_creating: number;
|
|
18
37
|
max: number;
|
|
38
|
+
min: number;
|
|
39
|
+
idleTimeoutMillis: number;
|
|
19
40
|
pending: Callback[];
|
|
20
41
|
options: any;
|
|
21
42
|
_destroyed: boolean;
|
|
43
|
+
_reaper: NodeJS.Timeout | null;
|
|
22
44
|
|
|
23
45
|
constructor(attach: AttachFn, max: number, options: any) {
|
|
46
|
+
super();
|
|
24
47
|
this.attach = attach;
|
|
25
48
|
this.internaldb = []; // connections created by the pool (for destroy)
|
|
26
49
|
this.pooldb = []; // connections available in the pool (idle)
|
|
27
50
|
this.dbinuse = 0; // connections currently handed out to callers
|
|
28
51
|
this._creating = 0; // connections currently being created (attach in flight)
|
|
29
52
|
this.max = max || 4;
|
|
53
|
+
this.min = (options && options.min > 0) ? Math.min(options.min, this.max) : 0;
|
|
54
|
+
this.idleTimeoutMillis = (options && options.idleTimeoutMillis > 0) ? options.idleTimeoutMillis : 0;
|
|
30
55
|
this.pending = []; // callbacks waiting for a free slot
|
|
31
56
|
this.options = options;
|
|
32
57
|
this._destroyed = false; // true after destroy() — prevents further use
|
|
58
|
+
this._reaper = null;
|
|
59
|
+
|
|
60
|
+
if (this.idleTimeoutMillis) {
|
|
61
|
+
var self = this;
|
|
62
|
+
// Sweep at half the idle timeout (bounded to 100ms..30s) so a
|
|
63
|
+
// connection lives at most ~1.5x idleTimeoutMillis. unref() keeps
|
|
64
|
+
// the timer from holding the process open.
|
|
65
|
+
var interval = Math.min(Math.max(this.idleTimeoutMillis / 2, 100), 30000);
|
|
66
|
+
this._reaper = setInterval(function() { self._reap(); }, interval);
|
|
67
|
+
if (this._reaper.unref) this._reaper.unref();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Physical connections owned by the pool (idle + in use). */
|
|
72
|
+
get totalCount(): number {
|
|
73
|
+
return this.internaldb.length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Connections sitting idle in the pool. */
|
|
77
|
+
get idleCount(): number {
|
|
78
|
+
return this.pooldb.length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Connections currently handed out to callers. */
|
|
82
|
+
get activeCount(): number {
|
|
83
|
+
return this.dbinuse;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** get() requests queued for a free slot. */
|
|
87
|
+
get waitingCount(): number {
|
|
88
|
+
return this.pending.length;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** True when the connection can no longer be used. */
|
|
92
|
+
_isDead(db: any): boolean {
|
|
93
|
+
return !db.connection || db.connection._isClosed || db.connection._isDetach ||
|
|
94
|
+
!db.connection._socket || db.connection._socket.destroyed;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Drop a physical connection from the pool's books and emit 'remove'. */
|
|
98
|
+
_forget(db: any): void {
|
|
99
|
+
var idx = this.internaldb.indexOf(db);
|
|
100
|
+
if (idx !== -1) this.internaldb.splice(idx, 1);
|
|
101
|
+
idx = this.pooldb.indexOf(db);
|
|
102
|
+
if (idx !== -1) this.pooldb.splice(idx, 1);
|
|
103
|
+
this.emit('remove', db);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Emit 'error' only when someone listens — never crash the app. */
|
|
107
|
+
_emitError(err: any, db?: any): void {
|
|
108
|
+
if (this.listenerCount('error') > 0) this.emit('error', err, db);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Idle sweep: evict dead idle connections immediately and close healthy
|
|
113
|
+
* ones that have been idle longer than idleTimeoutMillis, keeping at
|
|
114
|
+
* least `min` physical connections. (issue #329)
|
|
115
|
+
*/
|
|
116
|
+
_reap(): void {
|
|
117
|
+
if (this._destroyed) return;
|
|
118
|
+
var self = this;
|
|
119
|
+
var now = Date.now();
|
|
120
|
+
|
|
121
|
+
// iterate over a copy — we splice from pooldb while walking
|
|
122
|
+
this.pooldb.slice().forEach(function(db) {
|
|
123
|
+
if (self._isDead(db)) {
|
|
124
|
+
self._forget(db);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (self.internaldb.length <= self.min) return;
|
|
128
|
+
var idleSince = typeof db.__poolIdleSince === 'number' ? db.__poolIdleSince : now;
|
|
129
|
+
if (now - idleSince < self.idleTimeoutMillis) return;
|
|
130
|
+
|
|
131
|
+
self._forget(db);
|
|
132
|
+
db.connection._pooled = false;
|
|
133
|
+
try {
|
|
134
|
+
db.detach(function(err?: any) {
|
|
135
|
+
if (err) self._emitError(err, db);
|
|
136
|
+
});
|
|
137
|
+
} catch (e) {
|
|
138
|
+
self._emitError(e, db);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
33
141
|
}
|
|
34
142
|
|
|
35
143
|
get(callback: Callback): this {
|
|
@@ -59,15 +167,15 @@ class Pool {
|
|
|
59
167
|
if (self.pooldb.length) {
|
|
60
168
|
var db = self.pooldb.shift();
|
|
61
169
|
// Discard connections that have been closed or destroyed while idle
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
if (idx !== -1) self.internaldb.splice(idx, 1);
|
|
170
|
+
if (self._isDead(db)) {
|
|
171
|
+
self._forget(db);
|
|
65
172
|
self.pending.unshift(cb);
|
|
66
173
|
setImmediate(function () { self.check(); });
|
|
67
174
|
return self;
|
|
68
175
|
}
|
|
69
176
|
// Idle connection available — hand it out immediately.
|
|
70
177
|
self.dbinuse++;
|
|
178
|
+
self.emit('acquire', db);
|
|
71
179
|
cb(null, db);
|
|
72
180
|
} else {
|
|
73
181
|
// No idle connection — create a new one via attach().
|
|
@@ -133,14 +241,20 @@ class Pool {
|
|
|
133
241
|
if (self.pooldb.indexOf(db) !== -1 || self.internaldb.indexOf(db) === -1)
|
|
134
242
|
return;
|
|
135
243
|
// if not usable don't put it back in the pool
|
|
136
|
-
if (db.connection._isClosed || db.connection._isDetach || db.connection._pooled === false)
|
|
244
|
+
if (db.connection._isClosed || db.connection._isDetach || db.connection._pooled === false) {
|
|
137
245
|
self.internaldb.splice(self.internaldb.indexOf(db), 1);
|
|
138
|
-
|
|
246
|
+
self.emit('remove', db);
|
|
247
|
+
} else {
|
|
248
|
+
db.__poolIdleSince = Date.now();
|
|
139
249
|
self.pooldb.push(db);
|
|
250
|
+
self.emit('release', db);
|
|
251
|
+
}
|
|
140
252
|
|
|
141
253
|
self.dbinuse--;
|
|
142
254
|
self.check();
|
|
143
255
|
});
|
|
256
|
+
self.emit('connect', db);
|
|
257
|
+
self.emit('acquire', db);
|
|
144
258
|
}
|
|
145
259
|
|
|
146
260
|
cb(err, db);
|
|
@@ -157,6 +271,11 @@ class Pool {
|
|
|
157
271
|
var self = this;
|
|
158
272
|
self._destroyed = true;
|
|
159
273
|
|
|
274
|
+
if (self._reaper) {
|
|
275
|
+
clearInterval(self._reaper);
|
|
276
|
+
self._reaper = null;
|
|
277
|
+
}
|
|
278
|
+
|
|
160
279
|
// [Fix 4] Drain pending callbacks so callers are not left hanging.
|
|
161
280
|
// This is critical when destroy() is called as a recovery measure after
|
|
162
281
|
// a timeout: without draining, every concurrent pool.get() that had not
|
|
@@ -197,6 +316,7 @@ class Pool {
|
|
|
197
316
|
self.pooldb.splice(_db_in_pool, 1);
|
|
198
317
|
db.connection._pooled = false;
|
|
199
318
|
db.detach(detachCallback);
|
|
319
|
+
self.emit('remove', db);
|
|
200
320
|
} else {
|
|
201
321
|
// [Fix 5] Connection is currently in use (dbinuse > 0).
|
|
202
322
|
// The caller is responsible for releasing it via detach().
|
package/src/types.ts
CHANGED
|
@@ -75,6 +75,31 @@ export type TransactionOptions = {
|
|
|
75
75
|
waitTimeout?: number;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
/** Result of an executeBatch call (Firebird 4 batch API, protocol 16+). */
|
|
79
|
+
export interface BatchResult {
|
|
80
|
+
/** Total number of records processed by the server. */
|
|
81
|
+
recordCount: number;
|
|
82
|
+
/** Per-record update counts (in record order). */
|
|
83
|
+
updateCounts: number[];
|
|
84
|
+
/** Per-record failures with full status vectors (capped by `detailedErrors`). */
|
|
85
|
+
errors: Array<{ recordNumber: number; error: Error }>;
|
|
86
|
+
/** Record numbers of ALL failed records (detailed + status-less). */
|
|
87
|
+
errorRecordNumbers: number[];
|
|
88
|
+
/** True when every record executed without error. */
|
|
89
|
+
success: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type BatchOptions = {
|
|
93
|
+
/** Continue past per-record errors and report them all (default true). */
|
|
94
|
+
multiError?: boolean;
|
|
95
|
+
/** Server-side batch buffer limit in bytes (BATCH_TAG_BUFFER_BYTES_SIZE). */
|
|
96
|
+
bufferSize?: number;
|
|
97
|
+
/** Max number of detailed per-record status vectors returned (server default 64). */
|
|
98
|
+
detailedErrors?: number;
|
|
99
|
+
/** Rows per op_batch_msg packet (default 500). */
|
|
100
|
+
chunkSize?: number;
|
|
101
|
+
};
|
|
102
|
+
|
|
78
103
|
export type QueryOptions = {
|
|
79
104
|
timeout?: number;
|
|
80
105
|
scrollable?: boolean;
|
|
@@ -95,6 +120,8 @@ export interface Database {
|
|
|
95
120
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
|
|
96
121
|
query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
|
|
97
122
|
execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
|
|
123
|
+
/** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
|
|
124
|
+
executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
|
|
98
125
|
sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
|
|
99
126
|
drop(callback: SimpleCallback): void;
|
|
100
127
|
escape(value: any): string;
|
|
@@ -108,6 +135,7 @@ export interface Database {
|
|
|
108
135
|
// Result metadata is only available through the callback API.
|
|
109
136
|
queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
|
|
110
137
|
executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
|
|
138
|
+
executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
|
|
111
139
|
sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
112
140
|
sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
113
141
|
transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
|
|
@@ -132,6 +160,8 @@ export interface Transaction {
|
|
|
132
160
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
|
|
133
161
|
query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
|
|
134
162
|
execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
|
|
163
|
+
/** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
|
|
164
|
+
executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
|
|
135
165
|
sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
|
|
136
166
|
commit(callback?: SimpleCallback): void;
|
|
137
167
|
commitRetaining(callback?: SimpleCallback): void;
|
|
@@ -141,6 +171,7 @@ export interface Transaction {
|
|
|
141
171
|
// Promise / async-await API
|
|
142
172
|
queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
|
|
143
173
|
executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
|
|
174
|
+
executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
|
|
144
175
|
sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
145
176
|
sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
|
|
146
177
|
newStatementAsync(query: string): Promise<Statement>;
|
|
@@ -159,8 +190,12 @@ export interface Statement {
|
|
|
159
190
|
fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
|
|
160
191
|
fetchAll(transaction: Transaction, callback: QueryCallback): void;
|
|
161
192
|
|
|
193
|
+
/** Execute this prepared statement once per row (Firebird 4.0+ batch API). */
|
|
194
|
+
executeBatch(transaction: Transaction, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
|
|
195
|
+
|
|
162
196
|
// Promise / async-await API
|
|
163
197
|
executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
|
|
198
|
+
executeBatchAsync(transaction: Transaction, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
|
|
164
199
|
fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
|
|
165
200
|
fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
|
|
166
201
|
fetchAllAsync(transaction: Transaction): Promise<any>;
|
|
@@ -246,6 +281,19 @@ export interface Options {
|
|
|
246
281
|
* expected Firebird server response time under load.
|
|
247
282
|
*/
|
|
248
283
|
connectTimeout?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Pool only: minimum number of physical connections the idle reaper
|
|
286
|
+
* keeps alive. Only meaningful together with `idleTimeoutMillis`.
|
|
287
|
+
* Default 0 (the pool may shrink to no connections).
|
|
288
|
+
*/
|
|
289
|
+
min?: number;
|
|
290
|
+
/**
|
|
291
|
+
* Pool only: close connections that have been idle in the pool for this
|
|
292
|
+
* many milliseconds, never shrinking below `min`. Dead idle connections
|
|
293
|
+
* (server restarts, dropped sockets) are evicted on the same sweep.
|
|
294
|
+
* Default 0 (idle connections are kept forever).
|
|
295
|
+
*/
|
|
296
|
+
idleTimeoutMillis?: number;
|
|
249
297
|
/**
|
|
250
298
|
* **Firebird 6.0+ only (Protocol 20+)**
|
|
251
299
|
*
|
|
@@ -289,10 +337,30 @@ export interface SvcMgrOptions extends Options {
|
|
|
289
337
|
manager: true; // Attach to ServiceManager
|
|
290
338
|
}
|
|
291
339
|
|
|
340
|
+
export type PoolEvent = 'connect' | 'acquire' | 'release' | 'remove' | 'error';
|
|
341
|
+
|
|
292
342
|
export interface ConnectionPool {
|
|
293
343
|
get(callback: DatabaseCallback): void;
|
|
294
344
|
destroy(callback?: SimpleCallback): void;
|
|
295
345
|
|
|
346
|
+
// Metrics (live counters, pg.Pool-style)
|
|
347
|
+
/** Physical connections owned by the pool (idle + in use). */
|
|
348
|
+
readonly totalCount: number;
|
|
349
|
+
/** Connections sitting idle in the pool. */
|
|
350
|
+
readonly idleCount: number;
|
|
351
|
+
/** Connections currently handed out to callers. */
|
|
352
|
+
readonly activeCount: number;
|
|
353
|
+
/** get() requests queued for a free slot. */
|
|
354
|
+
readonly waitingCount: number;
|
|
355
|
+
|
|
356
|
+
// Events
|
|
357
|
+
on(event: 'connect' | 'acquire' | 'release' | 'remove', listener: (db: Database) => void): this;
|
|
358
|
+
on(event: 'error', listener: (err: Error, db?: Database) => void): this;
|
|
359
|
+
once(event: 'connect' | 'acquire' | 'release' | 'remove', listener: (db: Database) => void): this;
|
|
360
|
+
once(event: 'error', listener: (err: Error, db?: Database) => void): this;
|
|
361
|
+
off(event: PoolEvent, listener: (...args: any[]) => void): this;
|
|
362
|
+
removeListener(event: PoolEvent, listener: (...args: any[]) => void): this;
|
|
363
|
+
|
|
296
364
|
// Promise / async-await API
|
|
297
365
|
getAsync(): Promise<Database>;
|
|
298
366
|
destroyAsync(): Promise<void>;
|