node-firebird 2.12.0 → 2.13.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 +96 -6
- package/lib/types.d.ts +38 -5
- package/lib/utils.d.ts +12 -0
- package/lib/utils.js +20 -2
- package/lib/wire/batch-stream.d.ts +26 -0
- package/lib/wire/batch-stream.js +109 -0
- package/lib/wire/codepages.d.ts +23 -0
- package/lib/wire/codepages.js +137 -0
- package/lib/wire/connection.d.ts +38 -4
- package/lib/wire/connection.js +294 -44
- package/lib/wire/database.d.ts +9 -0
- package/lib/wire/database.js +13 -6
- package/lib/wire/serialize.d.ts +2 -0
- package/lib/wire/serialize.js +10 -0
- package/lib/wire/socket.js +9 -3
- package/lib/wire/transaction.d.ts +6 -0
- package/lib/wire/transaction.js +27 -2
- package/lib/wire/xsqlvar.d.ts +56 -1
- package/lib/wire/xsqlvar.js +107 -29
- package/package.json +19 -1
- package/src/types.ts +40 -5
- package/src/utils.ts +19 -1
- package/src/wire/batch-stream.ts +121 -0
- package/src/wire/codepages.ts +147 -0
- package/src/wire/connection.ts +315 -49
- package/src/wire/database.ts +15 -7
- package/src/wire/serialize.ts +11 -0
- package/src/wire/socket.ts +9 -3
- package/src/wire/transaction.ts +28 -2
- package/src/wire/xsqlvar.ts +129 -30
package/lib/wire/serialize.js
CHANGED
|
@@ -277,6 +277,16 @@ class XdrWriter {
|
|
|
277
277
|
this.buffer.fill(0, this.pos + len, this.pos + alen);
|
|
278
278
|
this.pos += alen;
|
|
279
279
|
}
|
|
280
|
+
/** addString for pre-encoded bytes (codepage connection charsets). */
|
|
281
|
+
addStringBuffer(b) {
|
|
282
|
+
var alen = align(b.length);
|
|
283
|
+
this.ensure(alen + 4);
|
|
284
|
+
this.buffer.writeInt32BE(b.length, this.pos);
|
|
285
|
+
this.pos += 4;
|
|
286
|
+
b.copy(this.buffer, this.pos);
|
|
287
|
+
this.buffer.fill(0, this.pos + b.length, this.pos + alen);
|
|
288
|
+
this.pos += alen;
|
|
289
|
+
}
|
|
280
290
|
addText(s, encoding) {
|
|
281
291
|
var len = Buffer.byteLength(s, encoding);
|
|
282
292
|
var alen = align(len);
|
package/lib/wire/socket.js
CHANGED
|
@@ -132,8 +132,14 @@ class Socket {
|
|
|
132
132
|
* Compress and/or encrypt data before sending to socket.
|
|
133
133
|
*/
|
|
134
134
|
write(data, defer = false) {
|
|
135
|
+
// Callers pass views of the connection's shared _msg buffer, and both
|
|
136
|
+
// net.Socket (when it cannot flush immediately) and zlib keep a
|
|
137
|
+
// REFERENCE to the chunk — a later sender rebuilding _msg would then
|
|
138
|
+
// corrupt this queued packet (intermittent, load-dependent). Own the
|
|
139
|
+
// bytes at the send boundary, once.
|
|
140
|
+
data = Buffer.from(data);
|
|
135
141
|
if (process.env.FIREBIRD_DEBUG) {
|
|
136
|
-
console.log('[fb-debug] socket.write: length=%d bytes=%s encrypt=%s defer=%s', data.length,
|
|
142
|
+
console.log('[fb-debug] socket.write: length=%d bytes=%s encrypt=%s defer=%s', data.length, data.toString('hex'), this.encrypt, defer);
|
|
137
143
|
}
|
|
138
144
|
if (defer) {
|
|
139
145
|
// Accumulate deferred packets instead of overwriting. Multiple
|
|
@@ -142,8 +148,8 @@ class Socket {
|
|
|
142
148
|
// overwriting the buffer silently drops packets and desynchronises
|
|
143
149
|
// the request/response queue, causing the connection to hang.
|
|
144
150
|
this.buffer = this.buffer
|
|
145
|
-
? Buffer.concat([this.buffer,
|
|
146
|
-
:
|
|
151
|
+
? Buffer.concat([this.buffer, data])
|
|
152
|
+
: data;
|
|
147
153
|
return;
|
|
148
154
|
}
|
|
149
155
|
if (!defer && this.buffer) {
|
|
@@ -48,6 +48,12 @@ declare class Transaction {
|
|
|
48
48
|
* stream ends — commit or roll back yourself.
|
|
49
49
|
*/
|
|
50
50
|
queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): import("node:stream").Readable;
|
|
51
|
+
/**
|
|
52
|
+
* Bulk-insert Writable running inside this transaction (see
|
|
53
|
+
* Database.batchStream). The transaction is NOT committed or rolled
|
|
54
|
+
* back by the stream — settle it yourself after 'finish'/'error'.
|
|
55
|
+
*/
|
|
56
|
+
batchStream(query: string, options?: any): import("node:stream").Writable;
|
|
51
57
|
query(query: string, params?: QueryParams | Callback, callback?: any, options?: InternalQueryOptions): void;
|
|
52
58
|
/**
|
|
53
59
|
* Execute `query` once per row in `rows` using the Firebird 4 batch API
|
package/lib/wire/transaction.js
CHANGED
|
@@ -9,6 +9,7 @@ const const_1 = __importDefault(require("./const"));
|
|
|
9
9
|
const sql_template_1 = require("../sql-template");
|
|
10
10
|
const xsqlvar_1 = require("./xsqlvar");
|
|
11
11
|
const query_stream_1 = __importDefault(require("./query-stream"));
|
|
12
|
+
const batch_stream_1 = __importDefault(require("./batch-stream"));
|
|
12
13
|
/***************************************
|
|
13
14
|
*
|
|
14
15
|
* Transaction
|
|
@@ -254,7 +255,17 @@ class Transaction {
|
|
|
254
255
|
break;
|
|
255
256
|
case const_1.default.isc_info_sql_stmt_exec_procedure:
|
|
256
257
|
if (ret && ret.data && ret.data.length > 0) {
|
|
257
|
-
|
|
258
|
+
// singleton op_execute2 rows never pass through
|
|
259
|
+
// fetchAll, so their blobAsText fetches must be
|
|
260
|
+
// resolved here (issue #305: EXECUTE PROCEDURE
|
|
261
|
+
// returned text blobs as unresolved functions)
|
|
262
|
+
self.connection.resolveTextBlobs(self, ret, function (blobErr) {
|
|
263
|
+
if (blobErr) {
|
|
264
|
+
dropError(blobErr);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
deliver(ret.data[0], true);
|
|
268
|
+
});
|
|
258
269
|
break;
|
|
259
270
|
}
|
|
260
271
|
else if (statement.output.length) {
|
|
@@ -263,7 +274,13 @@ class Transaction {
|
|
|
263
274
|
dropError(err);
|
|
264
275
|
return;
|
|
265
276
|
}
|
|
266
|
-
|
|
277
|
+
self.connection.resolveTextBlobs(self, fret, function (blobErr) {
|
|
278
|
+
if (blobErr) {
|
|
279
|
+
dropError(blobErr);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
deliver(fret.data[0], false);
|
|
283
|
+
});
|
|
267
284
|
});
|
|
268
285
|
break;
|
|
269
286
|
}
|
|
@@ -344,6 +361,14 @@ class Transaction {
|
|
|
344
361
|
queryStream(query, params, options) {
|
|
345
362
|
return (0, query_stream_1.default)(this, query, params, options);
|
|
346
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Bulk-insert Writable running inside this transaction (see
|
|
366
|
+
* Database.batchStream). The transaction is NOT committed or rolled
|
|
367
|
+
* back by the stream — settle it yourself after 'finish'/'error'.
|
|
368
|
+
*/
|
|
369
|
+
batchStream(query, options) {
|
|
370
|
+
return (0, batch_stream_1.default)(this, query, options, false);
|
|
371
|
+
}
|
|
347
372
|
query(query, params, callback, options = {}) {
|
|
348
373
|
if (params instanceof Function) {
|
|
349
374
|
callback = params;
|
package/lib/wire/xsqlvar.d.ts
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
|
+
import type { TextCodec } from './codepages';
|
|
1
2
|
import type { XdrReader, XdrWriter, BlrWriter } from './serialize';
|
|
2
3
|
import type { RecordCounts } from '../types';
|
|
4
|
+
export declare function getFirebirdCharsetWidth(charset?: string): number;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the Node.js Buffer encoding to use when decoding text from a
|
|
7
|
+
* Firebird response buffer.
|
|
8
|
+
*
|
|
9
|
+
* @param {object|null} options Connection options object (may be falsy).
|
|
10
|
+
* @returns {string} A Node.js-compatible encoding string.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveTextEncoding(options?: any): BufferEncoding;
|
|
13
|
+
/**
|
|
14
|
+
* Codec for the CONNECTION charset when it is a codepage Node cannot
|
|
15
|
+
* handle natively (WIN1251, ISO8859_7, KOI8R, …); null on the native
|
|
16
|
+
* path (UTF8/latin1/ascii) and for unknown charsets. With a codec
|
|
17
|
+
* connection charset the server transliterates all text to that
|
|
18
|
+
* codepage, so every text column, parameter, SQL string and text blob
|
|
19
|
+
* goes through the codec (issues #319/#301).
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveTextCodec(options?: any): TextCodec | null;
|
|
22
|
+
interface TextState {
|
|
23
|
+
key: string | undefined;
|
|
24
|
+
codec: TextCodec | null;
|
|
25
|
+
enc: BufferEncoding;
|
|
26
|
+
width: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-connection text handling, resolved once and memoized on the
|
|
30
|
+
* long-lived options object: the decode loop calls this per CELL, and
|
|
31
|
+
* recomputing uppercased names + map lookups a million times per large
|
|
32
|
+
* fetch is pure waste. Invalidated if options.encoding ever changes.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveTextState(options?: any): TextState;
|
|
35
|
+
/**
|
|
36
|
+
* Encode text in the CONNECTION charset — the byte form the server
|
|
37
|
+
* expects for parameters, SQL statement text and text-blob content.
|
|
38
|
+
*/
|
|
39
|
+
export declare function encodeConnectionText(options: any, value: string): Buffer;
|
|
40
|
+
/**
|
|
41
|
+
* Decode connection-charset bytes to text (the read counterpart of
|
|
42
|
+
* encodeConnectionText — used for text blobs).
|
|
43
|
+
*/
|
|
44
|
+
export declare function decodeConnectionText(options: any, buffer: Buffer): string;
|
|
3
45
|
/**
|
|
4
46
|
* Common shape of all SQLVar descriptor objects. The metadata properties
|
|
5
47
|
* are populated externally (in connection.ts) from the op_prepare_statement
|
|
@@ -19,6 +61,9 @@ export declare abstract class SQLVarBase {
|
|
|
19
61
|
owner?: string;
|
|
20
62
|
charSetId?: number;
|
|
21
63
|
collationId?: number;
|
|
64
|
+
/** Original declared byte length when scaleOutputLengths widened
|
|
65
|
+
* `length` for the fetch capacity check (issue #422). */
|
|
66
|
+
nativeLength?: number;
|
|
22
67
|
abstract decode(data: XdrReader, lowerV13: boolean, options?: any): any;
|
|
23
68
|
abstract calcBlr(blr: BlrWriter): void;
|
|
24
69
|
}
|
|
@@ -278,7 +323,17 @@ export declare class SQLParamDate {
|
|
|
278
323
|
}
|
|
279
324
|
export declare class SQLParamBool {
|
|
280
325
|
value: any;
|
|
281
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Encode as a real BOOLEAN (blr_bool + xdr opaque byte) instead of the
|
|
328
|
+
* legacy blr_short 0/1. Set when the DESCRIBED parameter type is
|
|
329
|
+
* SQL_BOOLEAN: Firebird refuses smallint→BOOLEAN conversion
|
|
330
|
+
* ("conversion error from string", issue #122), and conversely BOOLEAN
|
|
331
|
+
* does not convert to numbers — so smallint targets keep the legacy
|
|
332
|
+
* form for compatibility.
|
|
333
|
+
*/
|
|
334
|
+
asBoolean: boolean;
|
|
335
|
+
constructor(value: any, asBoolean?: boolean);
|
|
282
336
|
encode(data: XdrWriter): void;
|
|
283
337
|
calcBlr(blr: BlrWriter): void;
|
|
284
338
|
}
|
|
339
|
+
export {};
|
package/lib/wire/xsqlvar.js
CHANGED
|
@@ -4,6 +4,12 @@ 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.SQL_TYPE_NAMES = exports.SQLVarBase = void 0;
|
|
7
|
+
exports.getFirebirdCharsetWidth = getFirebirdCharsetWidth;
|
|
8
|
+
exports.resolveTextEncoding = resolveTextEncoding;
|
|
9
|
+
exports.resolveTextCodec = resolveTextCodec;
|
|
10
|
+
exports.resolveTextState = resolveTextState;
|
|
11
|
+
exports.encodeConnectionText = encodeConnectionText;
|
|
12
|
+
exports.decodeConnectionText = decodeConnectionText;
|
|
7
13
|
exports.computeColumnKeys = computeColumnKeys;
|
|
8
14
|
exports.camelizeKey = camelizeKey;
|
|
9
15
|
exports.resolveKeyTransform = resolveKeyTransform;
|
|
@@ -15,6 +21,7 @@ exports.parseRecordCounts = parseRecordCounts;
|
|
|
15
21
|
exports.encodeDateTimeParts = encodeDateTimeParts;
|
|
16
22
|
const const_1 = __importDefault(require("./const"));
|
|
17
23
|
const serialize_1 = require("./serialize");
|
|
24
|
+
const codepages_1 = require("./codepages");
|
|
18
25
|
/***************************************
|
|
19
26
|
*
|
|
20
27
|
* SQLVar
|
|
@@ -22,6 +29,7 @@ const serialize_1 = require("./serialize");
|
|
|
22
29
|
***************************************/
|
|
23
30
|
const ScaleDivisor = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000];
|
|
24
31
|
const DateOffset = 40587, TimeCoeff = 86400000, MsPerMinute = 60000;
|
|
32
|
+
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
25
33
|
/**
|
|
26
34
|
* Maps Firebird character-set names (upper-case) to the Node.js Buffer
|
|
27
35
|
* encoding string used by Buffer.toString() / Buffer.from().
|
|
@@ -46,8 +54,16 @@ const FirebirdToNodeEncoding = Object.freeze({
|
|
|
46
54
|
const FirebirdCharsetWidths = {
|
|
47
55
|
'UTF8': 4,
|
|
48
56
|
'UNICODE_FSS': 3,
|
|
49
|
-
'SJIS'
|
|
50
|
-
|
|
57
|
+
// real Firebird names — the bare 'SJIS'/'EUCJ' keys never matched a
|
|
58
|
+
// valid encoding option and silently resolved to width 1
|
|
59
|
+
'SJIS_0208': 2,
|
|
60
|
+
'EUCJ_0208': 2,
|
|
61
|
+
'KSC_5601': 2,
|
|
62
|
+
'BIG_5': 2,
|
|
63
|
+
'GB_2312': 2,
|
|
64
|
+
'GBK': 2,
|
|
65
|
+
'CP943C': 2,
|
|
66
|
+
'GB18030': 4,
|
|
51
67
|
};
|
|
52
68
|
function getFirebirdCharsetWidth(charset) {
|
|
53
69
|
if (!charset)
|
|
@@ -68,6 +84,67 @@ function resolveTextEncoding(options) {
|
|
|
68
84
|
: const_1.default.DEFAULT_ENCODING;
|
|
69
85
|
return (FirebirdToNodeEncoding[encoding] || const_1.default.DEFAULT_ENCODING.toLowerCase());
|
|
70
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Codec for the CONNECTION charset when it is a codepage Node cannot
|
|
89
|
+
* handle natively (WIN1251, ISO8859_7, KOI8R, …); null on the native
|
|
90
|
+
* path (UTF8/latin1/ascii) and for unknown charsets. With a codec
|
|
91
|
+
* connection charset the server transliterates all text to that
|
|
92
|
+
* codepage, so every text column, parameter, SQL string and text blob
|
|
93
|
+
* goes through the codec (issues #319/#301).
|
|
94
|
+
*/
|
|
95
|
+
function resolveTextCodec(options) {
|
|
96
|
+
return resolveTextState(options).codec;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Per-connection text handling, resolved once and memoized on the
|
|
100
|
+
* long-lived options object: the decode loop calls this per CELL, and
|
|
101
|
+
* recomputing uppercased names + map lookups a million times per large
|
|
102
|
+
* fetch is pure waste. Invalidated if options.encoding ever changes.
|
|
103
|
+
*/
|
|
104
|
+
function resolveTextState(options) {
|
|
105
|
+
const key = options && options.encoding;
|
|
106
|
+
if (options && options.__textState && options.__textState.key === key) {
|
|
107
|
+
return options.__textState;
|
|
108
|
+
}
|
|
109
|
+
const encoding = (key || const_1.default.DEFAULT_ENCODING).toUpperCase();
|
|
110
|
+
const state = {
|
|
111
|
+
key,
|
|
112
|
+
codec: FirebirdToNodeEncoding[encoding] ? null : (0, codepages_1.getCodec)(encoding),
|
|
113
|
+
enc: (FirebirdToNodeEncoding[encoding] || const_1.default.DEFAULT_ENCODING.toLowerCase()),
|
|
114
|
+
width: getFirebirdCharsetWidth(encoding),
|
|
115
|
+
};
|
|
116
|
+
if (options) {
|
|
117
|
+
options.__textState = state;
|
|
118
|
+
}
|
|
119
|
+
return state;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Encode text in the CONNECTION charset — the byte form the server
|
|
123
|
+
* expects for parameters, SQL statement text and text-blob content.
|
|
124
|
+
*/
|
|
125
|
+
function encodeConnectionText(options, value) {
|
|
126
|
+
const state = resolveTextState(options);
|
|
127
|
+
if (state.codec) {
|
|
128
|
+
return state.codec.encode(value);
|
|
129
|
+
}
|
|
130
|
+
if (state.enc === 'ascii') {
|
|
131
|
+
// Node's 'ascii' encoding masks high bits (0xE4 → 'd') — replace
|
|
132
|
+
// non-ASCII with '?' instead, matching the codec policy
|
|
133
|
+
value = value.replace(/[^\x00-\x7F]/g, '?');
|
|
134
|
+
}
|
|
135
|
+
return Buffer.from(value, state.enc);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Decode connection-charset bytes to text (the read counterpart of
|
|
139
|
+
* encodeConnectionText — used for text blobs).
|
|
140
|
+
*/
|
|
141
|
+
function decodeConnectionText(options, buffer) {
|
|
142
|
+
const codec = resolveTextCodec(options);
|
|
143
|
+
if (codec) {
|
|
144
|
+
return codec.decode(buffer);
|
|
145
|
+
}
|
|
146
|
+
return buffer.toString(resolveTextEncoding(options));
|
|
147
|
+
}
|
|
71
148
|
//------------------------------------------------------
|
|
72
149
|
/**
|
|
73
150
|
* Common shape of all SQLVar descriptor objects. The metadata properties
|
|
@@ -222,7 +299,9 @@ function describeField(meta) {
|
|
|
222
299
|
typeName: exports.SQL_TYPE_NAMES[meta.type] || 'UNKNOWN',
|
|
223
300
|
subType: meta.subType,
|
|
224
301
|
scale: meta.scale,
|
|
225
|
-
|
|
302
|
+
// report the column's true declared length, not the widened fetch
|
|
303
|
+
// buffer (see scaleOutputLengths)
|
|
304
|
+
length: meta.nativeLength !== undefined ? meta.nativeLength : meta.length,
|
|
226
305
|
nullable: meta.nullable,
|
|
227
306
|
field: meta.field,
|
|
228
307
|
relation: meta.relation,
|
|
@@ -290,23 +369,12 @@ function parseRecordCounts(buffer) {
|
|
|
290
369
|
class SQLVarText extends SQLVarBase {
|
|
291
370
|
decode(data, lowerV13, options) {
|
|
292
371
|
let ret;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
const charLength = Math.floor(this.length / width);
|
|
300
|
-
if (ret.length > charLength) {
|
|
301
|
-
ret = ret.substring(0, charLength);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
else if (this.subType === 0) {
|
|
305
|
-
// without charset definition
|
|
306
|
-
ret = data.readText(this.length, textEncoding);
|
|
307
|
-
const encoding = options && options.encoding ? options.encoding : 'UTF8';
|
|
308
|
-
const width = getFirebirdCharsetWidth(encoding);
|
|
309
|
-
const charLength = Math.floor(this.length / width);
|
|
372
|
+
if (this.subType > 1 || this.subType === 0) {
|
|
373
|
+
const state = resolveTextState(options);
|
|
374
|
+
ret = state.codec
|
|
375
|
+
? state.codec.decode(data.readBuffer(this.length) || EMPTY_BUFFER)
|
|
376
|
+
: data.readText(this.length, state.enc);
|
|
377
|
+
const charLength = Math.floor(this.length / state.width);
|
|
310
378
|
if (ret.length > charLength) {
|
|
311
379
|
ret = ret.substring(0, charLength);
|
|
312
380
|
}
|
|
@@ -333,14 +401,11 @@ exports.SQLVarNull = SQLVarNull;
|
|
|
333
401
|
class SQLVarString extends SQLVarBase {
|
|
334
402
|
decode(data, lowerV13, options) {
|
|
335
403
|
let ret;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
else if (this.subType === 0) {
|
|
342
|
-
// without charset definition
|
|
343
|
-
ret = data.readString(textEncoding);
|
|
404
|
+
if (this.subType > 1 || this.subType === 0) {
|
|
405
|
+
const state = resolveTextState(options);
|
|
406
|
+
ret = state.codec
|
|
407
|
+
? state.codec.decode(data.readArray() || EMPTY_BUFFER)
|
|
408
|
+
: data.readString(state.enc);
|
|
344
409
|
}
|
|
345
410
|
else {
|
|
346
411
|
ret = data.readBuffer();
|
|
@@ -875,10 +940,19 @@ class SQLParamDate {
|
|
|
875
940
|
exports.SQLParamDate = SQLParamDate;
|
|
876
941
|
//------------------------------------------------------
|
|
877
942
|
class SQLParamBool {
|
|
878
|
-
constructor(value) {
|
|
943
|
+
constructor(value, asBoolean = false) {
|
|
879
944
|
this.value = value;
|
|
945
|
+
this.asBoolean = asBoolean;
|
|
880
946
|
}
|
|
881
947
|
encode(data) {
|
|
948
|
+
if (this.asBoolean) {
|
|
949
|
+
// xdr_datum sends booleans as 1 opaque value byte + 3 pad bytes
|
|
950
|
+
// (NOT a big-endian int: the value byte comes FIRST — addInt(1)
|
|
951
|
+
// would decode server-side as false). Matches the batch encoder.
|
|
952
|
+
data.addBuffer(Buffer.from([this.value ? 1 : 0]));
|
|
953
|
+
data.addAlignment(1);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
882
956
|
if (this.value != null) {
|
|
883
957
|
data.addInt(this.value ? 1 : 0);
|
|
884
958
|
}
|
|
@@ -888,6 +962,10 @@ class SQLParamBool {
|
|
|
888
962
|
}
|
|
889
963
|
}
|
|
890
964
|
calcBlr(blr) {
|
|
965
|
+
if (this.asBoolean) {
|
|
966
|
+
blr.addByte(const_1.default.blr_bool);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
891
969
|
blr.addByte(const_1.default.blr_short);
|
|
892
970
|
blr.addShort(0);
|
|
893
971
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-firebird",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"firebird",
|
|
@@ -23,6 +23,24 @@
|
|
|
23
23
|
],
|
|
24
24
|
"main": "./lib/index.js",
|
|
25
25
|
"types": "./lib/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./lib/index.d.ts",
|
|
29
|
+
"import": "./lib/index.js",
|
|
30
|
+
"require": "./lib/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./lib/firebird.msg": "./lib/firebird.msg",
|
|
33
|
+
"./lib/firebird.msg.json": "./lib/firebird.msg.json",
|
|
34
|
+
"./lib/*.js": {
|
|
35
|
+
"types": "./lib/*.d.ts",
|
|
36
|
+
"default": "./lib/*.js"
|
|
37
|
+
},
|
|
38
|
+
"./lib/*": {
|
|
39
|
+
"types": "./lib/*.d.ts",
|
|
40
|
+
"default": "./lib/*.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
26
44
|
"files": [
|
|
27
45
|
"lib",
|
|
28
46
|
"src"
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// They now live in the TypeScript source tree and are compiled into the
|
|
6
6
|
// published declaration files.
|
|
7
7
|
|
|
8
|
-
import type { Readable } from 'stream';
|
|
8
|
+
import type { Readable, Writable } from 'stream';
|
|
9
9
|
import type { SqlTag } from './sql-template';
|
|
10
10
|
|
|
11
11
|
export type { SqlTag, SqlQuery, SqlIdentifier, CompiledQuery } from './sql-template';
|
|
@@ -211,6 +211,22 @@ export interface QueryResult<T = any> {
|
|
|
211
211
|
warnings: ServerWarning[];
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/** Options for batchStream: the executeBatch options plus stream tuning. */
|
|
215
|
+
export type BatchStreamOptions = BatchOptions & {
|
|
216
|
+
/** Rows buffered per executeBatch flush (default 1000). */
|
|
217
|
+
flushRows?: number;
|
|
218
|
+
/** Writable highWaterMark in rows (default: flushRows). */
|
|
219
|
+
highWaterMark?: number;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/** The Writable returned by batchStream, with totals valid after 'finish'. */
|
|
223
|
+
export interface BatchStream extends Writable {
|
|
224
|
+
/** Records the server processed so far. */
|
|
225
|
+
recordCount: number;
|
|
226
|
+
/** Sum of per-record update counts so far. */
|
|
227
|
+
affectedRows: number;
|
|
228
|
+
}
|
|
229
|
+
|
|
214
230
|
export type QueryStreamOptions = QueryOptions & {
|
|
215
231
|
/**
|
|
216
232
|
* Rows buffered internally before fetching pauses (object-mode
|
|
@@ -232,8 +248,8 @@ export interface Database {
|
|
|
232
248
|
detach(callback?: SimpleCallback): Database;
|
|
233
249
|
transaction(options: TransactionOptions|Isolation|TransactionCallback, callback?: TransactionCallback): Database;
|
|
234
250
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
|
|
235
|
-
query(query: string, params: QueryParams, callback:
|
|
236
|
-
execute(query: string, params: QueryParams, callback:
|
|
251
|
+
query<T = any>(query: string, params: QueryParams, callback: (err: any, result: T[], meta?: any[], isSelect?: boolean) => void, options?: QueryOptions): Database;
|
|
252
|
+
execute<T = any>(query: string, params: QueryParams, callback: (err: any, result: T[], meta?: any[], isSelect?: boolean) => void, options?: QueryOptions): Database;
|
|
237
253
|
/** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
|
|
238
254
|
executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
|
|
239
255
|
sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
|
|
@@ -244,6 +260,13 @@ export interface Database {
|
|
|
244
260
|
* fetch and releases the statement.
|
|
245
261
|
*/
|
|
246
262
|
queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): Readable;
|
|
263
|
+
/**
|
|
264
|
+
* Bulk-insert Writable (COPY FROM analogue, Firebird 4.0+): write
|
|
265
|
+
* parameter-array rows; they are flushed in chunks through the batch
|
|
266
|
+
* API. Runs its own transaction — committed on finish, rolled back on
|
|
267
|
+
* error/destroy. BLOB columns accept Buffers/strings.
|
|
268
|
+
*/
|
|
269
|
+
batchStream(query: string, options?: BatchStreamOptions): BatchStream;
|
|
247
270
|
drop(callback: SimpleCallback): void;
|
|
248
271
|
escape(value: any): string;
|
|
249
272
|
attachEvent(callback: any): this;
|
|
@@ -290,8 +313,8 @@ export interface Transaction {
|
|
|
290
313
|
*/
|
|
291
314
|
savepoint<T>(work: (transaction: Transaction) => Promise<T> | T): Promise<T>;
|
|
292
315
|
newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
|
|
293
|
-
query(query: string, params: QueryParams, callback:
|
|
294
|
-
execute(query: string, params: QueryParams, callback:
|
|
316
|
+
query<T = any>(query: string, params: QueryParams, callback: (err: any, result: T[], meta?: any[], isSelect?: boolean) => void, options?: QueryOptions): void;
|
|
317
|
+
execute<T = any>(query: string, params: QueryParams, callback: (err: any, result: T[], meta?: any[], isSelect?: boolean) => void, options?: QueryOptions): void;
|
|
295
318
|
/** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
|
|
296
319
|
executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
|
|
297
320
|
sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
|
|
@@ -301,6 +324,11 @@ export interface Transaction {
|
|
|
301
324
|
* transaction is NOT committed when the stream ends.
|
|
302
325
|
*/
|
|
303
326
|
queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): Readable;
|
|
327
|
+
/**
|
|
328
|
+
* Bulk-insert Writable inside this transaction (see
|
|
329
|
+
* Database.batchStream); commit/rollback stays with the caller.
|
|
330
|
+
*/
|
|
331
|
+
batchStream(query: string, options?: BatchStreamOptions): BatchStream;
|
|
304
332
|
commit(callback?: SimpleCallback): void;
|
|
305
333
|
commitRetaining(callback?: SimpleCallback): void;
|
|
306
334
|
rollback(callback?: SimpleCallback): void;
|
|
@@ -416,6 +444,13 @@ export interface Options {
|
|
|
416
444
|
* per-query `namedPlaceholders: false` override.
|
|
417
445
|
*/
|
|
418
446
|
namedPlaceholders?: boolean;
|
|
447
|
+
/**
|
|
448
|
+
* Default character set of a NEWLY CREATED database (create /
|
|
449
|
+
* attachOrCreate only). Falls back to the connection `encoding`, then
|
|
450
|
+
* UTF8 — pass e.g. `defaultCharset: 'UTF8'` to keep a modern database
|
|
451
|
+
* default while connecting with a legacy codepage `encoding`.
|
|
452
|
+
*/
|
|
453
|
+
defaultCharset?: string;
|
|
419
454
|
/**
|
|
420
455
|
* Qualify object-row keys by source table (same option as mysql2), so
|
|
421
456
|
* JOINed columns with the same name stop overwriting each other:
|
package/src/utils.ts
CHANGED
|
@@ -108,6 +108,21 @@ export const parseDate = (str: string): Date => {
|
|
|
108
108
|
/**
|
|
109
109
|
* Get Error Message per gdscode
|
|
110
110
|
*/
|
|
111
|
+
/**
|
|
112
|
+
* Turn a failed executeBatch completion into the all-or-nothing error
|
|
113
|
+
* shape shared by database.executeBatch and batchStream: the first
|
|
114
|
+
* record's own error (or a synthesized summary), with the full
|
|
115
|
+
* completion attached as err.batchCompletion.
|
|
116
|
+
*/
|
|
117
|
+
export const batchResultToError = (result: { errors: { error: any }[]; errorRecordNumbers: number[] }): any => {
|
|
118
|
+
const first = result.errors.length ? result.errors[0] : null;
|
|
119
|
+
const err: any = first
|
|
120
|
+
? first.error
|
|
121
|
+
: new Error('Batch failed for record(s) ' + result.errorRecordNumbers.join(', '));
|
|
122
|
+
err.batchCompletion = result;
|
|
123
|
+
return err;
|
|
124
|
+
};
|
|
125
|
+
|
|
111
126
|
export const lookupMessages = (status: FbStatusItem[]): string => {
|
|
112
127
|
const messages = status.map((item) => {
|
|
113
128
|
let text = MessagesError[item.gdscode];
|
|
@@ -143,7 +158,10 @@ export const escape = function(value: any, protocolVersion?: number): string {
|
|
|
143
158
|
case 'number':
|
|
144
159
|
return value.toString();
|
|
145
160
|
case 'string':
|
|
146
|
-
|
|
161
|
+
// Firebird string literals have NO backslash escapes — only the
|
|
162
|
+
// quote is doubled. Doubling backslashes corrupted the data
|
|
163
|
+
// (issue #156: '\' arrived as '\\').
|
|
164
|
+
return "'" + value.replace(/'/g, "''") + "'";
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
if (value instanceof Date)
|