postgrejs 3.9.0 → 3.10.1
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 +32 -1
- package/connection/connection.js +6 -2
- package/connection/intl-connection.d.ts +18 -1
- package/connection/intl-connection.js +104 -11
- 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/interval-type.js +1 -1
- package/data-types/money-type.js +11 -2
- package/data-types/numeric-type.js +21 -10
- package/data-types/temporal-types.d.ts +19 -0
- package/data-types/temporal-types.js +520 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/interfaces/data-mapping-options.d.ts +99 -4
- package/interfaces/database-connection-params.d.ts +10 -0
- package/package.json +1 -1
- package/util/connection-config.js +65 -1
- 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/resolve-column-formats.d.ts +5 -11
- package/util/resolve-column-formats.js +24 -24
- package/util/stringify-for-sql.js +6 -5
- package/util/temporal.d.ts +47 -0
- package/util/temporal.js +61 -0
- package/util/unspecified-param.d.ts +18 -3
- package/util/unspecified-param.js +21 -4
package/package.json
CHANGED
|
@@ -1,12 +1,66 @@
|
|
|
1
1
|
import { merge } from '@jsopen/objects';
|
|
2
2
|
import { configFromEnv } from './config-from-env.js';
|
|
3
|
+
/**
|
|
4
|
+
* Spellings from other clients that mean something here under a
|
|
5
|
+
* different name.
|
|
6
|
+
*
|
|
7
|
+
* Nothing checks that a configuration's keys are keys this client knows,
|
|
8
|
+
* and it deliberately stays that way: a `PoolConfiguration` carries
|
|
9
|
+
* `lightning-pool`'s own options, which are not declared here, and
|
|
10
|
+
* callers pass configurations that carry their own fields. An allowlist
|
|
11
|
+
* would reject those, and would have to be kept current forever.
|
|
12
|
+
*
|
|
13
|
+
* What is caught instead is the short list of names that are *wrong* -
|
|
14
|
+
* each is the right idea under another client's spelling, so it can be
|
|
15
|
+
* answered with the name that works rather than merely refused.
|
|
16
|
+
*/
|
|
17
|
+
const FOREIGN_KEYS = {
|
|
18
|
+
connection_string: 'connectionString',
|
|
19
|
+
connectionUri: 'connectionString',
|
|
20
|
+
uri: 'connectionString',
|
|
21
|
+
url: 'connectionString',
|
|
22
|
+
dbname: 'database',
|
|
23
|
+
db: 'database',
|
|
24
|
+
username: 'user',
|
|
25
|
+
pass: 'password',
|
|
26
|
+
passwd: 'password',
|
|
27
|
+
hostname: 'host',
|
|
28
|
+
application_name: 'applicationName',
|
|
29
|
+
connect_timeout: 'connectTimeoutMs',
|
|
30
|
+
connectionTimeoutMillis: 'connectTimeoutMs',
|
|
31
|
+
};
|
|
32
|
+
function checkForeignKeys(config) {
|
|
33
|
+
let k;
|
|
34
|
+
for (k in config) {
|
|
35
|
+
const meant = FOREIGN_KEYS[k];
|
|
36
|
+
if (meant && config[k] !== undefined)
|
|
37
|
+
throw new TypeError(`"${k}" is not a connection option here - did you mean "${meant}"? ` +
|
|
38
|
+
'An option this client does not know is otherwise ignored, so a ' +
|
|
39
|
+
'name that is nearly right does nothing and says nothing.');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
3
42
|
export function getConnectionConfig(config) {
|
|
4
43
|
const cfg = configFromEnv();
|
|
5
44
|
if (typeof config === 'string') {
|
|
6
45
|
merge(cfg, parseConnectionString(config));
|
|
7
46
|
}
|
|
8
47
|
else if (typeof config === 'object') {
|
|
48
|
+
checkForeignKeys(config);
|
|
9
49
|
merge(cfg, config);
|
|
50
|
+
// `pg`'s spelling of the same thing, and what `drizzle-orm`'s first
|
|
51
|
+
// example writes - so it is the single most likely key someone
|
|
52
|
+
// moving from `pg` puts here. Ignored, it did not fail: it connected
|
|
53
|
+
// to whatever the environment defaults to, which in development is
|
|
54
|
+
// usually a database that exists, so the mistake surfaced as missing
|
|
55
|
+
// tables or as writes landing somewhere else. Applied after the
|
|
56
|
+
// merge and not before it, because `pg` lets the string win over the
|
|
57
|
+
// fields beside it.
|
|
58
|
+
const cs = cfg.connectionString;
|
|
59
|
+
if (cs !== undefined) {
|
|
60
|
+
delete cfg.connectionString;
|
|
61
|
+
if (cs)
|
|
62
|
+
merge(cfg, parseConnectionString('' + cs));
|
|
63
|
+
}
|
|
10
64
|
}
|
|
11
65
|
if (cfg.host) {
|
|
12
66
|
const explicitRequireSSL = cfg.requireSSL;
|
|
@@ -61,6 +115,16 @@ function splitHosts(value, fallbackPort) {
|
|
|
61
115
|
return { host: entry, port: fallbackPort };
|
|
62
116
|
});
|
|
63
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* The schemes whose path is the database name.
|
|
120
|
+
*
|
|
121
|
+
* PostgreSQL gives two URI forms of its own, `postgres://` and
|
|
122
|
+
* `postgresql://`, and the longer one is what Prisma, TypeORM and most
|
|
123
|
+
* tutorials write - it was missing here, so a string naming a database
|
|
124
|
+
* connected to the default one instead, with nothing reported. `pg:`
|
|
125
|
+
* has been accepted for longer than either and stays.
|
|
126
|
+
*/
|
|
127
|
+
const DATABASE_PATH_SCHEMES = ['pg:', 'postgres:', 'postgresql:'];
|
|
64
128
|
export function parseConnectionString(str) {
|
|
65
129
|
if (str.startsWith('/'))
|
|
66
130
|
str = 'socket:/' + str;
|
|
@@ -89,7 +153,7 @@ export function parseConnectionString(str) {
|
|
|
89
153
|
if (parsed.searchParams.get('db'))
|
|
90
154
|
cfg.database = decodeURI(getFirst(parsed.searchParams.get('db')));
|
|
91
155
|
}
|
|
92
|
-
else if (
|
|
156
|
+
else if (DATABASE_PATH_SCHEMES.includes(parsed.protocol)) {
|
|
93
157
|
if (parsed.pathname)
|
|
94
158
|
cfg.database = decodeURI(parsed.pathname.substring(1));
|
|
95
159
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
|
|
2
|
+
import type { Maybe, OID } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The types that can decode to an exact decimal string: the two that
|
|
5
|
+
* carry a decimal the wire format states exactly, rather than a double's
|
|
6
|
+
* nearest approximation of one.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DecimalAsStringOIDs: readonly OID[];
|
|
9
|
+
/** Whether this type's values are wanted as their exact decimal string. */
|
|
10
|
+
export declare function wantsDecimalString(options: Maybe<DataMappingOptions>, oid: OID): boolean;
|
|
11
|
+
export declare function validateDecimalAsString(v: Maybe<boolean | OID[]>): void;
|
|
@@ -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) {
|
|
@@ -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.
|
|
@@ -58,10 +72,16 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
58
72
|
format = baseIsArray
|
|
59
73
|
? (base[i] ?? DEFAULT_COLUMN_FORMAT)
|
|
60
74
|
: base;
|
|
61
|
-
if (named &&
|
|
75
|
+
if (named &&
|
|
76
|
+
(fetchAsStringNamesOid(asString, oid) ||
|
|
77
|
+
namesElementOf(typeMap, asString, oid))) {
|
|
62
78
|
// fetchAsString names the value the caller wants verbatim, so it
|
|
63
79
|
// overrides an explicit columnFormat for its own columns - the two
|
|
64
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.
|
|
65
85
|
format = DataFormat.text;
|
|
66
86
|
hit = true;
|
|
67
87
|
}
|
|
@@ -76,23 +96,3 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
76
96
|
}
|
|
77
97
|
return hit ? out : base;
|
|
78
98
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Whether two `fetchAsString` lists select the same columns. Used to tell
|
|
81
|
-
* a cached set of parsers apart from one built for a different list, which
|
|
82
|
-
* `columnFormat` alone cannot do: an explicitly text column and one turned
|
|
83
|
-
* text by fetchAsString carry the same format code but decode differently.
|
|
84
|
-
*/
|
|
85
|
-
export function fetchAsStringEqual(a, b) {
|
|
86
|
-
if (a === b)
|
|
87
|
-
return true;
|
|
88
|
-
if (!a || !b)
|
|
89
|
-
return !a?.length && !b?.length;
|
|
90
|
-
const l = a.length;
|
|
91
|
-
if (l !== b.length)
|
|
92
|
-
return false;
|
|
93
|
-
let i;
|
|
94
|
-
for (i = 0; i < l; i++)
|
|
95
|
-
if (a[i] !== b[i])
|
|
96
|
-
return false;
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
@@ -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;
|
package/util/temporal.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
let _systemTimeZone;
|
|
13
|
+
const _knownZones = new Set();
|
|
14
|
+
/**
|
|
15
|
+
* The runtime's `Temporal`, or an error saying how to get one.
|
|
16
|
+
*
|
|
17
|
+
* Called when a type map is built - which for a connection is while it
|
|
18
|
+
* is being constructed - so a missing Temporal is reported before any
|
|
19
|
+
* I/O rather than in the middle of decoding a row.
|
|
20
|
+
*/
|
|
21
|
+
export function getTemporal() {
|
|
22
|
+
const t = globalThis.Temporal;
|
|
23
|
+
if (!t || !t.ZonedDateTime)
|
|
24
|
+
throw new Error('Temporal is not available in this runtime. postgrejs reads ' +
|
|
25
|
+
'globalThis.Temporal and takes no dependency of its own: install ' +
|
|
26
|
+
'`temporal-polyfill` and import it before connecting, or leave ' +
|
|
27
|
+
'`temporalTypes` off.');
|
|
28
|
+
return t;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The zone a `timestamptz` is printed in when the caller named none.
|
|
32
|
+
*
|
|
33
|
+
* Only the printing: a timestamptz is an absolute instant and arrives as
|
|
34
|
+
* one, so the zone decides what wall clock the ZonedDateTime shows and
|
|
35
|
+
* nothing about which moment it is.
|
|
36
|
+
*/
|
|
37
|
+
export function systemTimeZone() {
|
|
38
|
+
if (_systemTimeZone === undefined)
|
|
39
|
+
_systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
40
|
+
return _systemTimeZone;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Checks a zone name before it is handed to Temporal, so the complaint
|
|
44
|
+
* names the setting it came from instead of surfacing from inside a
|
|
45
|
+
* decode. `Intl` accepts exactly what Temporal does and is here whether
|
|
46
|
+
* Temporal is or not.
|
|
47
|
+
*/
|
|
48
|
+
export function requireTimeZone(zone, where) {
|
|
49
|
+
if (_knownZones.has(zone))
|
|
50
|
+
return zone;
|
|
51
|
+
try {
|
|
52
|
+
new Intl.DateTimeFormat('en-US', { timeZone: zone });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new Error(`"${zone}" (${where}) is not a time zone this runtime knows, so a ` +
|
|
56
|
+
'timestamptz cannot be given one. Pass `timeZone` in the mapping ' +
|
|
57
|
+
'options to name one yourself.');
|
|
58
|
+
}
|
|
59
|
+
_knownZones.add(zone);
|
|
60
|
+
return zone;
|
|
61
|
+
}
|
|
@@ -20,8 +20,23 @@
|
|
|
20
20
|
* as it does under `pg`. A cast (`$1::text`) or a named type
|
|
21
21
|
* (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* - An array of numbers is the same question, and the answer matters
|
|
24
|
+
* more: `[1, 2]` is `int2[]`, `int4[]`, `int8[]`, `numeric[]`,
|
|
25
|
+
* `float4[]` or `float8[]` depending on where it lands, and unlike
|
|
26
|
+
* their scalars those types have no operators or implicit casts
|
|
27
|
+
* between them. A declared `int4[]` is therefore not merely a guess
|
|
28
|
+
* but a wrong answer wherever the column is one of the other five:
|
|
29
|
+
* `array[1,2]::int8[] = $1` is `42883 operator does not exist:
|
|
30
|
+
* bigint[] = integer[]`, and `numeric[] = double precision[]` the
|
|
31
|
+
* same. The scalars are left declared because they do have those
|
|
32
|
+
* operators - `1::int8 = $1` and `1.5::numeric = $1` both resolve.
|
|
33
|
+
*
|
|
34
|
+
* A scalar number, a boolean, a Buffer, and an array of anything but
|
|
35
|
+
* numbers keep their declared types: an array of booleans, of Buffers
|
|
36
|
+
* or of one of this client's own classes has one type it can be, so
|
|
37
|
+
* naming it says nothing the server would have decided differently -
|
|
38
|
+
* and it keeps the binary encoding, which the text literal gives up.
|
|
39
|
+
* A caller who wants that back for a large numeric array names the type
|
|
40
|
+
* with `new BindParam(DataTypeOIDs._float8, v)`.
|
|
26
41
|
*/
|
|
27
42
|
export declare function isUnspecifiedParam(v: any): boolean;
|
|
@@ -21,11 +21,28 @@ import { arrayLeaf } from './array-leaf.js';
|
|
|
21
21
|
* as it does under `pg`. A cast (`$1::text`) or a named type
|
|
22
22
|
* (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* - An array of numbers is the same question, and the answer matters
|
|
25
|
+
* more: `[1, 2]` is `int2[]`, `int4[]`, `int8[]`, `numeric[]`,
|
|
26
|
+
* `float4[]` or `float8[]` depending on where it lands, and unlike
|
|
27
|
+
* their scalars those types have no operators or implicit casts
|
|
28
|
+
* between them. A declared `int4[]` is therefore not merely a guess
|
|
29
|
+
* but a wrong answer wherever the column is one of the other five:
|
|
30
|
+
* `array[1,2]::int8[] = $1` is `42883 operator does not exist:
|
|
31
|
+
* bigint[] = integer[]`, and `numeric[] = double precision[]` the
|
|
32
|
+
* same. The scalars are left declared because they do have those
|
|
33
|
+
* operators - `1::int8 = $1` and `1.5::numeric = $1` both resolve.
|
|
34
|
+
*
|
|
35
|
+
* A scalar number, a boolean, a Buffer, and an array of anything but
|
|
36
|
+
* numbers keep their declared types: an array of booleans, of Buffers
|
|
37
|
+
* or of one of this client's own classes has one type it can be, so
|
|
38
|
+
* naming it says nothing the server would have decided differently -
|
|
39
|
+
* and it keeps the binary encoding, which the text literal gives up.
|
|
40
|
+
* A caller who wants that back for a large numeric array names the type
|
|
41
|
+
* with `new BindParam(DataTypeOIDs._float8, v)`.
|
|
27
42
|
*/
|
|
28
43
|
export function isUnspecifiedParam(v) {
|
|
29
44
|
const x = arrayLeaf(v);
|
|
30
|
-
|
|
45
|
+
if (typeof x === 'string' || x instanceof Date)
|
|
46
|
+
return true;
|
|
47
|
+
return Array.isArray(v) && (typeof x === 'number' || typeof x === 'bigint');
|
|
31
48
|
}
|