node-firebird 2.11.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 +287 -9
- package/lib/pool.d.ts +14 -1
- package/lib/pool.js +59 -16
- package/lib/sql-template.d.ts +81 -0
- package/lib/sql-template.js +162 -0
- package/lib/types.d.ts +152 -5
- package/lib/uri.js +53 -2
- 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 +44 -4
- package/lib/wire/connection.js +344 -80
- package/lib/wire/const.d.ts +5 -0
- package/lib/wire/const.js +12 -0
- package/lib/wire/database.d.ts +18 -0
- package/lib/wire/database.js +40 -13
- package/lib/wire/serialize.d.ts +2 -0
- package/lib/wire/serialize.js +14 -0
- package/lib/wire/socket.js +9 -3
- package/lib/wire/transaction.d.ts +33 -0
- package/lib/wire/transaction.js +156 -13
- package/lib/wire/xsqlvar.d.ts +118 -2
- package/lib/wire/xsqlvar.js +271 -34
- package/package.json +19 -1
- package/src/pool.ts +57 -14
- package/src/sql-template.ts +196 -0
- package/src/types.ts +153 -6
- package/src/uri.ts +54 -2
- 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 +374 -86
- package/src/wire/const.ts +13 -0
- package/src/wire/database.ts +46 -15
- package/src/wire/serialize.ts +16 -1
- package/src/wire/socket.ts +9 -3
- package/src/wire/transaction.ts +166 -19
- package/src/wire/xsqlvar.ts +298 -34
package/lib/wire/xsqlvar.d.ts
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
|
+
import type { TextCodec } from './codepages';
|
|
1
2
|
import type { XdrReader, XdrWriter, BlrWriter } from './serialize';
|
|
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;
|
|
2
45
|
/**
|
|
3
46
|
* Common shape of all SQLVar descriptor objects. The metadata properties
|
|
4
47
|
* are populated externally (in connection.ts) from the op_prepare_statement
|
|
@@ -18,6 +61,9 @@ export declare abstract class SQLVarBase {
|
|
|
18
61
|
owner?: string;
|
|
19
62
|
charSetId?: number;
|
|
20
63
|
collationId?: number;
|
|
64
|
+
/** Original declared byte length when scaleOutputLengths widened
|
|
65
|
+
* `length` for the fetch capacity check (issue #422). */
|
|
66
|
+
nativeLength?: number;
|
|
21
67
|
abstract decode(data: XdrReader, lowerV13: boolean, options?: any): any;
|
|
22
68
|
abstract calcBlr(blr: BlrWriter): void;
|
|
23
69
|
}
|
|
@@ -41,7 +87,23 @@ export interface ColumnKey {
|
|
|
41
87
|
* decoder and by fetchBlobSyncRow, which must agree on where each column
|
|
42
88
|
* landed in the row.
|
|
43
89
|
*/
|
|
44
|
-
export declare function computeColumnKeys(output: SQLVarBase[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined): ColumnKey[];
|
|
90
|
+
export declare function computeColumnKeys(output: SQLVarBase[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined, transform?: (key: string) => string): ColumnKey[];
|
|
91
|
+
/** transformKeys option value: the built-in 'camel', or a custom mapper. */
|
|
92
|
+
export type KeyTransform = 'camel' | ((key: string) => string);
|
|
93
|
+
/** FIRST_NAME → firstName (the transformKeys: 'camel' built-in). */
|
|
94
|
+
export declare function camelizeKey(key: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the effective transformKeys value (per-query wins over the
|
|
97
|
+
* connection option) into a callable mapper, or undefined when off.
|
|
98
|
+
* A custom mapper is guarded like the typeCast hook: a throw inside the
|
|
99
|
+
* row-decode loop would be mistaken for an incomplete packet and desync
|
|
100
|
+
* the response queue, so failures fall back to the untransformed key.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveKeyTransform(queryOptions: {
|
|
103
|
+
transformKeys?: KeyTransform;
|
|
104
|
+
} | undefined, connectionOptions: {
|
|
105
|
+
transformKeys?: KeyTransform;
|
|
106
|
+
} | undefined): ((key: string) => string) | undefined;
|
|
45
107
|
/**
|
|
46
108
|
* Resolve the effective nestTables value: the per-query option wins over
|
|
47
109
|
* the connection option. The decoder and fetchBlobSyncRow both use this —
|
|
@@ -60,6 +122,50 @@ export declare function resolveNestTables(queryOptions: {
|
|
|
60
122
|
* cell by ColumnKey must resolve it through here.
|
|
61
123
|
*/
|
|
62
124
|
export declare function nestCell(row: any, table: string | undefined): any;
|
|
125
|
+
/** Human-readable names for the SQL_* wire type codes. */
|
|
126
|
+
export declare const SQL_TYPE_NAMES: Record<number, string>;
|
|
127
|
+
/**
|
|
128
|
+
* Public column-metadata shape for one output descriptor: the vocabulary
|
|
129
|
+
* both the typeCast hook and withMeta `fields` deliver. Keep the two in
|
|
130
|
+
* lockstep by building both through here.
|
|
131
|
+
*/
|
|
132
|
+
export declare function describeField(meta: Partial<SQLVarBase>): {
|
|
133
|
+
type: number;
|
|
134
|
+
typeName: string;
|
|
135
|
+
subType: number | undefined;
|
|
136
|
+
scale: number | undefined;
|
|
137
|
+
length: number | undefined;
|
|
138
|
+
nullable: boolean | undefined;
|
|
139
|
+
field: string | undefined;
|
|
140
|
+
relation: string | undefined;
|
|
141
|
+
relationAlias: string | undefined;
|
|
142
|
+
relationSchema: string | undefined;
|
|
143
|
+
alias: string | undefined;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Map a statement's output descriptors to the column-metadata array
|
|
147
|
+
* delivered in withMeta results ({ rows, fields, ... }).
|
|
148
|
+
*/
|
|
149
|
+
export declare function describeFields(output: SQLVarBase[]): {
|
|
150
|
+
type: number;
|
|
151
|
+
typeName: string;
|
|
152
|
+
subType: number | undefined;
|
|
153
|
+
scale: number | undefined;
|
|
154
|
+
length: number | undefined;
|
|
155
|
+
nullable: boolean | undefined;
|
|
156
|
+
field: string | undefined;
|
|
157
|
+
relation: string | undefined;
|
|
158
|
+
relationAlias: string | undefined;
|
|
159
|
+
relationSchema: string | undefined;
|
|
160
|
+
alias: string | undefined;
|
|
161
|
+
}[];
|
|
162
|
+
/**
|
|
163
|
+
* Parse the op_info_sql response buffer of a Const.RECORDS_INFO request
|
|
164
|
+
* into per-verb row counts. The buffer holds an isc_info_sql_records
|
|
165
|
+
* cluster (2-byte total length, then nested isc_info_req_*_count items,
|
|
166
|
+
* each 2-byte length + little-endian integer) terminated by isc_info_end.
|
|
167
|
+
*/
|
|
168
|
+
export declare function parseRecordCounts(buffer: Buffer | undefined): RecordCounts;
|
|
63
169
|
export declare class SQLVarText extends SQLVarBase {
|
|
64
170
|
decode(data: XdrReader, lowerV13: boolean, options?: any): string | Buffer<ArrayBufferLike> | null | undefined;
|
|
65
171
|
calcBlr(blr: BlrWriter): void;
|
|
@@ -217,7 +323,17 @@ export declare class SQLParamDate {
|
|
|
217
323
|
}
|
|
218
324
|
export declare class SQLParamBool {
|
|
219
325
|
value: any;
|
|
220
|
-
|
|
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);
|
|
221
336
|
encode(data: XdrWriter): void;
|
|
222
337
|
calcBlr(blr: BlrWriter): void;
|
|
223
338
|
}
|
|
339
|
+
export {};
|
package/lib/wire/xsqlvar.js
CHANGED
|
@@ -3,12 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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;
|
|
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;
|
|
14
|
+
exports.camelizeKey = camelizeKey;
|
|
15
|
+
exports.resolveKeyTransform = resolveKeyTransform;
|
|
8
16
|
exports.resolveNestTables = resolveNestTables;
|
|
9
17
|
exports.nestCell = nestCell;
|
|
18
|
+
exports.describeField = describeField;
|
|
19
|
+
exports.describeFields = describeFields;
|
|
20
|
+
exports.parseRecordCounts = parseRecordCounts;
|
|
10
21
|
exports.encodeDateTimeParts = encodeDateTimeParts;
|
|
11
22
|
const const_1 = __importDefault(require("./const"));
|
|
23
|
+
const serialize_1 = require("./serialize");
|
|
24
|
+
const codepages_1 = require("./codepages");
|
|
12
25
|
/***************************************
|
|
13
26
|
*
|
|
14
27
|
* SQLVar
|
|
@@ -16,6 +29,7 @@ const const_1 = __importDefault(require("./const"));
|
|
|
16
29
|
***************************************/
|
|
17
30
|
const ScaleDivisor = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000];
|
|
18
31
|
const DateOffset = 40587, TimeCoeff = 86400000, MsPerMinute = 60000;
|
|
32
|
+
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
19
33
|
/**
|
|
20
34
|
* Maps Firebird character-set names (upper-case) to the Node.js Buffer
|
|
21
35
|
* encoding string used by Buffer.toString() / Buffer.from().
|
|
@@ -40,8 +54,16 @@ const FirebirdToNodeEncoding = Object.freeze({
|
|
|
40
54
|
const FirebirdCharsetWidths = {
|
|
41
55
|
'UTF8': 4,
|
|
42
56
|
'UNICODE_FSS': 3,
|
|
43
|
-
'SJIS'
|
|
44
|
-
|
|
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,
|
|
45
67
|
};
|
|
46
68
|
function getFirebirdCharsetWidth(charset) {
|
|
47
69
|
if (!charset)
|
|
@@ -62,6 +84,67 @@ function resolveTextEncoding(options) {
|
|
|
62
84
|
: const_1.default.DEFAULT_ENCODING;
|
|
63
85
|
return (FirebirdToNodeEncoding[encoding] || const_1.default.DEFAULT_ENCODING.toLowerCase());
|
|
64
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
|
+
}
|
|
65
148
|
//------------------------------------------------------
|
|
66
149
|
/**
|
|
67
150
|
* Common shape of all SQLVar descriptor objects. The metadata properties
|
|
@@ -84,12 +167,15 @@ exports.SQLVarBase = SQLVarBase;
|
|
|
84
167
|
* decoder and by fetchBlobSyncRow, which must agree on where each column
|
|
85
168
|
* landed in the row.
|
|
86
169
|
*/
|
|
87
|
-
function computeColumnKeys(output, nestTables, lowercaseKeys) {
|
|
170
|
+
function computeColumnKeys(output, nestTables, lowercaseKeys, transform) {
|
|
88
171
|
return output.map((column) => {
|
|
89
172
|
let key = column.alias || '';
|
|
90
173
|
if (lowercaseKeys) {
|
|
91
174
|
key = key.toLowerCase();
|
|
92
175
|
}
|
|
176
|
+
if (transform) {
|
|
177
|
+
key = transform(key);
|
|
178
|
+
}
|
|
93
179
|
if (nestTables !== true && typeof nestTables !== 'string') {
|
|
94
180
|
return { key };
|
|
95
181
|
}
|
|
@@ -97,12 +183,63 @@ function computeColumnKeys(output, nestTables, lowercaseKeys) {
|
|
|
97
183
|
if (lowercaseKeys) {
|
|
98
184
|
table = table.toLowerCase();
|
|
99
185
|
}
|
|
186
|
+
if (transform) {
|
|
187
|
+
table = transform(table);
|
|
188
|
+
}
|
|
100
189
|
if (nestTables === true) {
|
|
101
190
|
return { table, key };
|
|
102
191
|
}
|
|
103
192
|
return { key: table + nestTables + key };
|
|
104
193
|
});
|
|
105
194
|
}
|
|
195
|
+
/** FIRST_NAME → firstName (the transformKeys: 'camel' built-in). */
|
|
196
|
+
function camelizeKey(key) {
|
|
197
|
+
const parts = String(key).toLowerCase().split('_');
|
|
198
|
+
let out = parts[0] || '';
|
|
199
|
+
for (let i = 1; i < parts.length; i++) {
|
|
200
|
+
const part = parts[i];
|
|
201
|
+
if (part) {
|
|
202
|
+
out += part.charAt(0).toUpperCase() + part.slice(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Resolve the effective transformKeys value (per-query wins over the
|
|
209
|
+
* connection option) into a callable mapper, or undefined when off.
|
|
210
|
+
* A custom mapper is guarded like the typeCast hook: a throw inside the
|
|
211
|
+
* row-decode loop would be mistaken for an incomplete packet and desync
|
|
212
|
+
* the response queue, so failures fall back to the untransformed key.
|
|
213
|
+
*/
|
|
214
|
+
function resolveKeyTransform(queryOptions, connectionOptions) {
|
|
215
|
+
const value = resolveQueryOption('transformKeys', queryOptions, connectionOptions);
|
|
216
|
+
if (value === 'camel') {
|
|
217
|
+
return camelizeKey;
|
|
218
|
+
}
|
|
219
|
+
if (typeof value !== 'function') {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return (key) => {
|
|
223
|
+
try {
|
|
224
|
+
return String(value(key));
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
console.warn('[node-firebird] transformKeys mapper threw for key "%s": %s — using the untransformed key', key, err && err.message);
|
|
228
|
+
return key;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Shared precedence rule for per-query-overridable connection options:
|
|
234
|
+
* the per-query value wins whenever it is present (even if falsy), the
|
|
235
|
+
* connection option applies otherwise.
|
|
236
|
+
*/
|
|
237
|
+
function resolveQueryOption(name, queryOptions, connectionOptions) {
|
|
238
|
+
if (queryOptions && queryOptions[name] !== undefined) {
|
|
239
|
+
return queryOptions[name];
|
|
240
|
+
}
|
|
241
|
+
return connectionOptions ? connectionOptions[name] : undefined;
|
|
242
|
+
}
|
|
106
243
|
/**
|
|
107
244
|
* Resolve the effective nestTables value: the per-query option wins over
|
|
108
245
|
* the connection option. The decoder and fetchBlobSyncRow both use this —
|
|
@@ -110,10 +247,7 @@ function computeColumnKeys(output, nestTables, lowercaseKeys) {
|
|
|
110
247
|
* up in the wrong place.
|
|
111
248
|
*/
|
|
112
249
|
function resolveNestTables(queryOptions, connectionOptions) {
|
|
113
|
-
|
|
114
|
-
return queryOptions.nestTables;
|
|
115
|
-
}
|
|
116
|
-
return connectionOptions && connectionOptions.nestTables;
|
|
250
|
+
return resolveQueryOption('nestTables', queryOptions, connectionOptions);
|
|
117
251
|
}
|
|
118
252
|
/**
|
|
119
253
|
* The object a column's value lives in: the row itself, or — when the
|
|
@@ -128,26 +262,119 @@ function nestCell(row, table) {
|
|
|
128
262
|
return row[table] || (row[table] = {});
|
|
129
263
|
}
|
|
130
264
|
//------------------------------------------------------
|
|
265
|
+
/** Human-readable names for the SQL_* wire type codes. */
|
|
266
|
+
exports.SQL_TYPE_NAMES = {
|
|
267
|
+
[const_1.default.SQL_TEXT]: 'TEXT',
|
|
268
|
+
[const_1.default.SQL_VARYING]: 'VARYING',
|
|
269
|
+
[const_1.default.SQL_SHORT]: 'SHORT',
|
|
270
|
+
[const_1.default.SQL_LONG]: 'LONG',
|
|
271
|
+
[const_1.default.SQL_FLOAT]: 'FLOAT',
|
|
272
|
+
[const_1.default.SQL_DOUBLE]: 'DOUBLE',
|
|
273
|
+
[const_1.default.SQL_D_FLOAT]: 'D_FLOAT',
|
|
274
|
+
[const_1.default.SQL_TIMESTAMP]: 'TIMESTAMP',
|
|
275
|
+
[const_1.default.SQL_BLOB]: 'BLOB',
|
|
276
|
+
[const_1.default.SQL_ARRAY]: 'ARRAY',
|
|
277
|
+
[const_1.default.SQL_QUAD]: 'QUAD',
|
|
278
|
+
[const_1.default.SQL_TYPE_TIME]: 'TIME',
|
|
279
|
+
[const_1.default.SQL_TYPE_DATE]: 'DATE',
|
|
280
|
+
[const_1.default.SQL_INT64]: 'INT64',
|
|
281
|
+
[const_1.default.SQL_INT128]: 'INT128',
|
|
282
|
+
[const_1.default.SQL_TIMESTAMP_TZ]: 'TIMESTAMP_TZ',
|
|
283
|
+
[const_1.default.SQL_TIMESTAMP_TZ_EX]: 'TIMESTAMP_TZ_EX',
|
|
284
|
+
[const_1.default.SQL_TIME_TZ]: 'TIME_TZ',
|
|
285
|
+
[const_1.default.SQL_TIME_TZ_EX]: 'TIME_TZ_EX',
|
|
286
|
+
[const_1.default.SQL_DEC16]: 'DEC16',
|
|
287
|
+
[const_1.default.SQL_DEC34]: 'DEC34',
|
|
288
|
+
[const_1.default.SQL_BOOLEAN]: 'BOOLEAN',
|
|
289
|
+
[const_1.default.SQL_NULL]: 'NULL',
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* Public column-metadata shape for one output descriptor: the vocabulary
|
|
293
|
+
* both the typeCast hook and withMeta `fields` deliver. Keep the two in
|
|
294
|
+
* lockstep by building both through here.
|
|
295
|
+
*/
|
|
296
|
+
function describeField(meta) {
|
|
297
|
+
return {
|
|
298
|
+
type: meta.type,
|
|
299
|
+
typeName: exports.SQL_TYPE_NAMES[meta.type] || 'UNKNOWN',
|
|
300
|
+
subType: meta.subType,
|
|
301
|
+
scale: meta.scale,
|
|
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,
|
|
305
|
+
nullable: meta.nullable,
|
|
306
|
+
field: meta.field,
|
|
307
|
+
relation: meta.relation,
|
|
308
|
+
relationAlias: meta.relationAlias,
|
|
309
|
+
relationSchema: meta.relationSchema,
|
|
310
|
+
alias: meta.alias,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Map a statement's output descriptors to the column-metadata array
|
|
315
|
+
* delivered in withMeta results ({ rows, fields, ... }).
|
|
316
|
+
*/
|
|
317
|
+
function describeFields(output) {
|
|
318
|
+
return (output || []).map(describeField);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Parse the op_info_sql response buffer of a Const.RECORDS_INFO request
|
|
322
|
+
* into per-verb row counts. The buffer holds an isc_info_sql_records
|
|
323
|
+
* cluster (2-byte total length, then nested isc_info_req_*_count items,
|
|
324
|
+
* each 2-byte length + little-endian integer) terminated by isc_info_end.
|
|
325
|
+
*/
|
|
326
|
+
function parseRecordCounts(buffer) {
|
|
327
|
+
const counts = { selectCount: 0, insertCount: 0, updateCount: 0, deleteCount: 0 };
|
|
328
|
+
if (!buffer || !buffer.length) {
|
|
329
|
+
return counts;
|
|
330
|
+
}
|
|
331
|
+
// this runs inside a response callback — a malformed/truncated buffer
|
|
332
|
+
// must yield partial counts, never a throw
|
|
333
|
+
try {
|
|
334
|
+
const br = new serialize_1.BlrReader(buffer);
|
|
335
|
+
while (br.pos < br.buffer.length) {
|
|
336
|
+
const item = br.readByteCode();
|
|
337
|
+
if (item === const_1.default.isc_info_end || item === const_1.default.isc_info_truncated) {
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
if (item === const_1.default.isc_info_sql_records) {
|
|
341
|
+
br.pos += 2; // skip the cluster's total length; nested items follow
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
switch (item) {
|
|
345
|
+
case const_1.default.isc_info_req_select_count:
|
|
346
|
+
counts.selectCount = br.readInt() || 0;
|
|
347
|
+
break;
|
|
348
|
+
case const_1.default.isc_info_req_insert_count:
|
|
349
|
+
counts.insertCount = br.readInt() || 0;
|
|
350
|
+
break;
|
|
351
|
+
case const_1.default.isc_info_req_update_count:
|
|
352
|
+
counts.updateCount = br.readInt() || 0;
|
|
353
|
+
break;
|
|
354
|
+
case const_1.default.isc_info_req_delete_count:
|
|
355
|
+
counts.deleteCount = br.readInt() || 0;
|
|
356
|
+
break;
|
|
357
|
+
default:
|
|
358
|
+
// unknown item: its 2-byte length prefix tells us how far to skip
|
|
359
|
+
br.pos += 2 + br.buffer.readUInt16LE(br.pos);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
// fall through with whatever was parsed so far
|
|
365
|
+
}
|
|
366
|
+
return counts;
|
|
367
|
+
}
|
|
368
|
+
//------------------------------------------------------
|
|
131
369
|
class SQLVarText extends SQLVarBase {
|
|
132
370
|
decode(data, lowerV13, options) {
|
|
133
371
|
let ret;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
const charLength = Math.floor(this.length / width);
|
|
141
|
-
if (ret.length > charLength) {
|
|
142
|
-
ret = ret.substring(0, charLength);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else if (this.subType === 0) {
|
|
146
|
-
// without charset definition
|
|
147
|
-
ret = data.readText(this.length, textEncoding);
|
|
148
|
-
const encoding = options && options.encoding ? options.encoding : 'UTF8';
|
|
149
|
-
const width = getFirebirdCharsetWidth(encoding);
|
|
150
|
-
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);
|
|
151
378
|
if (ret.length > charLength) {
|
|
152
379
|
ret = ret.substring(0, charLength);
|
|
153
380
|
}
|
|
@@ -174,14 +401,11 @@ exports.SQLVarNull = SQLVarNull;
|
|
|
174
401
|
class SQLVarString extends SQLVarBase {
|
|
175
402
|
decode(data, lowerV13, options) {
|
|
176
403
|
let ret;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
else if (this.subType === 0) {
|
|
183
|
-
// without charset definition
|
|
184
|
-
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);
|
|
185
409
|
}
|
|
186
410
|
else {
|
|
187
411
|
ret = data.readBuffer();
|
|
@@ -716,10 +940,19 @@ class SQLParamDate {
|
|
|
716
940
|
exports.SQLParamDate = SQLParamDate;
|
|
717
941
|
//------------------------------------------------------
|
|
718
942
|
class SQLParamBool {
|
|
719
|
-
constructor(value) {
|
|
943
|
+
constructor(value, asBoolean = false) {
|
|
720
944
|
this.value = value;
|
|
945
|
+
this.asBoolean = asBoolean;
|
|
721
946
|
}
|
|
722
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
|
+
}
|
|
723
956
|
if (this.value != null) {
|
|
724
957
|
data.addInt(this.value ? 1 : 0);
|
|
725
958
|
}
|
|
@@ -729,6 +962,10 @@ class SQLParamBool {
|
|
|
729
962
|
}
|
|
730
963
|
}
|
|
731
964
|
calcBlr(blr) {
|
|
965
|
+
if (this.asBoolean) {
|
|
966
|
+
blr.addByte(const_1.default.blr_bool);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
732
969
|
blr.addByte(const_1.default.blr_short);
|
|
733
970
|
blr.addShort(0);
|
|
734
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"
|