node-firebird 2.10.0 → 2.11.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 +46 -1
- package/lib/types.d.ts +22 -0
- package/lib/wire/connection.d.ts +1 -1
- package/lib/wire/connection.js +18 -11
- package/lib/wire/const.js +2 -0
- package/lib/wire/database.js +27 -12
- package/lib/wire/serialize.d.ts +3 -1
- package/lib/wire/xsqlvar.d.ts +39 -0
- package/lib/wire/xsqlvar.js +59 -0
- package/package.json +1 -1
- package/src/types.ts +22 -0
- package/src/wire/connection.ts +19 -10
- package/src/wire/const.ts +2 -0
- package/src/wire/database.ts +27 -12
- package/src/wire/serialize.ts +3 -1
- package/src/wire/xsqlvar.ts +76 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- [Promises and async/await](#promises-and-asyncawait) — the `*Async` API plus `withConnection` / `withTransaction` helpers
|
|
14
14
|
- [Connection types](#connection-types) — connection options, `firebird://` URIs and traditional connection strings, classic connections, pooling
|
|
15
15
|
- [Database object (db)](#database-object-db) — database, transaction and statement methods/options
|
|
16
|
-
- [Examples](#examples) — parametrized queries, named placeholders, custom type parsers (typeCast), BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
|
|
16
|
+
- [Examples](#examples) — parametrized queries, named placeholders, nested result tables (nestTables), custom type parsers (typeCast), BLOBs, streaming big data, transactions, driver events, database events (POST_EVENT), service manager, charsets/encoding, Firebird 3.0–6.0 features
|
|
17
17
|
- [Extensive Examples](#extensive-examples) — DECFLOAT/INT128, query cancellation (AbortSignal), batch execution (bulk inserts), statement timeouts, scrollable cursors, RETURNING multiple rows, SKIP LOCKED, advanced pooling
|
|
18
18
|
- [Using node-firebird with Express.js](#using-node-firebird-with-expressjs)
|
|
19
19
|
- [FAQ](#faq)
|
|
@@ -185,6 +185,7 @@ options.searchPath = undefined; // optional; ordered list/array of schemas to re
|
|
|
185
185
|
options.owner = undefined; // optional; owner of a newly created database — lets a superuser create a database for another user (create only, FB >= 6.0)
|
|
186
186
|
options.jsonAsObject = false; // optional; automatically stringify parameters and parse query results that contain JSON (FB >= 6.0)
|
|
187
187
|
options.namedPlaceholders = false; // set to true to allow :name placeholders in SQL with a { name: value } params object (see Named placeholders)
|
|
188
|
+
options.nestTables = false; // true nests object rows by source table (row[table][column]); a string separator flattens to 'table<sep>column' keys — see Nested result tables (nestTables). Overridable per query
|
|
188
189
|
options.typeCast = undefined; // optional; custom type parser called for every result column value (see Custom type parsers)
|
|
189
190
|
options.statementCacheSize = 0; // optional; per-connection LRU cache of prepared statements, 0 = disabled (see Prepared-statement cache)
|
|
190
191
|
```
|
|
@@ -492,6 +493,50 @@ the per-query option:
|
|
|
492
493
|
await db.queryAsync(execBlockSql, [], { namedPlaceholders: false });
|
|
493
494
|
```
|
|
494
495
|
|
|
496
|
+
### Nested result tables (nestTables)
|
|
497
|
+
|
|
498
|
+
In a `JOIN`, columns sharing a name overwrite each other in object rows —
|
|
499
|
+
`SELECT EMP.ID, DEPT.ID ...` leaves only one `ID` key. The `nestTables`
|
|
500
|
+
option (same as mysql2's) qualifies row keys by source table instead. It is
|
|
501
|
+
accepted at connection level and per query; the per-query value wins.
|
|
502
|
+
|
|
503
|
+
With `nestTables: true` each row nests one sub-object per table:
|
|
504
|
+
|
|
505
|
+
```js
|
|
506
|
+
const rows = await db.queryAsync(
|
|
507
|
+
'SELECT EMP.ID, EMP.NAME, DEPT.ID, DEPT.NAME FROM EMP JOIN DEPT ON DEPT.ID = EMP.DEPT_ID',
|
|
508
|
+
[], { nestTables: true });
|
|
509
|
+
// rows[0] = { EMP: { ID: 10, NAME: 'Ada' }, DEPT: { ID: 1, NAME: 'Engineering' } }
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
With a string separator the keys stay flat but qualified:
|
|
513
|
+
|
|
514
|
+
```js
|
|
515
|
+
const rows = await db.queryAsync(sql, [], { nestTables: '_' });
|
|
516
|
+
// rows[0] = { EMP_ID: 10, EMP_NAME: 'Ada', DEPT_ID: 1, DEPT_NAME: 'Engineering' }
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
The table qualifier is the query's relation alias when one is used, the
|
|
520
|
+
table name otherwise — so self-joins nest cleanly:
|
|
521
|
+
|
|
522
|
+
```js
|
|
523
|
+
const rows = await db.queryAsync(
|
|
524
|
+
'SELECT E.NAME, B.NAME FROM EMP E LEFT JOIN EMP B ON B.ID = E.BOSS_ID',
|
|
525
|
+
[], { nestTables: true });
|
|
526
|
+
// rows[0] = { E: { NAME: 'Grace' }, B: { NAME: 'Ada' } }
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Expression columns (no source table) qualify as `''`, exactly like mysql2:
|
|
530
|
+
they land under the `''` key when nesting (`row[''].ANSWER`) and get the
|
|
531
|
+
bare separator prefix in separator mode (`row._ANSWER`) — always prefixing
|
|
532
|
+
keeps qualified keys collision-free (a bare expression alias could
|
|
533
|
+
otherwise collide with a real `table<sep>column` key). Keys honour
|
|
534
|
+
`lowercase_keys`, and the option composes with
|
|
535
|
+
`typeCast`, `blobAsText` and `queryStream`. Object rows only: `db.execute`
|
|
536
|
+
array rows are positional and need no qualification. Works on every
|
|
537
|
+
supported Firebird version (the source-table metadata comes from the
|
|
538
|
+
statement describe, available since Firebird 2.0).
|
|
539
|
+
|
|
495
540
|
### Custom type parsers (typeCast)
|
|
496
541
|
|
|
497
542
|
The `typeCast` connection option lets you override how column values are
|
package/lib/types.d.ts
CHANGED
|
@@ -115,6 +115,18 @@ export type QueryOptions = {
|
|
|
115
115
|
* it cancels whatever is currently executing on the connection.
|
|
116
116
|
*/
|
|
117
117
|
signal?: AbortSignal;
|
|
118
|
+
/**
|
|
119
|
+
* Per-query override of the `nestTables` connection option (mysql2
|
|
120
|
+
* semantics). `true` nests each object row by source table:
|
|
121
|
+
* `row[table][column]` — the table key is the query's relation alias
|
|
122
|
+
* when one is used (`FROM emp e` → `row.E`), the table name otherwise,
|
|
123
|
+
* and `''` for expression columns. A string separator flattens keys
|
|
124
|
+
* instead: `nestTables: '_'` → `row.EMP_NAME`; expression columns get
|
|
125
|
+
* the bare separator prefix (`row._ANSWER`, as in mysql2). Keys honour
|
|
126
|
+
* `lowercase_keys`. Object rows only — `db.execute` array rows are
|
|
127
|
+
* unaffected.
|
|
128
|
+
*/
|
|
129
|
+
nestTables?: boolean | string;
|
|
118
130
|
};
|
|
119
131
|
export type QueryStreamOptions = QueryOptions & {
|
|
120
132
|
/**
|
|
@@ -254,6 +266,16 @@ export interface Options {
|
|
|
254
266
|
* per-query `namedPlaceholders: false` override.
|
|
255
267
|
*/
|
|
256
268
|
namedPlaceholders?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Qualify object-row keys by source table (same option as mysql2), so
|
|
271
|
+
* JOINed columns with the same name stop overwriting each other:
|
|
272
|
+
* `true` nests each row as `row[table][column]`; a string separator
|
|
273
|
+
* flattens to `row['table' + sep + 'column']`. See
|
|
274
|
+
* `QueryOptions.nestTables` for the exact key rules. Applies wherever
|
|
275
|
+
* object rows are produced (query / sequentially / queryStream);
|
|
276
|
+
* array rows (execute) are unaffected. Overridable per query.
|
|
277
|
+
*/
|
|
278
|
+
nestTables?: boolean | string;
|
|
257
279
|
/**
|
|
258
280
|
* TCP keepalive probing to detect dead/stale connections (same option
|
|
259
281
|
* names as mysql2). On by default; set false to disable.
|
package/lib/wire/connection.d.ts
CHANGED
|
@@ -150,6 +150,6 @@ declare function decodeResponse(data: XdrReader, callback: QueueCallback | undef
|
|
|
150
150
|
error: Error;
|
|
151
151
|
};
|
|
152
152
|
declare function describe(buff: Buffer, statement: Statement): void;
|
|
153
|
-
declare function fetch_blob_async_transaction(statement: Statement, id: Quad, column: string | number, row: number, meta?: Xsql.SQLVarBase): (transactionArg: any) => Promise<unknown>;
|
|
153
|
+
declare function fetch_blob_async_transaction(statement: Statement, id: Quad, column: string | number, row: number, meta?: Xsql.SQLVarBase, table?: string): (transactionArg: any) => Promise<unknown>;
|
|
154
154
|
declare function fetch_blob_async(statement: Statement, id: Quad, name: string | number, row: number): (transaction: Transaction, callback: any) => void;
|
|
155
155
|
export = Connection;
|
package/lib/wire/connection.js
CHANGED
|
@@ -1699,7 +1699,9 @@ class Connection {
|
|
|
1699
1699
|
readBlobsSequentially(0, []).then((arrBlob) => {
|
|
1700
1700
|
for (let i = 0; i < arrBlob.length; i++) {
|
|
1701
1701
|
const blob = arrBlob[i];
|
|
1702
|
-
|
|
1702
|
+
// nestTables === true rows: the value lives in the
|
|
1703
|
+
// per-table sub-object, not on the row itself
|
|
1704
|
+
Xsql.nestCell(ret.data[blob.row], blob.table)[blob.column] = applyTypeCast(statement.connection.options, blob.meta || {}, parseValueIfJson(blob.value, statement.connection.options));
|
|
1703
1705
|
}
|
|
1704
1706
|
doSynchronousLoop(ret.data, (row, _i, next) => {
|
|
1705
1707
|
const pos = asStream ? streamIndex++ : (data.push(row) - 1);
|
|
@@ -2085,6 +2087,7 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2085
2087
|
delete data.frow;
|
|
2086
2088
|
delete data.frows;
|
|
2087
2089
|
delete data.fcols;
|
|
2090
|
+
delete data.ftables;
|
|
2088
2091
|
if (isOpFetch && data.fop) { // could be set when a packet is not complete
|
|
2089
2092
|
data.readBuffer(68); // ??
|
|
2090
2093
|
op = data.readInt(); // ??
|
|
@@ -2102,11 +2105,12 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2102
2105
|
data.frow = data.frow || (custom.asObject ? {} : new Array(output.length));
|
|
2103
2106
|
data.frows = data.frows || [];
|
|
2104
2107
|
if (custom.asObject && !data.fcols) {
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2108
|
+
const nest = Xsql.resolveNestTables(custom, cnx.options);
|
|
2109
|
+
const columnKeys = Xsql.computeColumnKeys(output, nest, lowercase_keys);
|
|
2110
|
+
data.fcols = columnKeys.map((k) => k.key);
|
|
2111
|
+
if (nest === true) {
|
|
2112
|
+
// computeColumnKeys always sets table when nesting
|
|
2113
|
+
data.ftables = columnKeys.map((k) => k.table);
|
|
2110
2114
|
}
|
|
2111
2115
|
}
|
|
2112
2116
|
const arrBlob = [];
|
|
@@ -2129,7 +2133,10 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2129
2133
|
item = output[data.fcolumn];
|
|
2130
2134
|
if (!lowerV13 && nullBitSet.get(data.fcolumn)) {
|
|
2131
2135
|
const nullKey = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
|
|
2132
|
-
|
|
2136
|
+
// ftables is only set when nestTables === true, so
|
|
2137
|
+
// the default path writes straight into the row
|
|
2138
|
+
(data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn]) : data.frow)[nullKey] =
|
|
2139
|
+
applyTypeCast(cnx.options, item, null);
|
|
2133
2140
|
continue;
|
|
2134
2141
|
}
|
|
2135
2142
|
try {
|
|
@@ -2143,7 +2150,7 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2143
2150
|
let pendingTextBlob = false;
|
|
2144
2151
|
if (item.type === const_1.default.SQL_BLOB && value !== null) {
|
|
2145
2152
|
if (item.subType === const_1.default.isc_blob_text && cnx.options.blobAsText) {
|
|
2146
|
-
value = fetch_blob_async_transaction(statement, value, key, row, item);
|
|
2153
|
+
value = fetch_blob_async_transaction(statement, value, key, row, item, data.ftables && data.ftables[data.fcolumn]);
|
|
2147
2154
|
arrBlob.push(value);
|
|
2148
2155
|
pendingTextBlob = true;
|
|
2149
2156
|
}
|
|
@@ -2151,7 +2158,7 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
2151
2158
|
value = fetch_blob_async(statement, value, key, row);
|
|
2152
2159
|
}
|
|
2153
2160
|
}
|
|
2154
|
-
data.frow[key] = pendingTextBlob
|
|
2161
|
+
(data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn]) : data.frow)[key] = pendingTextBlob
|
|
2155
2162
|
? value
|
|
2156
2163
|
: applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
|
|
2157
2164
|
}
|
|
@@ -3019,8 +3026,8 @@ function CalcBlr(blr, xsqlda) {
|
|
|
3019
3026
|
blr.addByte(const_1.default.blr_end);
|
|
3020
3027
|
blr.addByte(const_1.default.blr_eoc);
|
|
3021
3028
|
}
|
|
3022
|
-
function fetch_blob_async_transaction(statement, id, column, row, meta) {
|
|
3023
|
-
const infoValue = { row, column, value: '', meta };
|
|
3029
|
+
function fetch_blob_async_transaction(statement, id, column, row, meta, table) {
|
|
3030
|
+
const infoValue = { row, column, value: '', meta, table };
|
|
3024
3031
|
return (transactionArg) => {
|
|
3025
3032
|
const cacheKey = `${id.high}:${id.low}`;
|
|
3026
3033
|
if (statement.connection._inlineBlobs && statement.connection._inlineBlobs.has(cacheKey)) {
|
package/lib/wire/const.js
CHANGED
|
@@ -577,6 +577,7 @@ const DESCRIBE = [
|
|
|
577
577
|
sqlInfo.isc_info_sql_length,
|
|
578
578
|
sqlInfo.isc_info_sql_field,
|
|
579
579
|
sqlInfo.isc_info_sql_relation,
|
|
580
|
+
sqlInfo.isc_info_sql_relation_alias, // FB 2.0+: query alias of the source relation (nestTables)
|
|
580
581
|
//isc_info_sql_owner,
|
|
581
582
|
sqlInfo.isc_info_sql_alias,
|
|
582
583
|
sqlInfo.isc_info_sql_describe_end,
|
|
@@ -604,6 +605,7 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
604
605
|
sqlInfo.isc_info_sql_field,
|
|
605
606
|
sqlInfo.isc_info_sql_relation,
|
|
606
607
|
sqlInfo.isc_info_sql_relation_schema, // FB 6.0: schema of source relation
|
|
608
|
+
sqlInfo.isc_info_sql_relation_alias, // query alias of the source relation (nestTables)
|
|
607
609
|
//isc_info_sql_owner,
|
|
608
610
|
sqlInfo.isc_info_sql_alias,
|
|
609
611
|
sqlInfo.isc_info_sql_describe_end,
|
package/lib/wire/database.js
CHANGED
|
@@ -6,6 +6,7 @@ const events_1 = __importDefault(require("events"));
|
|
|
6
6
|
const callback_1 = require("../callback");
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const const_1 = __importDefault(require("./const"));
|
|
9
|
+
const xsqlvar_1 = require("./xsqlvar");
|
|
9
10
|
const eventConnection_1 = __importDefault(require("./eventConnection"));
|
|
10
11
|
const fbEventManager_1 = __importDefault(require("./fbEventManager"));
|
|
11
12
|
const query_stream_1 = __importDefault(require("./query-stream"));
|
|
@@ -75,30 +76,42 @@ function readblob(blob, callback) {
|
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
|
-
function fetchBlobSyncRow(row, meta, callback) {
|
|
79
|
-
if (!row || !meta || !meta.length) {
|
|
79
|
+
function fetchBlobSyncRow(row, meta, nestTables, lowercaseKeys, callback) {
|
|
80
|
+
if (!row || !meta || !meta.length || !meta.some((m) => m && m.type === const_1.default.SQL_BLOB)) {
|
|
80
81
|
callback(null, row);
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
// locate blob cells by the same key computation the fetch decoder used,
|
|
85
|
+
// rather than assuming Object.keys(row) is index-aligned with meta —
|
|
86
|
+
// duplicate JOIN column names (and nested rows) break that alignment.
|
|
87
|
+
// Array rows (sequentially's legacy boolean form) are keyed by index.
|
|
88
|
+
const isArrayRow = Array.isArray(row);
|
|
89
|
+
const keys = isArrayRow ? null : (0, xsqlvar_1.computeColumnKeys)(meta, nestTables, lowercaseKeys);
|
|
90
|
+
const blobCells = [];
|
|
85
91
|
for (let i = 0; i < meta.length; i++) {
|
|
86
|
-
if (meta[i]
|
|
87
|
-
|
|
92
|
+
if (!meta[i] || meta[i].type !== const_1.default.SQL_BLOB) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const target = keys ? (0, xsqlvar_1.nestCell)(row, keys[i].table) : row;
|
|
96
|
+
const key = keys ? keys[i].key : i;
|
|
97
|
+
// duplicate aliases collapse onto one cell — read it only once
|
|
98
|
+
if (typeof target[key] === 'function' &&
|
|
99
|
+
!blobCells.some((cell) => cell.target === target && cell.key === key)) {
|
|
100
|
+
blobCells.push({ target, key });
|
|
88
101
|
}
|
|
89
102
|
}
|
|
90
|
-
if (!
|
|
103
|
+
if (!blobCells.length) {
|
|
91
104
|
callback(null, row);
|
|
92
105
|
return;
|
|
93
106
|
}
|
|
94
|
-
let pending =
|
|
107
|
+
let pending = blobCells.length;
|
|
95
108
|
let blobErr;
|
|
96
|
-
|
|
97
|
-
readblob(
|
|
109
|
+
blobCells.forEach(function (cell) {
|
|
110
|
+
readblob(cell.target[cell.key], function (err, data) {
|
|
98
111
|
if (err && !blobErr) {
|
|
99
112
|
blobErr = err;
|
|
100
113
|
}
|
|
101
|
-
|
|
114
|
+
cell.target[cell.key] = data;
|
|
102
115
|
pending--;
|
|
103
116
|
if (pending === 0) {
|
|
104
117
|
callback(blobErr, row);
|
|
@@ -259,7 +272,9 @@ class Database extends events_1.default.EventEmitter {
|
|
|
259
272
|
done = true;
|
|
260
273
|
next(err);
|
|
261
274
|
};
|
|
262
|
-
|
|
275
|
+
// options is read at call time, after the normalization below
|
|
276
|
+
const nest = (0, xsqlvar_1.resolveNestTables)(options, self.connection.options);
|
|
277
|
+
fetchBlobSyncRow(row, meta, nest, self.connection._lowercase_keys, function (blobErr) {
|
|
263
278
|
if (blobErr) {
|
|
264
279
|
finish(blobErr);
|
|
265
280
|
return;
|
package/lib/wire/serialize.d.ts
CHANGED
|
@@ -83,8 +83,10 @@ export declare class XdrReader {
|
|
|
83
83
|
frow?: any;
|
|
84
84
|
/** rows decoded so far in this call */
|
|
85
85
|
frows?: any[];
|
|
86
|
-
/** cached object-row keys (column aliases) */
|
|
86
|
+
/** cached object-row keys (column aliases, qualified when nestTables is set) */
|
|
87
87
|
fcols?: string[];
|
|
88
|
+
/** cached per-column table keys when nestTables === true */
|
|
89
|
+
ftables?: string[];
|
|
88
90
|
constructor(buffer: Buffer);
|
|
89
91
|
readInt(): number;
|
|
90
92
|
readUInt(): number;
|
package/lib/wire/xsqlvar.d.ts
CHANGED
|
@@ -21,6 +21,45 @@ export declare abstract class SQLVarBase {
|
|
|
21
21
|
abstract decode(data: XdrReader, lowerV13: boolean, options?: any): any;
|
|
22
22
|
abstract calcBlr(blr: BlrWriter): void;
|
|
23
23
|
}
|
|
24
|
+
/** Effective object-row key(s) of one output column (see computeColumnKeys). */
|
|
25
|
+
export interface ColumnKey {
|
|
26
|
+
/** Top-level table key when nestTables === true; undefined otherwise. */
|
|
27
|
+
table?: string;
|
|
28
|
+
/** Property key: the column alias, or 'table<sep>alias' in separator mode. */
|
|
29
|
+
key: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compute the object-row property keys for a statement's output columns,
|
|
33
|
+
* honouring the nestTables and lowercase_keys options. The table qualifier
|
|
34
|
+
* is the query's relation alias when one is used (relationAlias, requested
|
|
35
|
+
* via isc_info_sql_relation_alias), the relation name otherwise, so
|
|
36
|
+
* self-joins nest under their query aliases. Expression columns (no source
|
|
37
|
+
* relation) qualify as '' exactly like mysql2: they nest under the '' key,
|
|
38
|
+
* and in separator mode become '<sep>alias' — always prefixing keeps
|
|
39
|
+
* qualified keys collision-free (a bare expression alias could otherwise
|
|
40
|
+
* collide with a real column's 'table<sep>column' key). Used by the fetch
|
|
41
|
+
* decoder and by fetchBlobSyncRow, which must agree on where each column
|
|
42
|
+
* landed in the row.
|
|
43
|
+
*/
|
|
44
|
+
export declare function computeColumnKeys(output: SQLVarBase[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined): ColumnKey[];
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the effective nestTables value: the per-query option wins over
|
|
47
|
+
* the connection option. The decoder and fetchBlobSyncRow both use this —
|
|
48
|
+
* they must agree on whether nesting is active or blob cells are looked
|
|
49
|
+
* up in the wrong place.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveNestTables(queryOptions: {
|
|
52
|
+
nestTables?: boolean | string;
|
|
53
|
+
} | undefined, connectionOptions: {
|
|
54
|
+
nestTables?: boolean | string;
|
|
55
|
+
} | undefined): boolean | string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The object a column's value lives in: the row itself, or — when the
|
|
58
|
+
* column carries a nestTables table qualifier — the row's per-table
|
|
59
|
+
* sub-object, created on first use. Every site that reads or writes a
|
|
60
|
+
* cell by ColumnKey must resolve it through here.
|
|
61
|
+
*/
|
|
62
|
+
export declare function nestCell(row: any, table: string | undefined): any;
|
|
24
63
|
export declare class SQLVarText extends SQLVarBase {
|
|
25
64
|
decode(data: XdrReader, lowerV13: boolean, options?: any): string | Buffer<ArrayBufferLike> | null | undefined;
|
|
26
65
|
calcBlr(blr: BlrWriter): void;
|
package/lib/wire/xsqlvar.js
CHANGED
|
@@ -4,6 +4,9 @@ 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.computeColumnKeys = computeColumnKeys;
|
|
8
|
+
exports.resolveNestTables = resolveNestTables;
|
|
9
|
+
exports.nestCell = nestCell;
|
|
7
10
|
exports.encodeDateTimeParts = encodeDateTimeParts;
|
|
8
11
|
const const_1 = __importDefault(require("./const"));
|
|
9
12
|
/***************************************
|
|
@@ -68,6 +71,62 @@ function resolveTextEncoding(options) {
|
|
|
68
71
|
class SQLVarBase {
|
|
69
72
|
}
|
|
70
73
|
exports.SQLVarBase = SQLVarBase;
|
|
74
|
+
/**
|
|
75
|
+
* Compute the object-row property keys for a statement's output columns,
|
|
76
|
+
* honouring the nestTables and lowercase_keys options. The table qualifier
|
|
77
|
+
* is the query's relation alias when one is used (relationAlias, requested
|
|
78
|
+
* via isc_info_sql_relation_alias), the relation name otherwise, so
|
|
79
|
+
* self-joins nest under their query aliases. Expression columns (no source
|
|
80
|
+
* relation) qualify as '' exactly like mysql2: they nest under the '' key,
|
|
81
|
+
* and in separator mode become '<sep>alias' — always prefixing keeps
|
|
82
|
+
* qualified keys collision-free (a bare expression alias could otherwise
|
|
83
|
+
* collide with a real column's 'table<sep>column' key). Used by the fetch
|
|
84
|
+
* decoder and by fetchBlobSyncRow, which must agree on where each column
|
|
85
|
+
* landed in the row.
|
|
86
|
+
*/
|
|
87
|
+
function computeColumnKeys(output, nestTables, lowercaseKeys) {
|
|
88
|
+
return output.map((column) => {
|
|
89
|
+
let key = column.alias || '';
|
|
90
|
+
if (lowercaseKeys) {
|
|
91
|
+
key = key.toLowerCase();
|
|
92
|
+
}
|
|
93
|
+
if (nestTables !== true && typeof nestTables !== 'string') {
|
|
94
|
+
return { key };
|
|
95
|
+
}
|
|
96
|
+
let table = column.relationAlias || column.relation || '';
|
|
97
|
+
if (lowercaseKeys) {
|
|
98
|
+
table = table.toLowerCase();
|
|
99
|
+
}
|
|
100
|
+
if (nestTables === true) {
|
|
101
|
+
return { table, key };
|
|
102
|
+
}
|
|
103
|
+
return { key: table + nestTables + key };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the effective nestTables value: the per-query option wins over
|
|
108
|
+
* the connection option. The decoder and fetchBlobSyncRow both use this —
|
|
109
|
+
* they must agree on whether nesting is active or blob cells are looked
|
|
110
|
+
* up in the wrong place.
|
|
111
|
+
*/
|
|
112
|
+
function resolveNestTables(queryOptions, connectionOptions) {
|
|
113
|
+
if (queryOptions && queryOptions.nestTables !== undefined) {
|
|
114
|
+
return queryOptions.nestTables;
|
|
115
|
+
}
|
|
116
|
+
return connectionOptions && connectionOptions.nestTables;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The object a column's value lives in: the row itself, or — when the
|
|
120
|
+
* column carries a nestTables table qualifier — the row's per-table
|
|
121
|
+
* sub-object, created on first use. Every site that reads or writes a
|
|
122
|
+
* cell by ColumnKey must resolve it through here.
|
|
123
|
+
*/
|
|
124
|
+
function nestCell(row, table) {
|
|
125
|
+
if (table === undefined) {
|
|
126
|
+
return row;
|
|
127
|
+
}
|
|
128
|
+
return row[table] || (row[table] = {});
|
|
129
|
+
}
|
|
71
130
|
//------------------------------------------------------
|
|
72
131
|
class SQLVarText extends SQLVarBase {
|
|
73
132
|
decode(data, lowerV13, options) {
|
package/package.json
CHANGED
package/src/types.ts
CHANGED
|
@@ -127,6 +127,18 @@ export type QueryOptions = {
|
|
|
127
127
|
* it cancels whatever is currently executing on the connection.
|
|
128
128
|
*/
|
|
129
129
|
signal?: AbortSignal;
|
|
130
|
+
/**
|
|
131
|
+
* Per-query override of the `nestTables` connection option (mysql2
|
|
132
|
+
* semantics). `true` nests each object row by source table:
|
|
133
|
+
* `row[table][column]` — the table key is the query's relation alias
|
|
134
|
+
* when one is used (`FROM emp e` → `row.E`), the table name otherwise,
|
|
135
|
+
* and `''` for expression columns. A string separator flattens keys
|
|
136
|
+
* instead: `nestTables: '_'` → `row.EMP_NAME`; expression columns get
|
|
137
|
+
* the bare separator prefix (`row._ANSWER`, as in mysql2). Keys honour
|
|
138
|
+
* `lowercase_keys`. Object rows only — `db.execute` array rows are
|
|
139
|
+
* unaffected.
|
|
140
|
+
*/
|
|
141
|
+
nestTables?: boolean | string;
|
|
130
142
|
}
|
|
131
143
|
|
|
132
144
|
export type QueryStreamOptions = QueryOptions & {
|
|
@@ -314,6 +326,16 @@ export interface Options {
|
|
|
314
326
|
* per-query `namedPlaceholders: false` override.
|
|
315
327
|
*/
|
|
316
328
|
namedPlaceholders?: boolean;
|
|
329
|
+
/**
|
|
330
|
+
* Qualify object-row keys by source table (same option as mysql2), so
|
|
331
|
+
* JOINed columns with the same name stop overwriting each other:
|
|
332
|
+
* `true` nests each row as `row[table][column]`; a string separator
|
|
333
|
+
* flattens to `row['table' + sep + 'column']`. See
|
|
334
|
+
* `QueryOptions.nestTables` for the exact key rules. Applies wherever
|
|
335
|
+
* object rows are produced (query / sequentially / queryStream);
|
|
336
|
+
* array rows (execute) are unaffected. Overridable per query.
|
|
337
|
+
*/
|
|
338
|
+
nestTables?: boolean | string;
|
|
317
339
|
/**
|
|
318
340
|
* TCP keepalive probing to detect dead/stale connections (same option
|
|
319
341
|
* names as mysql2). On by default; set false to disable.
|
package/src/wire/connection.ts
CHANGED
|
@@ -2015,7 +2015,9 @@ class Connection {
|
|
|
2015
2015
|
readBlobsSequentially(0, []).then((arrBlob: any) => {
|
|
2016
2016
|
for (let i = 0; i < arrBlob.length; i++) {
|
|
2017
2017
|
const blob = arrBlob[i];
|
|
2018
|
-
|
|
2018
|
+
// nestTables === true rows: the value lives in the
|
|
2019
|
+
// per-table sub-object, not on the row itself
|
|
2020
|
+
Xsql.nestCell(ret.data[blob.row], blob.table)[blob.column] = applyTypeCast(
|
|
2019
2021
|
statement.connection.options, blob.meta || {},
|
|
2020
2022
|
parseValueIfJson(blob.value, statement.connection.options));
|
|
2021
2023
|
}
|
|
@@ -2469,6 +2471,7 @@ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cn
|
|
|
2469
2471
|
delete data.frow;
|
|
2470
2472
|
delete data.frows;
|
|
2471
2473
|
delete data.fcols;
|
|
2474
|
+
delete data.ftables;
|
|
2472
2475
|
|
|
2473
2476
|
if (isOpFetch && data.fop) { // could be set when a packet is not complete
|
|
2474
2477
|
data.readBuffer(68); // ??
|
|
@@ -2490,10 +2493,12 @@ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cn
|
|
|
2490
2493
|
data.frows = data.frows || [];
|
|
2491
2494
|
|
|
2492
2495
|
if (custom.asObject && !data.fcols) {
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2496
|
+
const nest = Xsql.resolveNestTables(custom, cnx.options);
|
|
2497
|
+
const columnKeys = Xsql.computeColumnKeys(output, nest, lowercase_keys);
|
|
2498
|
+
data.fcols = columnKeys.map((k) => k.key);
|
|
2499
|
+
if (nest === true) {
|
|
2500
|
+
// computeColumnKeys always sets table when nesting
|
|
2501
|
+
data.ftables = columnKeys.map((k) => k.table!);
|
|
2497
2502
|
}
|
|
2498
2503
|
}
|
|
2499
2504
|
|
|
@@ -2521,7 +2526,10 @@ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cn
|
|
|
2521
2526
|
|
|
2522
2527
|
if (!lowerV13 && nullBitSet!.get(data.fcolumn)) {
|
|
2523
2528
|
const nullKey = custom.asObject ? data.fcols![data.fcolumn!] : data.fcolumn;
|
|
2524
|
-
|
|
2529
|
+
// ftables is only set when nestTables === true, so
|
|
2530
|
+
// the default path writes straight into the row
|
|
2531
|
+
(data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn!]) : data.frow)[nullKey] =
|
|
2532
|
+
applyTypeCast(cnx.options, item, null);
|
|
2525
2533
|
|
|
2526
2534
|
continue;
|
|
2527
2535
|
}
|
|
@@ -2538,7 +2546,8 @@ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cn
|
|
|
2538
2546
|
|
|
2539
2547
|
if (item.type === Const.SQL_BLOB && value !== null) {
|
|
2540
2548
|
if (item.subType === Const.isc_blob_text && cnx.options.blobAsText) {
|
|
2541
|
-
value = fetch_blob_async_transaction(statement, value, key, row, item
|
|
2549
|
+
value = fetch_blob_async_transaction(statement, value, key, row, item,
|
|
2550
|
+
data.ftables && data.ftables[data.fcolumn!]);
|
|
2542
2551
|
arrBlob.push(value);
|
|
2543
2552
|
pendingTextBlob = true;
|
|
2544
2553
|
} else {
|
|
@@ -2546,7 +2555,7 @@ function decodeResponse(data: XdrReader, callback: QueueCallback | undefined, cn
|
|
|
2546
2555
|
}
|
|
2547
2556
|
}
|
|
2548
2557
|
|
|
2549
|
-
data.frow[key] = pendingTextBlob
|
|
2558
|
+
(data.ftables ? Xsql.nestCell(data.frow, data.ftables[data.fcolumn!]) : data.frow)[key] = pendingTextBlob
|
|
2550
2559
|
? value
|
|
2551
2560
|
: applyTypeCast(cnx.options, item, parseValueIfJson(value, cnx.options));
|
|
2552
2561
|
} catch (e) {
|
|
@@ -3475,8 +3484,8 @@ function CalcBlr(blr: BlrWriter, xsqlda: any[]) {
|
|
|
3475
3484
|
blr.addByte(Const.blr_eoc);
|
|
3476
3485
|
}
|
|
3477
3486
|
|
|
3478
|
-
function fetch_blob_async_transaction(statement: Statement, id: Quad, column: string | number, row: number, meta?: Xsql.SQLVarBase) {
|
|
3479
|
-
const infoValue = { row, column, value: '', meta };
|
|
3487
|
+
function fetch_blob_async_transaction(statement: Statement, id: Quad, column: string | number, row: number, meta?: Xsql.SQLVarBase, table?: string) {
|
|
3488
|
+
const infoValue = { row, column, value: '', meta, table };
|
|
3480
3489
|
|
|
3481
3490
|
return (transactionArg: any) => {
|
|
3482
3491
|
const cacheKey = `${id.high}:${id.low}`;
|
package/src/wire/const.ts
CHANGED
|
@@ -626,6 +626,7 @@ const DESCRIBE = [
|
|
|
626
626
|
sqlInfo.isc_info_sql_length,
|
|
627
627
|
sqlInfo.isc_info_sql_field,
|
|
628
628
|
sqlInfo.isc_info_sql_relation,
|
|
629
|
+
sqlInfo.isc_info_sql_relation_alias, // FB 2.0+: query alias of the source relation (nestTables)
|
|
629
630
|
//isc_info_sql_owner,
|
|
630
631
|
sqlInfo.isc_info_sql_alias,
|
|
631
632
|
sqlInfo.isc_info_sql_describe_end,
|
|
@@ -654,6 +655,7 @@ const DESCRIBE_WITH_SCHEMA = [
|
|
|
654
655
|
sqlInfo.isc_info_sql_field,
|
|
655
656
|
sqlInfo.isc_info_sql_relation,
|
|
656
657
|
sqlInfo.isc_info_sql_relation_schema, // FB 6.0: schema of source relation
|
|
658
|
+
sqlInfo.isc_info_sql_relation_alias, // query alias of the source relation (nestTables)
|
|
657
659
|
//isc_info_sql_owner,
|
|
658
660
|
sqlInfo.isc_info_sql_alias,
|
|
659
661
|
sqlInfo.isc_info_sql_describe_end,
|
package/src/wire/database.ts
CHANGED
|
@@ -2,6 +2,7 @@ import Events from 'events';
|
|
|
2
2
|
import { doError, fromCallback, type Callback, type SimpleCallback } from '../callback';
|
|
3
3
|
import { escape } from '../utils';
|
|
4
4
|
import Const from './const';
|
|
5
|
+
import { computeColumnKeys, nestCell, resolveNestTables } from './xsqlvar';
|
|
5
6
|
import EventConnection from './eventConnection';
|
|
6
7
|
import FbEventManager from './fbEventManager';
|
|
7
8
|
import makeQueryStream from './query-stream';
|
|
@@ -94,35 +95,47 @@ function readblob(blob: any, callback: Callback): void {
|
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
function fetchBlobSyncRow(row: any, meta: any[], callback: Callback): void {
|
|
98
|
-
if (!row || !meta || !meta.length) {
|
|
98
|
+
function fetchBlobSyncRow(row: any, meta: any[], nestTables: boolean | string | undefined, lowercaseKeys: boolean | undefined, callback: Callback): void {
|
|
99
|
+
if (!row || !meta || !meta.length || !meta.some((m) => m && m.type === Const.SQL_BLOB)) {
|
|
99
100
|
callback(null, row);
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
// locate blob cells by the same key computation the fetch decoder used,
|
|
105
|
+
// rather than assuming Object.keys(row) is index-aligned with meta —
|
|
106
|
+
// duplicate JOIN column names (and nested rows) break that alignment.
|
|
107
|
+
// Array rows (sequentially's legacy boolean form) are keyed by index.
|
|
108
|
+
const isArrayRow = Array.isArray(row);
|
|
109
|
+
const keys = isArrayRow ? null : computeColumnKeys(meta, nestTables, lowercaseKeys);
|
|
110
|
+
const blobCells: { target: any; key: string | number }[] = [];
|
|
105
111
|
|
|
106
112
|
for (let i = 0; i < meta.length; i++) {
|
|
107
|
-
if (meta[i]
|
|
108
|
-
|
|
113
|
+
if (!meta[i] || meta[i].type !== Const.SQL_BLOB) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const target = keys ? nestCell(row, keys[i].table) : row;
|
|
117
|
+
const key = keys ? keys[i].key : i;
|
|
118
|
+
// duplicate aliases collapse onto one cell — read it only once
|
|
119
|
+
if (typeof target[key] === 'function' &&
|
|
120
|
+
!blobCells.some((cell) => cell.target === target && cell.key === key)) {
|
|
121
|
+
blobCells.push({ target, key });
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
if (!
|
|
125
|
+
if (!blobCells.length) {
|
|
113
126
|
callback(null, row);
|
|
114
127
|
return;
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
let pending =
|
|
130
|
+
let pending = blobCells.length;
|
|
118
131
|
let blobErr: any;
|
|
119
132
|
|
|
120
|
-
|
|
121
|
-
readblob(
|
|
133
|
+
blobCells.forEach(function(cell) {
|
|
134
|
+
readblob(cell.target[cell.key], function(err: any, data: any) {
|
|
122
135
|
if (err && !blobErr) {
|
|
123
136
|
blobErr = err;
|
|
124
137
|
}
|
|
125
|
-
|
|
138
|
+
cell.target[cell.key] = data;
|
|
126
139
|
pending--;
|
|
127
140
|
if (pending === 0) {
|
|
128
141
|
callback(blobErr, row);
|
|
@@ -322,7 +335,9 @@ class Database extends Events.EventEmitter {
|
|
|
322
335
|
next(err);
|
|
323
336
|
};
|
|
324
337
|
|
|
325
|
-
|
|
338
|
+
// options is read at call time, after the normalization below
|
|
339
|
+
const nest = resolveNestTables(options as any, self.connection.options);
|
|
340
|
+
fetchBlobSyncRow(row, meta, nest, self.connection._lowercase_keys, function(blobErr: any) {
|
|
326
341
|
if (blobErr) {
|
|
327
342
|
finish(blobErr);
|
|
328
343
|
return;
|
package/src/wire/serialize.ts
CHANGED
|
@@ -440,8 +440,10 @@ export class XdrReader {
|
|
|
440
440
|
frow?: any;
|
|
441
441
|
/** rows decoded so far in this call */
|
|
442
442
|
frows?: any[];
|
|
443
|
-
/** cached object-row keys (column aliases) */
|
|
443
|
+
/** cached object-row keys (column aliases, qualified when nestTables is set) */
|
|
444
444
|
fcols?: string[];
|
|
445
|
+
/** cached per-column table keys when nestTables === true */
|
|
446
|
+
ftables?: string[];
|
|
445
447
|
|
|
446
448
|
constructor(buffer: Buffer) {
|
|
447
449
|
this.buffer = buffer;
|
package/src/wire/xsqlvar.ts
CHANGED
|
@@ -93,6 +93,82 @@ export abstract class SQLVarBase {
|
|
|
93
93
|
|
|
94
94
|
//------------------------------------------------------
|
|
95
95
|
|
|
96
|
+
/** Effective object-row key(s) of one output column (see computeColumnKeys). */
|
|
97
|
+
export interface ColumnKey {
|
|
98
|
+
/** Top-level table key when nestTables === true; undefined otherwise. */
|
|
99
|
+
table?: string;
|
|
100
|
+
/** Property key: the column alias, or 'table<sep>alias' in separator mode. */
|
|
101
|
+
key: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Compute the object-row property keys for a statement's output columns,
|
|
106
|
+
* honouring the nestTables and lowercase_keys options. The table qualifier
|
|
107
|
+
* is the query's relation alias when one is used (relationAlias, requested
|
|
108
|
+
* via isc_info_sql_relation_alias), the relation name otherwise, so
|
|
109
|
+
* self-joins nest under their query aliases. Expression columns (no source
|
|
110
|
+
* relation) qualify as '' exactly like mysql2: they nest under the '' key,
|
|
111
|
+
* and in separator mode become '<sep>alias' — always prefixing keeps
|
|
112
|
+
* qualified keys collision-free (a bare expression alias could otherwise
|
|
113
|
+
* collide with a real column's 'table<sep>column' key). Used by the fetch
|
|
114
|
+
* decoder and by fetchBlobSyncRow, which must agree on where each column
|
|
115
|
+
* landed in the row.
|
|
116
|
+
*/
|
|
117
|
+
export function computeColumnKeys(
|
|
118
|
+
output: SQLVarBase[],
|
|
119
|
+
nestTables: boolean | string | undefined,
|
|
120
|
+
lowercaseKeys: boolean | undefined
|
|
121
|
+
): ColumnKey[] {
|
|
122
|
+
return output.map((column) => {
|
|
123
|
+
let key = column.alias || '';
|
|
124
|
+
if (lowercaseKeys) {
|
|
125
|
+
key = key.toLowerCase();
|
|
126
|
+
}
|
|
127
|
+
if (nestTables !== true && typeof nestTables !== 'string') {
|
|
128
|
+
return { key };
|
|
129
|
+
}
|
|
130
|
+
let table = column.relationAlias || column.relation || '';
|
|
131
|
+
if (lowercaseKeys) {
|
|
132
|
+
table = table.toLowerCase();
|
|
133
|
+
}
|
|
134
|
+
if (nestTables === true) {
|
|
135
|
+
return { table, key };
|
|
136
|
+
}
|
|
137
|
+
return { key: table + nestTables + key };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the effective nestTables value: the per-query option wins over
|
|
143
|
+
* the connection option. The decoder and fetchBlobSyncRow both use this —
|
|
144
|
+
* they must agree on whether nesting is active or blob cells are looked
|
|
145
|
+
* up in the wrong place.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveNestTables(
|
|
148
|
+
queryOptions: { nestTables?: boolean | string } | undefined,
|
|
149
|
+
connectionOptions: { nestTables?: boolean | string } | undefined
|
|
150
|
+
): boolean | string | undefined {
|
|
151
|
+
if (queryOptions && queryOptions.nestTables !== undefined) {
|
|
152
|
+
return queryOptions.nestTables;
|
|
153
|
+
}
|
|
154
|
+
return connectionOptions && connectionOptions.nestTables;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The object a column's value lives in: the row itself, or — when the
|
|
159
|
+
* column carries a nestTables table qualifier — the row's per-table
|
|
160
|
+
* sub-object, created on first use. Every site that reads or writes a
|
|
161
|
+
* cell by ColumnKey must resolve it through here.
|
|
162
|
+
*/
|
|
163
|
+
export function nestCell(row: any, table: string | undefined) {
|
|
164
|
+
if (table === undefined) {
|
|
165
|
+
return row;
|
|
166
|
+
}
|
|
167
|
+
return row[table] || (row[table] = {});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//------------------------------------------------------
|
|
171
|
+
|
|
96
172
|
export class SQLVarText extends SQLVarBase {
|
|
97
173
|
decode(data: XdrReader, lowerV13: boolean, options?: any) {
|
|
98
174
|
let ret;
|