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/src/wire/transaction.ts
CHANGED
|
@@ -5,6 +5,7 @@ import Const from './const';
|
|
|
5
5
|
import { makeSqlTag, type SqlTag } from '../sql-template';
|
|
6
6
|
import { describeFields, parseRecordCounts } from './xsqlvar';
|
|
7
7
|
import makeQueryStream from './query-stream';
|
|
8
|
+
import makeBatchStream from './batch-stream';
|
|
8
9
|
import type Connection from './connection';
|
|
9
10
|
import type Database from './database';
|
|
10
11
|
import type Statement from './statement';
|
|
@@ -287,7 +288,17 @@ class Transaction {
|
|
|
287
288
|
|
|
288
289
|
case Const.isc_info_sql_stmt_exec_procedure:
|
|
289
290
|
if (ret && ret.data && ret.data.length > 0) {
|
|
290
|
-
|
|
291
|
+
// singleton op_execute2 rows never pass through
|
|
292
|
+
// fetchAll, so their blobAsText fetches must be
|
|
293
|
+
// resolved here (issue #305: EXECUTE PROCEDURE
|
|
294
|
+
// returned text blobs as unresolved functions)
|
|
295
|
+
self.connection.resolveTextBlobs(self, ret, function(blobErr?: any) {
|
|
296
|
+
if (blobErr) {
|
|
297
|
+
dropError(blobErr);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
deliver(ret.data[0], true);
|
|
301
|
+
});
|
|
291
302
|
break;
|
|
292
303
|
} else if (statement.output.length) {
|
|
293
304
|
statement.fetch(self, 1, function(err: any, fret: any) {
|
|
@@ -296,7 +307,13 @@ class Transaction {
|
|
|
296
307
|
return;
|
|
297
308
|
}
|
|
298
309
|
|
|
299
|
-
|
|
310
|
+
self.connection.resolveTextBlobs(self, fret, function(blobErr?: any) {
|
|
311
|
+
if (blobErr) {
|
|
312
|
+
dropError(blobErr);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
deliver(fret.data[0], false);
|
|
316
|
+
});
|
|
300
317
|
});
|
|
301
318
|
|
|
302
319
|
break;
|
|
@@ -386,6 +403,15 @@ class Transaction {
|
|
|
386
403
|
return makeQueryStream(this, query, params, options);
|
|
387
404
|
}
|
|
388
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Bulk-insert Writable running inside this transaction (see
|
|
408
|
+
* Database.batchStream). The transaction is NOT committed or rolled
|
|
409
|
+
* back by the stream — settle it yourself after 'finish'/'error'.
|
|
410
|
+
*/
|
|
411
|
+
batchStream(query: string, options?: any) {
|
|
412
|
+
return makeBatchStream(this, query, options, false);
|
|
413
|
+
}
|
|
414
|
+
|
|
389
415
|
query(query: string, params?: QueryParams | Callback, callback?: any, options: InternalQueryOptions = {}): void {
|
|
390
416
|
if (params instanceof Function) {
|
|
391
417
|
callback = params;
|
package/src/wire/xsqlvar.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Const from './const';
|
|
2
2
|
import { BlrReader } from './serialize';
|
|
3
|
+
import { getCodec } from './codepages';
|
|
4
|
+
import type { TextCodec } from './codepages';
|
|
3
5
|
import type { XdrReader, XdrWriter, BlrWriter } from './serialize';
|
|
4
6
|
import type { RecordCounts } from '../types';
|
|
5
7
|
|
|
@@ -16,6 +18,8 @@ const
|
|
|
16
18
|
TimeCoeff = 86400000,
|
|
17
19
|
MsPerMinute = 60000;
|
|
18
20
|
|
|
21
|
+
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
22
|
+
|
|
19
23
|
/**
|
|
20
24
|
* Maps Firebird character-set names (upper-case) to the Node.js Buffer
|
|
21
25
|
* encoding string used by Buffer.toString() / Buffer.from().
|
|
@@ -41,11 +45,19 @@ const FirebirdToNodeEncoding: Readonly<Record<string, string>> = Object.freeze({
|
|
|
41
45
|
const FirebirdCharsetWidths: Record<string, number> = {
|
|
42
46
|
'UTF8': 4,
|
|
43
47
|
'UNICODE_FSS': 3,
|
|
44
|
-
'SJIS'
|
|
45
|
-
|
|
48
|
+
// real Firebird names — the bare 'SJIS'/'EUCJ' keys never matched a
|
|
49
|
+
// valid encoding option and silently resolved to width 1
|
|
50
|
+
'SJIS_0208': 2,
|
|
51
|
+
'EUCJ_0208': 2,
|
|
52
|
+
'KSC_5601': 2,
|
|
53
|
+
'BIG_5': 2,
|
|
54
|
+
'GB_2312': 2,
|
|
55
|
+
'GBK': 2,
|
|
56
|
+
'CP943C': 2,
|
|
57
|
+
'GB18030': 4,
|
|
46
58
|
};
|
|
47
59
|
|
|
48
|
-
function getFirebirdCharsetWidth(charset?: string): number {
|
|
60
|
+
export function getFirebirdCharsetWidth(charset?: string): number {
|
|
49
61
|
if (!charset) return 4;
|
|
50
62
|
const upper = charset.toUpperCase();
|
|
51
63
|
return FirebirdCharsetWidths[upper] || 1;
|
|
@@ -58,13 +70,85 @@ function getFirebirdCharsetWidth(charset?: string): number {
|
|
|
58
70
|
* @param {object|null} options Connection options object (may be falsy).
|
|
59
71
|
* @returns {string} A Node.js-compatible encoding string.
|
|
60
72
|
*/
|
|
61
|
-
function resolveTextEncoding(options?: any): BufferEncoding {
|
|
73
|
+
export function resolveTextEncoding(options?: any): BufferEncoding {
|
|
62
74
|
const encoding = (options && options.encoding)
|
|
63
75
|
? options.encoding.toUpperCase()
|
|
64
76
|
: Const.DEFAULT_ENCODING;
|
|
65
77
|
return (FirebirdToNodeEncoding[encoding] || Const.DEFAULT_ENCODING.toLowerCase()) as BufferEncoding;
|
|
66
78
|
}
|
|
67
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Codec for the CONNECTION charset when it is a codepage Node cannot
|
|
82
|
+
* handle natively (WIN1251, ISO8859_7, KOI8R, …); null on the native
|
|
83
|
+
* path (UTF8/latin1/ascii) and for unknown charsets. With a codec
|
|
84
|
+
* connection charset the server transliterates all text to that
|
|
85
|
+
* codepage, so every text column, parameter, SQL string and text blob
|
|
86
|
+
* goes through the codec (issues #319/#301).
|
|
87
|
+
*/
|
|
88
|
+
export function resolveTextCodec(options?: any): TextCodec | null {
|
|
89
|
+
return resolveTextState(options).codec;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface TextState {
|
|
93
|
+
key: string | undefined;
|
|
94
|
+
codec: TextCodec | null;
|
|
95
|
+
enc: BufferEncoding;
|
|
96
|
+
width: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Per-connection text handling, resolved once and memoized on the
|
|
101
|
+
* long-lived options object: the decode loop calls this per CELL, and
|
|
102
|
+
* recomputing uppercased names + map lookups a million times per large
|
|
103
|
+
* fetch is pure waste. Invalidated if options.encoding ever changes.
|
|
104
|
+
*/
|
|
105
|
+
export function resolveTextState(options?: any): TextState {
|
|
106
|
+
const key = options && options.encoding;
|
|
107
|
+
if (options && options.__textState && options.__textState.key === key) {
|
|
108
|
+
return options.__textState;
|
|
109
|
+
}
|
|
110
|
+
const encoding = (key || Const.DEFAULT_ENCODING).toUpperCase();
|
|
111
|
+
const state: TextState = {
|
|
112
|
+
key,
|
|
113
|
+
codec: FirebirdToNodeEncoding[encoding] ? null : getCodec(encoding),
|
|
114
|
+
enc: (FirebirdToNodeEncoding[encoding] || Const.DEFAULT_ENCODING.toLowerCase()) as BufferEncoding,
|
|
115
|
+
width: getFirebirdCharsetWidth(encoding),
|
|
116
|
+
};
|
|
117
|
+
if (options) {
|
|
118
|
+
options.__textState = state;
|
|
119
|
+
}
|
|
120
|
+
return state;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Encode text in the CONNECTION charset — the byte form the server
|
|
125
|
+
* expects for parameters, SQL statement text and text-blob content.
|
|
126
|
+
*/
|
|
127
|
+
export function encodeConnectionText(options: any, value: string): Buffer {
|
|
128
|
+
const state = resolveTextState(options);
|
|
129
|
+
if (state.codec) {
|
|
130
|
+
return state.codec.encode(value);
|
|
131
|
+
}
|
|
132
|
+
if (state.enc === 'ascii') {
|
|
133
|
+
// Node's 'ascii' encoding masks high bits (0xE4 → 'd') — replace
|
|
134
|
+
// non-ASCII with '?' instead, matching the codec policy
|
|
135
|
+
value = value.replace(/[^\x00-\x7F]/g, '?');
|
|
136
|
+
}
|
|
137
|
+
return Buffer.from(value, state.enc);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Decode connection-charset bytes to text (the read counterpart of
|
|
142
|
+
* encodeConnectionText — used for text blobs).
|
|
143
|
+
*/
|
|
144
|
+
export function decodeConnectionText(options: any, buffer: Buffer): string {
|
|
145
|
+
const codec = resolveTextCodec(options);
|
|
146
|
+
if (codec) {
|
|
147
|
+
return codec.decode(buffer);
|
|
148
|
+
}
|
|
149
|
+
return buffer.toString(resolveTextEncoding(options));
|
|
150
|
+
}
|
|
151
|
+
|
|
68
152
|
//------------------------------------------------------
|
|
69
153
|
|
|
70
154
|
/**
|
|
@@ -88,6 +172,9 @@ export abstract class SQLVarBase {
|
|
|
88
172
|
owner?: string;
|
|
89
173
|
charSetId?: number;
|
|
90
174
|
collationId?: number;
|
|
175
|
+
/** Original declared byte length when scaleOutputLengths widened
|
|
176
|
+
* `length` for the fetch capacity check (issue #422). */
|
|
177
|
+
nativeLength?: number;
|
|
91
178
|
|
|
92
179
|
abstract decode(data: XdrReader, lowerV13: boolean, options?: any): any;
|
|
93
180
|
abstract calcBlr(blr: BlrWriter): void;
|
|
@@ -274,7 +361,9 @@ export function describeField(meta: Partial<SQLVarBase>) {
|
|
|
274
361
|
typeName: SQL_TYPE_NAMES[meta.type!] || 'UNKNOWN',
|
|
275
362
|
subType: meta.subType,
|
|
276
363
|
scale: meta.scale,
|
|
277
|
-
|
|
364
|
+
// report the column's true declared length, not the widened fetch
|
|
365
|
+
// buffer (see scaleOutputLengths)
|
|
366
|
+
length: meta.nativeLength !== undefined ? meta.nativeLength : meta.length,
|
|
278
367
|
nullable: meta.nullable,
|
|
279
368
|
field: meta.field,
|
|
280
369
|
relation: meta.relation,
|
|
@@ -337,22 +426,12 @@ export function parseRecordCounts(buffer: Buffer | undefined): RecordCounts {
|
|
|
337
426
|
export class SQLVarText extends SQLVarBase {
|
|
338
427
|
decode(data: XdrReader, lowerV13: boolean, options?: any) {
|
|
339
428
|
let ret;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
const charLength = Math.floor(this.length / width);
|
|
347
|
-
if (ret.length > charLength) {
|
|
348
|
-
ret = ret.substring(0, charLength);
|
|
349
|
-
}
|
|
350
|
-
} else if (this.subType === 0) {
|
|
351
|
-
// without charset definition
|
|
352
|
-
ret = data.readText(this.length, textEncoding);
|
|
353
|
-
const encoding = options && options.encoding ? options.encoding : 'UTF8';
|
|
354
|
-
const width = getFirebirdCharsetWidth(encoding);
|
|
355
|
-
const charLength = Math.floor(this.length / width);
|
|
429
|
+
if (this.subType > 1 || this.subType === 0) {
|
|
430
|
+
const state = resolveTextState(options);
|
|
431
|
+
ret = state.codec
|
|
432
|
+
? state.codec.decode(data.readBuffer(this.length) || EMPTY_BUFFER)
|
|
433
|
+
: data.readText(this.length, state.enc);
|
|
434
|
+
const charLength = Math.floor(this.length / state.width);
|
|
356
435
|
if (ret.length > charLength) {
|
|
357
436
|
ret = ret.substring(0, charLength);
|
|
358
437
|
}
|
|
@@ -383,13 +462,11 @@ export class SQLVarNull extends SQLVarText {
|
|
|
383
462
|
export class SQLVarString extends SQLVarBase {
|
|
384
463
|
decode(data: XdrReader, lowerV13: boolean, options?: any) {
|
|
385
464
|
let ret;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
// without charset definition
|
|
392
|
-
ret = data.readString(textEncoding);
|
|
465
|
+
if (this.subType > 1 || this.subType === 0) {
|
|
466
|
+
const state = resolveTextState(options);
|
|
467
|
+
ret = state.codec
|
|
468
|
+
? state.codec.decode(data.readArray() || EMPTY_BUFFER)
|
|
469
|
+
: data.readString(state.enc);
|
|
393
470
|
} else {
|
|
394
471
|
ret = data.readBuffer();
|
|
395
472
|
}
|
|
@@ -1037,12 +1114,30 @@ export class SQLParamDate {
|
|
|
1037
1114
|
|
|
1038
1115
|
export class SQLParamBool {
|
|
1039
1116
|
value: any;
|
|
1040
|
-
|
|
1041
|
-
|
|
1117
|
+
/**
|
|
1118
|
+
* Encode as a real BOOLEAN (blr_bool + xdr opaque byte) instead of the
|
|
1119
|
+
* legacy blr_short 0/1. Set when the DESCRIBED parameter type is
|
|
1120
|
+
* SQL_BOOLEAN: Firebird refuses smallint→BOOLEAN conversion
|
|
1121
|
+
* ("conversion error from string", issue #122), and conversely BOOLEAN
|
|
1122
|
+
* does not convert to numbers — so smallint targets keep the legacy
|
|
1123
|
+
* form for compatibility.
|
|
1124
|
+
*/
|
|
1125
|
+
asBoolean: boolean;
|
|
1126
|
+
|
|
1127
|
+
constructor(value: any, asBoolean = false) {
|
|
1042
1128
|
this.value = value;
|
|
1129
|
+
this.asBoolean = asBoolean;
|
|
1043
1130
|
}
|
|
1044
1131
|
|
|
1045
1132
|
encode(data: XdrWriter): void {
|
|
1133
|
+
if (this.asBoolean) {
|
|
1134
|
+
// xdr_datum sends booleans as 1 opaque value byte + 3 pad bytes
|
|
1135
|
+
// (NOT a big-endian int: the value byte comes FIRST — addInt(1)
|
|
1136
|
+
// would decode server-side as false). Matches the batch encoder.
|
|
1137
|
+
data.addBuffer(Buffer.from([this.value ? 1 : 0]));
|
|
1138
|
+
data.addAlignment(1);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1046
1141
|
if (this.value != null) {
|
|
1047
1142
|
data.addInt(this.value ? 1 : 0);
|
|
1048
1143
|
} else {
|
|
@@ -1052,6 +1147,10 @@ export class SQLParamBool {
|
|
|
1052
1147
|
}
|
|
1053
1148
|
|
|
1054
1149
|
calcBlr(blr: BlrWriter): void {
|
|
1150
|
+
if (this.asBoolean) {
|
|
1151
|
+
blr.addByte(Const.blr_bool);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1055
1154
|
blr.addByte(Const.blr_short);
|
|
1056
1155
|
blr.addShort(0);
|
|
1057
1156
|
}
|