postgrejs 3.8.0 → 3.10.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/CHANGELOG.md +59 -1
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +63 -11
- package/connection/intl-connection.d.ts +164 -6
- package/connection/intl-connection.js +558 -37
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +22 -3
- package/connection/prepared-statement.js +22 -2
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/circle-type.js +5 -2
- package/data-types/classes/geometric.d.ts +49 -7
- package/data-types/classes/geometric.js +59 -11
- package/data-types/classes/interval.d.ts +40 -16
- package/data-types/classes/interval.js +68 -40
- package/data-types/classes/numeric.d.ts +6 -0
- package/data-types/classes/numeric.js +8 -0
- package/data-types/classes/range.d.ts +6 -1
- package/data-types/classes/range.js +6 -1
- package/data-types/date-type.js +2 -2
- package/data-types/interval-type.js +1 -1
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +212 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +22 -11
- package/data-types/temporal-types.d.ts +19 -0
- package/data-types/temporal-types.js +520 -0
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +2 -2
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/interfaces/data-mapping-options.d.ts +126 -4
- package/interfaces/database-connection-params.d.ts +32 -5
- package/interfaces/query-options.d.ts +32 -0
- package/interfaces/script-execute-options.d.ts +32 -0
- package/interfaces/transaction-options.d.ts +27 -0
- package/interfaces/transaction-options.js +1 -0
- package/package.json +1 -1
- package/protocol/database-error.d.ts +15 -0
- package/protocol/database-error.js +19 -0
- package/protocol/frontend.js +1 -1
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/connection-config.js +11 -1
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/decimal-as-string.d.ts +11 -0
- package/util/decimal-as-string.js +30 -0
- package/util/fetch-as-string.d.ts +33 -0
- package/util/fetch-as-string.js +122 -0
- package/util/get-parsers.js +18 -1
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.d.ts +5 -11
- package/util/resolve-column-formats.js +28 -24
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +2 -1
- package/util/stringify-for-sql.js +6 -5
- package/util/temporal.d.ts +47 -0
- package/util/temporal.js +61 -0
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +18 -8
- package/util/unspecified-param.js +22 -14
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DataTypeNames, DataTypeOIDs } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* The types that can decode to an exact decimal string: the two that
|
|
4
|
+
* carry a decimal the wire format states exactly, rather than a double's
|
|
5
|
+
* nearest approximation of one.
|
|
6
|
+
*/
|
|
7
|
+
export const DecimalAsStringOIDs = Object.freeze([
|
|
8
|
+
DataTypeOIDs.numeric,
|
|
9
|
+
DataTypeOIDs.money,
|
|
10
|
+
]);
|
|
11
|
+
/** Whether this type's values are wanted as their exact decimal string. */
|
|
12
|
+
export function wantsDecimalString(options, oid) {
|
|
13
|
+
const v = options?.decimalAsString;
|
|
14
|
+
if (v === undefined || v === false)
|
|
15
|
+
return false;
|
|
16
|
+
return v === true || v.includes(oid);
|
|
17
|
+
}
|
|
18
|
+
export function validateDecimalAsString(v) {
|
|
19
|
+
if (v === undefined || typeof v === 'boolean')
|
|
20
|
+
return;
|
|
21
|
+
if (!Array.isArray(v))
|
|
22
|
+
throw new TypeError('`decimalAsString` is either a boolean or an array of OIDs, not ' +
|
|
23
|
+
typeof v);
|
|
24
|
+
const l = v.length;
|
|
25
|
+
let i;
|
|
26
|
+
for (i = 0; i < l; i++)
|
|
27
|
+
if (!DecimalAsStringOIDs.includes(v[i]))
|
|
28
|
+
throw new TypeError('`decimalAsString` accepts only numeric (1700) and money (790). ' +
|
|
29
|
+
`Received ${v[i]}${DataTypeNames[v[i]] ? ' (' + DataTypeNames[v[i]] + ')' : ''}.`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DataTypeMap } from '../data-type-map.js';
|
|
2
|
+
import type { FetchAsStringItem } from '../interfaces/data-mapping-options.js';
|
|
3
|
+
import type { Maybe, OID } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* What `fetchAsString`'s entries mean, in one place - three call sites
|
|
6
|
+
* read the list and they have to agree: the Bind's result format codes
|
|
7
|
+
* (resolve-column-formats.ts), the parser chosen for each column
|
|
8
|
+
* (get-parsers.ts), and whether a cached set of parsers was built for
|
|
9
|
+
* the same ask (fetchAsStringEqual, below).
|
|
10
|
+
*/
|
|
11
|
+
/** Whether the list names this OID itself, in either form. */
|
|
12
|
+
export declare function fetchAsStringNamesOid(list: Maybe<FetchAsStringItem[]>, oid: OID): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the list names the element type of an array column, and lets
|
|
15
|
+
* that reach the array - `{ oid, arrays: false }` is how a caller asks
|
|
16
|
+
* for a scalar column as text and leaves its array columns decoding.
|
|
17
|
+
*
|
|
18
|
+
* `numeric` is why: `pg` hands back a scalar `numeric` as a string and a
|
|
19
|
+
* `numeric[]` as numbers, so reproducing it needs the two said
|
|
20
|
+
* separately.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetchAsStringNamesElement(list: Maybe<FetchAsStringItem[]>, elementsOID: OID): boolean;
|
|
23
|
+
/** Whether two lists select the same columns the same way. */
|
|
24
|
+
export declare function fetchAsStringEqual(a: Maybe<FetchAsStringItem[]>, b: Maybe<FetchAsStringItem[]>): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Refuses a selector that asks for something this does not do, rather
|
|
27
|
+
* than quietly not doing it.
|
|
28
|
+
*
|
|
29
|
+
* Checked once per list object, which is what a connection-level default
|
|
30
|
+
* or a module-level constant is; an inline list is a walk of a handful
|
|
31
|
+
* of entries.
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateFetchAsString(list: Maybe<FetchAsStringItem[]>, typeMap?: Maybe<DataTypeMap>): void;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DataTypeNames } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* What `fetchAsString`'s entries mean, in one place - three call sites
|
|
4
|
+
* read the list and they have to agree: the Bind's result format codes
|
|
5
|
+
* (resolve-column-formats.ts), the parser chosen for each column
|
|
6
|
+
* (get-parsers.ts), and whether a cached set of parsers was built for
|
|
7
|
+
* the same ask (fetchAsStringEqual, below).
|
|
8
|
+
*/
|
|
9
|
+
/** Whether the list names this OID itself, in either form. */
|
|
10
|
+
export function fetchAsStringNamesOid(list, oid) {
|
|
11
|
+
if (!list)
|
|
12
|
+
return false;
|
|
13
|
+
const l = list.length;
|
|
14
|
+
let i;
|
|
15
|
+
let item;
|
|
16
|
+
for (i = 0; i < l; i++) {
|
|
17
|
+
item = list[i];
|
|
18
|
+
if (typeof item === 'number' ? item === oid : item.oid === oid)
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether the list names the element type of an array column, and lets
|
|
25
|
+
* that reach the array - `{ oid, arrays: false }` is how a caller asks
|
|
26
|
+
* for a scalar column as text and leaves its array columns decoding.
|
|
27
|
+
*
|
|
28
|
+
* `numeric` is why: `pg` hands back a scalar `numeric` as a string and a
|
|
29
|
+
* `numeric[]` as numbers, so reproducing it needs the two said
|
|
30
|
+
* separately.
|
|
31
|
+
*/
|
|
32
|
+
export function fetchAsStringNamesElement(list, elementsOID) {
|
|
33
|
+
if (!list)
|
|
34
|
+
return false;
|
|
35
|
+
const l = list.length;
|
|
36
|
+
let i;
|
|
37
|
+
let item;
|
|
38
|
+
for (i = 0; i < l; i++) {
|
|
39
|
+
item = list[i];
|
|
40
|
+
if (typeof item === 'number') {
|
|
41
|
+
if (item === elementsOID)
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
else if (item.oid === elementsOID && item.arrays !== false)
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/** Whether two lists select the same columns the same way. */
|
|
50
|
+
export function fetchAsStringEqual(a, b) {
|
|
51
|
+
if (a === b)
|
|
52
|
+
return true;
|
|
53
|
+
if (!a || !b)
|
|
54
|
+
return !a?.length && !b?.length;
|
|
55
|
+
const l = a.length;
|
|
56
|
+
if (l !== b.length)
|
|
57
|
+
return false;
|
|
58
|
+
let i;
|
|
59
|
+
let x;
|
|
60
|
+
let y;
|
|
61
|
+
for (i = 0; i < l; i++) {
|
|
62
|
+
x = a[i];
|
|
63
|
+
y = b[i];
|
|
64
|
+
if (x === y)
|
|
65
|
+
continue;
|
|
66
|
+
// A bare OID and a selector for the same OID are only the same ask
|
|
67
|
+
// while the selector says nothing; anything else decodes differently
|
|
68
|
+
// and must not be handed the other one's cached parsers.
|
|
69
|
+
if (typeof x === 'number' || typeof y === 'number') {
|
|
70
|
+
const num = typeof x === 'number' ? x : y;
|
|
71
|
+
const sel = (typeof x === 'number' ? y : x);
|
|
72
|
+
if (sel.oid !== num || sel.arrays === false || sel.ranges)
|
|
73
|
+
return false;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (x.oid !== y.oid || (x.arrays === false) !== (y.arrays === false))
|
|
77
|
+
return false;
|
|
78
|
+
if (!!x.ranges !== !!y.ranges)
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const _checked = new WeakSet();
|
|
84
|
+
/**
|
|
85
|
+
* Refuses a selector that asks for something this does not do, rather
|
|
86
|
+
* than quietly not doing it.
|
|
87
|
+
*
|
|
88
|
+
* Checked once per list object, which is what a connection-level default
|
|
89
|
+
* or a module-level constant is; an inline list is a walk of a handful
|
|
90
|
+
* of entries.
|
|
91
|
+
*/
|
|
92
|
+
export function validateFetchAsString(list, typeMap) {
|
|
93
|
+
if (!list || _checked.has(list))
|
|
94
|
+
return;
|
|
95
|
+
const l = list.length;
|
|
96
|
+
let i;
|
|
97
|
+
let item;
|
|
98
|
+
for (i = 0; i < l; i++) {
|
|
99
|
+
item = list[i];
|
|
100
|
+
if (typeof item === 'number')
|
|
101
|
+
continue;
|
|
102
|
+
if (!item || typeof item.oid !== 'number')
|
|
103
|
+
throw new TypeError('`fetchAsString` takes an OID or `{ oid, arrays }`; received ' +
|
|
104
|
+
JSON.stringify(item));
|
|
105
|
+
if (item.ranges)
|
|
106
|
+
throw new TypeError('`fetchAsString` does not reach range columns through their ' +
|
|
107
|
+
"element's OID yet, so `ranges: true` would ask for something " +
|
|
108
|
+
'that will not happen. Name the range type itself instead - ' +
|
|
109
|
+
'`numrange` rather than `numeric`.');
|
|
110
|
+
// The flags say what happens to the *arrays of* the named type, so
|
|
111
|
+
// naming an array type and then talking about its arrays is not an
|
|
112
|
+
// ask anything can honour.
|
|
113
|
+
if (typeMap?.get(item.oid)?.elementsOID)
|
|
114
|
+
throw new TypeError(`\`fetchAsString\` selector ${name(item.oid)} is an array type, and ` +
|
|
115
|
+
'the flags describe what happens to a type’s array columns. ' +
|
|
116
|
+
'Name the element type instead, or list the array OID on its own.');
|
|
117
|
+
}
|
|
118
|
+
_checked.add(list);
|
|
119
|
+
}
|
|
120
|
+
function name(oid) {
|
|
121
|
+
return DataTypeNames[oid] ? `${oid} (${DataTypeNames[oid]})` : String(oid);
|
|
122
|
+
}
|
package/util/get-parsers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DataFormat } from '../constants.js';
|
|
2
2
|
import { decodeBinaryArray } from './decode-binaryarray.js';
|
|
3
|
+
import { fetchAsStringNamesElement, fetchAsStringNamesOid, } from './fetch-as-string.js';
|
|
3
4
|
import { parsePostgresArray } from './parse-array.js';
|
|
4
5
|
const DefaultColumnParser = (data, offset, len) => data.subarray(offset, offset + len);
|
|
5
6
|
const DefaultTextColumnParser = (data, offset, len) => data.toString('utf8', offset, offset + len);
|
|
@@ -21,11 +22,27 @@ export function getParsers(typeMap, fields, mappingOptions) {
|
|
|
21
22
|
// then the registered decoder is the only thing that can read it.
|
|
22
23
|
if (asString &&
|
|
23
24
|
f.format === DataFormat.text &&
|
|
24
|
-
asString
|
|
25
|
+
fetchAsStringNamesOid(asString, f.dataTypeId)) {
|
|
25
26
|
parsers[i] = DefaultTextColumnParser;
|
|
26
27
|
continue;
|
|
27
28
|
}
|
|
28
29
|
const dataTypeReg = typeMap.get(f.dataTypeId);
|
|
30
|
+
// The other half of the same ask: the list named this column's
|
|
31
|
+
// *element* type, so the literal is split and the elements are handed
|
|
32
|
+
// back as the server wrote them - an array of what naming the scalar
|
|
33
|
+
// OID gives for a scalar column. Naming the array's own OID still
|
|
34
|
+
// means the whole literal, above; the two remain tellable apart
|
|
35
|
+
// because they name different OIDs.
|
|
36
|
+
if (asString &&
|
|
37
|
+
f.format === DataFormat.text &&
|
|
38
|
+
dataTypeReg?.elementsOID &&
|
|
39
|
+
fetchAsStringNamesElement(asString, dataTypeReg.elementsOID)) {
|
|
40
|
+
const separator = dataTypeReg.arraySeparator;
|
|
41
|
+
parsers[i] = (data, offset, len) => parsePostgresArray(data.toString('utf8', offset, offset + len), {
|
|
42
|
+
separator,
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
29
46
|
if (dataTypeReg) {
|
|
30
47
|
const isArray = !!dataTypeReg.elementsOID;
|
|
31
48
|
if (f.format === DataFormat.binary) {
|
package/util/parse-datetime.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
1
|
+
import { type PgDateStyle } from './date-style.js';
|
|
2
|
+
export declare function parseDate(str: string, utc?: boolean, dateStyle?: PgDateStyle): Date | number;
|
|
3
|
+
export declare function parseDateTime(str: string, utc?: boolean, dateStyle?: PgDateStyle): Date | number;
|
|
4
|
+
export declare function parseDateTimeTz(str: string, utc?: boolean, dateStyle?: PgDateStyle): Date | number;
|
package/util/parse-datetime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// noinspection RegExpUnnecessaryNonCapturingGroup
|
|
2
|
+
import { parseStyledDateTime } from './date-style.js';
|
|
2
3
|
import { fastParseInt } from './fast-parseint.js';
|
|
3
4
|
import { parsePgTimestampString } from './parse-pg-timestamp.js';
|
|
4
5
|
// noinspection RegExpUnnecessaryNonCapturingGroup
|
|
@@ -21,7 +22,12 @@ function fillTimeArgs(m, args) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
|
-
export function parseDate(str, utc) {
|
|
25
|
+
export function parseDate(str, utc, dateStyle) {
|
|
26
|
+
if (dateStyle) {
|
|
27
|
+
const styled = parseStyledDateTime(str, dateStyle, utc);
|
|
28
|
+
if (styled)
|
|
29
|
+
return styled;
|
|
30
|
+
}
|
|
25
31
|
const m = str.match(TIMESTAMP_PATTERN);
|
|
26
32
|
if (!m)
|
|
27
33
|
return noMatch(str);
|
|
@@ -40,7 +46,12 @@ export function parseDate(str, utc) {
|
|
|
40
46
|
return new Date(Date.UTC(...args));
|
|
41
47
|
return new Date(...args);
|
|
42
48
|
}
|
|
43
|
-
export function parseDateTime(str, utc) {
|
|
49
|
+
export function parseDateTime(str, utc, dateStyle) {
|
|
50
|
+
if (dateStyle) {
|
|
51
|
+
const styled = parseStyledDateTime(str, dateStyle, utc);
|
|
52
|
+
if (styled)
|
|
53
|
+
return styled;
|
|
54
|
+
}
|
|
44
55
|
if (!utc) {
|
|
45
56
|
if (str === 'infinity')
|
|
46
57
|
return Infinity;
|
|
@@ -49,9 +60,14 @@ export function parseDateTime(str, utc) {
|
|
|
49
60
|
const fast = parsePgTimestampString(str);
|
|
50
61
|
if (fast)
|
|
51
62
|
return fast;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
// Only reached under the ISO DateStyle: `new Date()` reads
|
|
64
|
+
// `05.03.2024` as the third of May, and a session that renders dates
|
|
65
|
+
// that way has said so above.
|
|
66
|
+
if (!dateStyle) {
|
|
67
|
+
const d = new Date(str);
|
|
68
|
+
if (!isNaN(d.getTime()))
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
55
71
|
}
|
|
56
72
|
const m = str.match(TIMESTAMP_PATTERN);
|
|
57
73
|
if (!m)
|
|
@@ -72,7 +88,12 @@ export function parseDateTime(str, utc) {
|
|
|
72
88
|
return new Date(Date.UTC(...args));
|
|
73
89
|
return new Date(...args);
|
|
74
90
|
}
|
|
75
|
-
export function parseDateTimeTz(str, utc) {
|
|
91
|
+
export function parseDateTimeTz(str, utc, dateStyle) {
|
|
92
|
+
if (dateStyle) {
|
|
93
|
+
const styled = parseStyledDateTime(str, dateStyle, utc);
|
|
94
|
+
if (styled)
|
|
95
|
+
return styled;
|
|
96
|
+
}
|
|
76
97
|
if (str === 'infinity')
|
|
77
98
|
return Infinity;
|
|
78
99
|
if (str === '-infinity')
|
|
@@ -80,9 +101,13 @@ export function parseDateTimeTz(str, utc) {
|
|
|
80
101
|
const fast = parsePgTimestampString(str);
|
|
81
102
|
if (fast)
|
|
82
103
|
return fast;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
// See parseDateTime(): the engine's guess is only trusted where the
|
|
105
|
+
// session renders ISO.
|
|
106
|
+
if (!dateStyle) {
|
|
107
|
+
const native = new Date(str);
|
|
108
|
+
if (!isNaN(native.getTime()))
|
|
109
|
+
return native;
|
|
110
|
+
}
|
|
86
111
|
const m = str.match(TIMESTAMP_PATTERN);
|
|
87
112
|
if (!m)
|
|
88
113
|
return noMatch(str);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DataTypeMap } from '../data-type-map.js';
|
|
2
2
|
import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
|
|
3
3
|
import { Protocol } from '../protocol/protocol.js';
|
|
4
|
-
import type { Maybe
|
|
4
|
+
import type { Maybe } from '../types.js';
|
|
5
5
|
export interface ColumnFormatOptions extends DataMappingOptions {
|
|
6
6
|
columnFormat?: Protocol.DataFormat | Protocol.DataFormat[];
|
|
7
7
|
}
|
|
@@ -17,18 +17,12 @@ export interface ColumnFormatOptions extends DataMappingOptions {
|
|
|
17
17
|
* statement's, or a cached one's) pay nothing for this; callers that do
|
|
18
18
|
* not have to ask for the whole row as text instead - see queryOnce().
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* An array column is selected by its own array OID (`_timestamptz`) and
|
|
21
|
+
* by its element's (`timestamptz`) - both need the column as text, and
|
|
22
|
+
* which of the two was named is what get-parsers.ts reads to decide
|
|
23
|
+
* between the whole literal and the elements.
|
|
23
24
|
*
|
|
24
25
|
* Returns `columnFormat` untouched when there is nothing to do, so the
|
|
25
26
|
* common case allocates nothing and keeps sending a single format code.
|
|
26
27
|
*/
|
|
27
28
|
export declare function resolveColumnFormats(fields: Maybe<Protocol.RowDescription[]>, options: ColumnFormatOptions, typeMap?: DataTypeMap): Protocol.DataFormat | Protocol.DataFormat[];
|
|
28
|
-
/**
|
|
29
|
-
* Whether two `fetchAsString` lists select the same columns. Used to tell
|
|
30
|
-
* a cached set of parsers apart from one built for a different list, which
|
|
31
|
-
* `columnFormat` alone cannot do: an explicitly text column and one turned
|
|
32
|
-
* text by fetchAsString carry the same format code but decode differently.
|
|
33
|
-
*/
|
|
34
|
-
export declare function fetchAsStringEqual(a: Maybe<OID[]>, b: Maybe<OID[]>): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
|
|
2
2
|
import { Protocol } from '../protocol/protocol.js';
|
|
3
|
+
import { fetchAsStringNamesElement, fetchAsStringNamesOid, } from './fetch-as-string.js';
|
|
3
4
|
const DataFormat = Protocol.DataFormat;
|
|
4
5
|
/**
|
|
5
6
|
* Whether this client could read the column if it arrived in `format`.
|
|
@@ -13,6 +14,18 @@ function canDecode(typeMap, oid, format) {
|
|
|
13
14
|
const reg = typeMap.get(oid);
|
|
14
15
|
return !!reg && typeof reg.decodeBinary === 'function';
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether `asString` names the element type of the array type `oid` -
|
|
19
|
+
* "give me this type verbatim", asked of a column of them.
|
|
20
|
+
*
|
|
21
|
+
* Needs the type map to find the element OID, which the caller already
|
|
22
|
+
* hands over for `unknownTypesAsString`; without one this answers no and
|
|
23
|
+
* only an array's own OID selects it, as it always did.
|
|
24
|
+
*/
|
|
25
|
+
function namesElementOf(typeMap, asString, oid) {
|
|
26
|
+
const elementsOID = typeMap?.get(oid)?.elementsOID;
|
|
27
|
+
return !!elementsOID && fetchAsStringNamesElement(asString, elementsOID);
|
|
28
|
+
}
|
|
16
29
|
/**
|
|
17
30
|
* Turns `fetchAsString`'s OID list into the positional result format codes
|
|
18
31
|
* a Bind message actually carries.
|
|
@@ -25,9 +38,10 @@ function canDecode(typeMap, oid, format) {
|
|
|
25
38
|
* statement's, or a cached one's) pay nothing for this; callers that do
|
|
26
39
|
* not have to ask for the whole row as text instead - see queryOnce().
|
|
27
40
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
41
|
+
* An array column is selected by its own array OID (`_timestamptz`) and
|
|
42
|
+
* by its element's (`timestamptz`) - both need the column as text, and
|
|
43
|
+
* which of the two was named is what get-parsers.ts reads to decide
|
|
44
|
+
* between the whole literal and the elements.
|
|
31
45
|
*
|
|
32
46
|
* Returns `columnFormat` untouched when there is nothing to do, so the
|
|
33
47
|
* common case allocates nothing and keeps sending a single format code.
|
|
@@ -42,6 +56,10 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
42
56
|
const named = !!asString && asString.length > 0;
|
|
43
57
|
if (!fields || (!named && !unknownAsString))
|
|
44
58
|
return base;
|
|
59
|
+
// Both options do one thing: ask for a column as text. A row that is
|
|
60
|
+
// already all text has nothing left for them to ask.
|
|
61
|
+
if (base === DataFormat.text)
|
|
62
|
+
return base;
|
|
45
63
|
const l = fields.length;
|
|
46
64
|
const baseIsArray = Array.isArray(base);
|
|
47
65
|
const out = new Array(l);
|
|
@@ -54,10 +72,16 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
54
72
|
format = baseIsArray
|
|
55
73
|
? (base[i] ?? DEFAULT_COLUMN_FORMAT)
|
|
56
74
|
: base;
|
|
57
|
-
if (named &&
|
|
75
|
+
if (named &&
|
|
76
|
+
(fetchAsStringNamesOid(asString, oid) ||
|
|
77
|
+
namesElementOf(typeMap, asString, oid))) {
|
|
58
78
|
// fetchAsString names the value the caller wants verbatim, so it
|
|
59
79
|
// overrides an explicit columnFormat for its own columns - the two
|
|
60
80
|
// cannot both be honored and the OID list is the more specific ask.
|
|
81
|
+
// An array column is selected either way: by its own OID, which
|
|
82
|
+
// asks for the whole literal, or by its element's, which asks for
|
|
83
|
+
// the elements - both need the column as text, and get-parsers.ts
|
|
84
|
+
// is where the two part company.
|
|
61
85
|
format = DataFormat.text;
|
|
62
86
|
hit = true;
|
|
63
87
|
}
|
|
@@ -72,23 +96,3 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
72
96
|
}
|
|
73
97
|
return hit ? out : base;
|
|
74
98
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Whether two `fetchAsString` lists select the same columns. Used to tell
|
|
77
|
-
* a cached set of parsers apart from one built for a different list, which
|
|
78
|
-
* `columnFormat` alone cannot do: an explicitly text column and one turned
|
|
79
|
-
* text by fetchAsString carry the same format code but decode differently.
|
|
80
|
-
*/
|
|
81
|
-
export function fetchAsStringEqual(a, b) {
|
|
82
|
-
if (a === b)
|
|
83
|
-
return true;
|
|
84
|
-
if (!a || !b)
|
|
85
|
-
return !a?.length && !b?.length;
|
|
86
|
-
const l = a.length;
|
|
87
|
-
if (l !== b.length)
|
|
88
|
-
return false;
|
|
89
|
-
let i;
|
|
90
|
-
for (i = 0; i < l; i++)
|
|
91
|
-
if (a[i] !== b[i])
|
|
92
|
-
return false;
|
|
93
|
-
return true;
|
|
94
|
-
}
|
package/util/row-decoder.d.ts
CHANGED
|
@@ -17,12 +17,28 @@ import type { AnyParseFunction } from '../types.js';
|
|
|
17
17
|
* every other row's bytes in it) alive until released.
|
|
18
18
|
*/
|
|
19
19
|
export declare abstract class RowDecoder {
|
|
20
|
+
/**
|
|
21
|
+
* What `QueryResult.rowType`/`Cursor.rowType` reports for rows this
|
|
22
|
+
* decoder produced - a promise to the caller about their shape, so it
|
|
23
|
+
* is the decoder's to make. `'custom'` here is the honest answer for
|
|
24
|
+
* a decoder nobody else knows anything about; the two built-ins name
|
|
25
|
+
* their own shape, and a subclass of one that still produces that
|
|
26
|
+
* shape inherits the right answer without doing anything.
|
|
27
|
+
*
|
|
28
|
+
* Set it when a subclass changes the shape - a decoder that hands
|
|
29
|
+
* back a lazy view rather than a plain array is not an `'array'`, and
|
|
30
|
+
* a caller that indexes it because the result said so would be
|
|
31
|
+
* reading the wrong thing.
|
|
32
|
+
*/
|
|
33
|
+
readonly rowType: 'array' | 'object' | 'custom';
|
|
20
34
|
abstract decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): any;
|
|
21
35
|
}
|
|
22
36
|
export declare class ArrayRowDecoder extends RowDecoder {
|
|
37
|
+
readonly rowType: "array";
|
|
23
38
|
decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions): any[];
|
|
24
39
|
}
|
|
25
40
|
export declare class ObjectRowDecoder extends RowDecoder {
|
|
41
|
+
readonly rowType: "object";
|
|
26
42
|
decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): object;
|
|
27
43
|
}
|
|
28
44
|
export declare const DEFAULT_ARRAY_ROW_DECODER: ArrayRowDecoder;
|
|
@@ -40,10 +56,14 @@ interface RowDecoderOptions {
|
|
|
40
56
|
*/
|
|
41
57
|
export declare function resolveRowDecoder(options: RowDecoderOptions): RowDecoder;
|
|
42
58
|
/**
|
|
43
|
-
* The `rowType` string surfaced on `QueryResult`/`Cursor.rowType`.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
59
|
+
* The `rowType` string surfaced on `QueryResult`/`Cursor.rowType`.
|
|
60
|
+
*
|
|
61
|
+
* Asked of the decoder rather than worked out from its class: an
|
|
62
|
+
* `instanceof` test answers `'array'` for anything derived from
|
|
63
|
+
* `ArrayRowDecoder`, including a subclass that overrode `decode()` to
|
|
64
|
+
* return something else entirely - which is the only reason to subclass
|
|
65
|
+
* it. The built-ins declare their own shape, so the answers are
|
|
66
|
+
* unchanged, and a subclass that changes the shape can now say so.
|
|
47
67
|
*/
|
|
48
68
|
export declare function resolveRowType(options: RowDecoderOptions): 'array' | 'object' | 'custom';
|
|
49
69
|
export {};
|
package/util/row-decoder.js
CHANGED
|
@@ -15,13 +15,29 @@ import { parseObjectRow, parseRow } from './parse-row.js';
|
|
|
15
15
|
* every other row's bytes in it) alive until released.
|
|
16
16
|
*/
|
|
17
17
|
export class RowDecoder {
|
|
18
|
+
/**
|
|
19
|
+
* What `QueryResult.rowType`/`Cursor.rowType` reports for rows this
|
|
20
|
+
* decoder produced - a promise to the caller about their shape, so it
|
|
21
|
+
* is the decoder's to make. `'custom'` here is the honest answer for
|
|
22
|
+
* a decoder nobody else knows anything about; the two built-ins name
|
|
23
|
+
* their own shape, and a subclass of one that still produces that
|
|
24
|
+
* shape inherits the right answer without doing anything.
|
|
25
|
+
*
|
|
26
|
+
* Set it when a subclass changes the shape - a decoder that hands
|
|
27
|
+
* back a lazy view rather than a plain array is not an `'array'`, and
|
|
28
|
+
* a caller that indexes it because the result said so would be
|
|
29
|
+
* reading the wrong thing.
|
|
30
|
+
*/
|
|
31
|
+
rowType = 'custom';
|
|
18
32
|
}
|
|
19
33
|
export class ArrayRowDecoder extends RowDecoder {
|
|
34
|
+
rowType = 'array';
|
|
20
35
|
decode(parsers, data, columnCount, options) {
|
|
21
36
|
return parseRow(parsers, data, columnCount, options);
|
|
22
37
|
}
|
|
23
38
|
}
|
|
24
39
|
export class ObjectRowDecoder extends RowDecoder {
|
|
40
|
+
rowType = 'object';
|
|
25
41
|
decode(parsers, data, columnCount, options, fields) {
|
|
26
42
|
return parseObjectRow(parsers, data, columnCount, options, fields);
|
|
27
43
|
}
|
|
@@ -48,16 +64,15 @@ export function resolveRowDecoder(options) {
|
|
|
48
64
|
: DEFAULT_ARRAY_ROW_DECODER;
|
|
49
65
|
}
|
|
50
66
|
/**
|
|
51
|
-
* The `rowType` string surfaced on `QueryResult`/`Cursor.rowType`.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
67
|
+
* The `rowType` string surfaced on `QueryResult`/`Cursor.rowType`.
|
|
68
|
+
*
|
|
69
|
+
* Asked of the decoder rather than worked out from its class: an
|
|
70
|
+
* `instanceof` test answers `'array'` for anything derived from
|
|
71
|
+
* `ArrayRowDecoder`, including a subclass that overrode `decode()` to
|
|
72
|
+
* return something else entirely - which is the only reason to subclass
|
|
73
|
+
* it. The built-ins declare their own shape, so the answers are
|
|
74
|
+
* unchanged, and a subclass that changes the shape can now say so.
|
|
55
75
|
*/
|
|
56
76
|
export function resolveRowType(options) {
|
|
57
|
-
|
|
58
|
-
if (decoder instanceof ObjectRowDecoder)
|
|
59
|
-
return 'object';
|
|
60
|
-
if (decoder instanceof ArrayRowDecoder)
|
|
61
|
-
return 'array';
|
|
62
|
-
return 'custom';
|
|
77
|
+
return resolveRowDecoder(options).rowType;
|
|
63
78
|
}
|
package/util/sql-tag.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BindParam } from '../connection/bind-param.js';
|
|
2
2
|
import { DataTypeMap, GlobalTypeMap } from '../data-type-map.js';
|
|
3
|
+
import { arrayLeaf } from './array-leaf.js';
|
|
3
4
|
import { escapeIdentifier } from './escape-identifier.js';
|
|
4
5
|
import { escapeLiteral } from './escape-literal.js';
|
|
5
6
|
import { formatDateParam } from './format-datetime.js';
|
|
6
7
|
import { stringifyArrayLiteral } from './stringify-arrayliteral.js';
|
|
7
|
-
import {
|
|
8
|
+
import { isUnspecifiedParam } from './unspecified-param.js';
|
|
8
9
|
/**
|
|
9
10
|
* A statement built by the `sql` tag: its text with the values pulled out.
|
|
10
11
|
*
|
|
@@ -14,11 +14,12 @@ export function stringifyArrayForSQL(v, options, encode) {
|
|
|
14
14
|
* registered `encodeText()` that writes exactly the literal PostgreSQL
|
|
15
15
|
* reads back, and `determine()` already knows which type each one is -
|
|
16
16
|
* it is what the same value is sent as when it is bound as a parameter.
|
|
17
|
-
* Without asking, all of them became `::json
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
21
|
-
* ISO string and a Buffer leaked its
|
|
17
|
+
* Without asking, all of them became `::json`: `'{"x":1,"y":2}'::json`
|
|
18
|
+
* where `'(1,2)'::point` was meant - a JSON object that casts to no
|
|
19
|
+
* geometric type at all, and which back when these classes had a
|
|
20
|
+
* `toJSON()` of their own was not even that, but a JSON string holding
|
|
21
|
+
* the literal. A Date became its ISO string and a Buffer leaked its
|
|
22
|
+
* bytes as `{"type":"Buffer",...}`.
|
|
22
23
|
*/
|
|
23
24
|
function stringifyObjectForSQL(v, options) {
|
|
24
25
|
// determine() throws for a class that must carry a type OID and does
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reaching the `Temporal` the application provides, without depending on
|
|
3
|
+
* one.
|
|
4
|
+
*
|
|
5
|
+
* No runtime ships Temporal yet - Node 24.15 and Bun 1.3.10 both answer
|
|
6
|
+
* `undefined`, with or without `--harmony` - so today every caller that
|
|
7
|
+
* turns `temporalTypes` on has installed a polyfill. That is why this is
|
|
8
|
+
* read off `globalThis` when it is needed rather than imported: this
|
|
9
|
+
* package takes no dependency, a polyfill loaded after this module is
|
|
10
|
+
* still seen, and the day a runtime ships Temporal nothing here changes.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The parts of `Temporal` this client calls. Deliberately untyped past
|
|
14
|
+
* the names: the shape cannot be checked against a global that may not
|
|
15
|
+
* exist, so the presence check below is the check.
|
|
16
|
+
*/
|
|
17
|
+
export interface TemporalApi {
|
|
18
|
+
Instant: any;
|
|
19
|
+
PlainDate: any;
|
|
20
|
+
PlainDateTime: any;
|
|
21
|
+
PlainTime: any;
|
|
22
|
+
Duration: any;
|
|
23
|
+
ZonedDateTime: any;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The runtime's `Temporal`, or an error saying how to get one.
|
|
27
|
+
*
|
|
28
|
+
* Called when a type map is built - which for a connection is while it
|
|
29
|
+
* is being constructed - so a missing Temporal is reported before any
|
|
30
|
+
* I/O rather than in the middle of decoding a row.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getTemporal(): TemporalApi;
|
|
33
|
+
/**
|
|
34
|
+
* The zone a `timestamptz` is printed in when the caller named none.
|
|
35
|
+
*
|
|
36
|
+
* Only the printing: a timestamptz is an absolute instant and arrives as
|
|
37
|
+
* one, so the zone decides what wall clock the ZonedDateTime shows and
|
|
38
|
+
* nothing about which moment it is.
|
|
39
|
+
*/
|
|
40
|
+
export declare function systemTimeZone(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Checks a zone name before it is handed to Temporal, so the complaint
|
|
43
|
+
* names the setting it came from instead of surfacing from inside a
|
|
44
|
+
* decode. `Intl` accepts exactly what Temporal does and is here whether
|
|
45
|
+
* Temporal is or not.
|
|
46
|
+
*/
|
|
47
|
+
export declare function requireTimeZone(zone: string, where: string): string;
|