postgrejs 3.6.1 → 3.8.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 +66 -6
- package/README.md +9 -83
- package/connection/connection.js +13 -1
- package/connection/intl-connection.d.ts +5 -3
- package/connection/intl-connection.js +70 -20
- package/connection/pool.js +6 -0
- package/connection/prepared-statement.d.ts +11 -0
- package/connection/prepared-statement.js +19 -5
- package/constants.d.ts +60 -0
- package/constants.js +119 -1
- package/data-type-map.js +82 -0
- package/data-types/bit-type.d.ts +5 -0
- package/data-types/bit-type.js +91 -0
- package/data-types/box-type.js +9 -19
- package/data-types/circle-type.js +6 -16
- package/data-types/classes/geometric.d.ts +105 -0
- package/data-types/classes/geometric.js +197 -0
- package/data-types/classes/interval.d.ts +73 -0
- package/data-types/classes/interval.js +181 -0
- package/data-types/classes/numeric.d.ts +41 -0
- package/data-types/classes/numeric.js +51 -0
- package/data-types/classes/range.d.ts +74 -0
- package/data-types/classes/range.js +111 -0
- package/data-types/classes/type-oid.d.ts +34 -0
- package/data-types/classes/type-oid.js +45 -0
- package/data-types/inet-type.d.ts +5 -0
- package/data-types/inet-type.js +286 -0
- package/data-types/int2-vector-type.js +4 -1
- package/data-types/interval-type.d.ts +5 -0
- package/data-types/interval-type.js +79 -0
- package/data-types/jsonpath-type.d.ts +3 -0
- package/data-types/jsonpath-type.js +53 -0
- package/data-types/line-type.d.ts +3 -0
- package/data-types/line-type.js +41 -0
- package/data-types/lseg-type.js +9 -19
- package/data-types/macaddr-type.d.ts +5 -0
- package/data-types/macaddr-type.js +86 -0
- package/data-types/numeric-type.js +98 -69
- package/data-types/oid-vector-type.js +4 -1
- package/data-types/path-type.d.ts +5 -0
- package/data-types/path-type.js +139 -0
- package/data-types/pg-lsn-type.d.ts +3 -0
- package/data-types/pg-lsn-type.js +58 -0
- package/data-types/point-type.js +8 -13
- package/data-types/range-type.d.ts +23 -0
- package/data-types/range-type.js +342 -0
- package/data-types/snapshot-type.d.ts +5 -0
- package/data-types/snapshot-type.js +96 -0
- package/data-types/tid-type.d.ts +3 -0
- package/data-types/tid-type.js +50 -0
- package/data-types/timestamptz-type.js +9 -0
- package/data-types/timetz-type.d.ts +3 -0
- package/data-types/timetz-type.js +184 -0
- package/data-types/tsvector-type.d.ts +5 -0
- package/data-types/tsvector-type.js +216 -0
- package/data-types/xid-type.d.ts +7 -0
- package/data-types/xid-type.js +110 -0
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/interfaces/data-mapping-options.d.ts +26 -0
- package/interfaces/data-type.d.ts +8 -9
- package/package.json +1 -1
- package/protocol/connection-lost-error.d.ts +5 -3
- package/protocol/connection-lost-error.js +5 -3
- package/protocol/frontend.js +17 -0
- package/protocol/pg-socket.js +16 -2
- package/util/decode-binaryarray.js +8 -1
- package/util/encode-binaryarray.d.ts +12 -1
- package/util/encode-binaryarray.js +13 -2
- package/util/format-datetime.d.ts +18 -0
- package/util/format-datetime.js +33 -0
- package/util/parse-array.js +17 -2
- package/util/resolve-column-formats.d.ts +2 -1
- package/util/resolve-column-formats.js +35 -8
- package/util/sql-tag.js +15 -0
- package/util/stringify-for-sql.js +44 -1
- package/util/unspecified-param.d.ts +32 -0
- package/util/unspecified-param.js +40 -0
package/protocol/pg-socket.js
CHANGED
|
@@ -644,7 +644,13 @@ export class PgSocket extends SafeEventEmitter {
|
|
|
644
644
|
const reason = this._state === ConnectionState.CLOSING
|
|
645
645
|
? undefined
|
|
646
646
|
: this._buildCloseReason();
|
|
647
|
-
|
|
647
|
+
// Whatever was in flight is rejected with the same object the 'close'
|
|
648
|
+
// event carries, so `err.code === '08006'` works in a caller's catch
|
|
649
|
+
// exactly as it does in a Pool listener - the generic message it used
|
|
650
|
+
// to get named no cause and carried no code at all. A close that was
|
|
651
|
+
// asked for keeps that message: nothing went wrong, the caller simply
|
|
652
|
+
// raced the shutdown.
|
|
653
|
+
this._failPendingCaptures(reason ?? new Error('Connection closed'));
|
|
648
654
|
this._reset();
|
|
649
655
|
this._socket = undefined;
|
|
650
656
|
this._state = ConnectionState.CLOSED;
|
|
@@ -658,7 +664,15 @@ export class PgSocket extends SafeEventEmitter {
|
|
|
658
664
|
// here and the fact of closing separately, and only the pair together
|
|
659
665
|
// says what actually happened.
|
|
660
666
|
this._closeReason = err instanceof Error ? err : new Error(String(err));
|
|
661
|
-
|
|
667
|
+
// An error on a live connection is that connection going away - Node
|
|
668
|
+
// always follows 'error' with 'close' - so the caller gets the same
|
|
669
|
+
// ConnectionLostError the close will carry, with this as its cause,
|
|
670
|
+
// rather than a raw ECONNRESET whose `code` is a Node errno where a
|
|
671
|
+
// SQLSTATE is expected. Before READY there is no connection to have
|
|
672
|
+
// lost, and the error itself is the useful thing.
|
|
673
|
+
this._failPendingCaptures(this._state === ConnectionState.READY
|
|
674
|
+
? this._buildCloseReason()
|
|
675
|
+
: this._closeReason);
|
|
662
676
|
if (this._state !== ConnectionState.READY) {
|
|
663
677
|
this._socket?.end();
|
|
664
678
|
}
|
|
@@ -38,7 +38,14 @@ export function decodeBinaryArray(buf, offset, decoder, options = {}) {
|
|
|
38
38
|
};
|
|
39
39
|
for (let d = 0; d < ndims; d++) {
|
|
40
40
|
dims[d] = io.readInt32BE();
|
|
41
|
-
|
|
41
|
+
// The lower bound is read and dropped, deliberately. A server-side
|
|
42
|
+
// array really can have a non-default one, and a JavaScript array is
|
|
43
|
+
// always 0-based, so carrying it would mean deciding what a decoded
|
|
44
|
+
// array is supposed to represent - a wider change than the encoder's.
|
|
45
|
+
// Note that this is also what hid the encoder writing 0: a value
|
|
46
|
+
// written and read back through this client looked right, and only
|
|
47
|
+
// SQL against the stored value saw the difference.
|
|
48
|
+
io.readInt32BE();
|
|
42
49
|
}
|
|
43
50
|
return readDim(0);
|
|
44
51
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
|
|
2
2
|
import type { SmartBuffer } from '../protocol/smart-buffer.js';
|
|
3
3
|
import type { EncodeBinaryFunction, EncodeCalculateDimFunction, OID } from '../types.js';
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Writes an array in the binary wire format.
|
|
6
|
+
*
|
|
7
|
+
* `lowerBound` is the index the first element answers to in SQL, and it
|
|
8
|
+
* is 1 for every ordinary array - `array_lower('{1,2}'::int4[], 1)` is
|
|
9
|
+
* 1, and a value written with 0 comes back subscripted from 0, so
|
|
10
|
+
* `arr[1]` is its *second* element and `::text` renders the
|
|
11
|
+
* explicit-bounds form `[0:2]={…}`. `oidvector` and `int2vector` are the
|
|
12
|
+
* exception and really are 0-based - confirmed against the catalog,
|
|
13
|
+
* `array_lower(pg_index.indkey, 1)` is 0 - so they pass 0 themselves.
|
|
14
|
+
*/
|
|
15
|
+
export declare function encodeBinaryArray(io: SmartBuffer, value: any[], itemOid: OID, options: DataMappingOptions, encode: EncodeBinaryFunction, encodeCalculateDimFn?: EncodeCalculateDimFunction, lowerBound?: number): void;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
2
|
import { arrayCalculateDim } from './array-calculatedim.js';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Writes an array in the binary wire format.
|
|
5
|
+
*
|
|
6
|
+
* `lowerBound` is the index the first element answers to in SQL, and it
|
|
7
|
+
* is 1 for every ordinary array - `array_lower('{1,2}'::int4[], 1)` is
|
|
8
|
+
* 1, and a value written with 0 comes back subscripted from 0, so
|
|
9
|
+
* `arr[1]` is its *second* element and `::text` renders the
|
|
10
|
+
* explicit-bounds form `[0:2]={…}`. `oidvector` and `int2vector` are the
|
|
11
|
+
* exception and really are 0-based - confirmed against the catalog,
|
|
12
|
+
* `array_lower(pg_index.indkey, 1)` is 0 - so they pass 0 themselves.
|
|
13
|
+
*/
|
|
14
|
+
export function encodeBinaryArray(io, value, itemOid, options, encode, encodeCalculateDimFn, lowerBound = 1) {
|
|
4
15
|
encodeCalculateDimFn = encodeCalculateDimFn || arrayCalculateDim;
|
|
5
16
|
itemOid = itemOid || DataTypeOIDs.varchar;
|
|
6
17
|
const dim = encodeCalculateDimFn(value);
|
|
@@ -12,7 +23,7 @@ export function encodeBinaryArray(io, value, itemOid, options, encode, encodeCal
|
|
|
12
23
|
let d;
|
|
13
24
|
for (d = 0; d < ndims; d++) {
|
|
14
25
|
io.writeInt32BE(dim[d]); // Number of items in dimension
|
|
15
|
-
io.writeInt32BE(
|
|
26
|
+
io.writeInt32BE(lowerBound); // Index the first element answers to
|
|
16
27
|
}
|
|
17
28
|
let hasNull = false;
|
|
18
29
|
let pos;
|
|
@@ -8,3 +8,21 @@ export declare function formatTimestamp(v: Date | number | string, options?: Dat
|
|
|
8
8
|
* ignores `utcDates`, which only governs the zone-less types.
|
|
9
9
|
*/
|
|
10
10
|
export declare function formatTimestamptz(v: Date | number | string): string;
|
|
11
|
+
/**
|
|
12
|
+
* A `Date` as a parameter, rendered the way `pg` renders one: the wall
|
|
13
|
+
* clock in the process's own zone, followed by that zone's offset.
|
|
14
|
+
*
|
|
15
|
+
* It is the only rendering that is right for both column kinds at once,
|
|
16
|
+
* which is why the parameter path uses it instead of declaring a type.
|
|
17
|
+
* A `timestamptz` column honours the offset and stores the instant; a
|
|
18
|
+
* `timestamp` column discards it and keeps the wall clock - so the
|
|
19
|
+
* server decides, from the column, what one JavaScript `Date` could
|
|
20
|
+
* never say on its own. Declaring `timestamp` instead moved the instant
|
|
21
|
+
* by the client's offset, and declaring `timestamptz` moved the wall
|
|
22
|
+
* clock by it; measured against `pg` in three session zones, this
|
|
23
|
+
* matches in all six.
|
|
24
|
+
*
|
|
25
|
+
* Under `utcDates` the UTC components go out with a zero offset, which
|
|
26
|
+
* is the same rule the zone-less encoders follow.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatDateParam(v: Date | number | string, options?: DataMappingOptions): string;
|
package/util/format-datetime.js
CHANGED
|
@@ -69,3 +69,36 @@ export function formatTimestamptz(v) {
|
|
|
69
69
|
return d;
|
|
70
70
|
return d.toISOString().replace('T', ' ').replace('Z', '+00');
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* A `Date` as a parameter, rendered the way `pg` renders one: the wall
|
|
74
|
+
* clock in the process's own zone, followed by that zone's offset.
|
|
75
|
+
*
|
|
76
|
+
* It is the only rendering that is right for both column kinds at once,
|
|
77
|
+
* which is why the parameter path uses it instead of declaring a type.
|
|
78
|
+
* A `timestamptz` column honours the offset and stores the instant; a
|
|
79
|
+
* `timestamp` column discards it and keeps the wall clock - so the
|
|
80
|
+
* server decides, from the column, what one JavaScript `Date` could
|
|
81
|
+
* never say on its own. Declaring `timestamp` instead moved the instant
|
|
82
|
+
* by the client's offset, and declaring `timestamptz` moved the wall
|
|
83
|
+
* clock by it; measured against `pg` in three session zones, this
|
|
84
|
+
* matches in all six.
|
|
85
|
+
*
|
|
86
|
+
* Under `utcDates` the UTC components go out with a zero offset, which
|
|
87
|
+
* is the same rule the zone-less encoders follow.
|
|
88
|
+
*/
|
|
89
|
+
export function formatDateParam(v, options) {
|
|
90
|
+
const d = coerce(v);
|
|
91
|
+
if (typeof d === 'string')
|
|
92
|
+
return d;
|
|
93
|
+
const p = parts(d, options);
|
|
94
|
+
const clock = `${pad(p.year, 4)}-${pad(p.month)}-${pad(p.day)}` +
|
|
95
|
+
` ${pad(p.hour)}:${pad(p.minute)}:${pad(p.second)}.${pad(p.ms, 3)}`;
|
|
96
|
+
if (options?.utcDates)
|
|
97
|
+
return clock + '+00:00';
|
|
98
|
+
// getTimezoneOffset() counts minutes west of UTC, the opposite way
|
|
99
|
+
// round from how an offset is written.
|
|
100
|
+
const offset = -d.getTimezoneOffset();
|
|
101
|
+
const sign = offset < 0 ? '-' : '+';
|
|
102
|
+
const abs = Math.abs(offset);
|
|
103
|
+
return `${clock}${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
104
|
+
}
|
package/util/parse-array.js
CHANGED
|
@@ -21,13 +21,28 @@ export function parsePostgresArray(s, opts) {
|
|
|
21
21
|
continue;
|
|
22
22
|
}
|
|
23
23
|
if (c === '}' || c === sep) {
|
|
24
|
-
|
|
24
|
+
// An element is present when it had content OR when it was
|
|
25
|
+
// quoted: `""` is a real empty string, and testing the token
|
|
26
|
+
// alone dropped it - `{"",b}` came back as one element, with
|
|
27
|
+
// every later index shifted.
|
|
28
|
+
//
|
|
29
|
+
// An unquoted empty token is still skipped. PostgreSQL never
|
|
30
|
+
// emits one (a null prints as the bare word NULL, an empty
|
|
31
|
+
// string always as `""`), so `{a,,b}` is not output this has to
|
|
32
|
+
// read - and skipping it is what keeps the separator that
|
|
33
|
+
// follows a nested array from pushing an element of its own.
|
|
34
|
+
if (token || exactlyValue) {
|
|
25
35
|
if (token === 'NULL' && !exactlyValue)
|
|
26
36
|
arr.push(null);
|
|
27
37
|
else
|
|
28
38
|
arr.push(transform ? transform(token) : token);
|
|
29
|
-
exactlyValue = false;
|
|
30
39
|
}
|
|
40
|
+
// Reset per element rather than only when one was pushed.
|
|
41
|
+
// Leaving it set carried "this one was quoted" into the next
|
|
42
|
+
// element - and that flag is what tells a real NULL apart from
|
|
43
|
+
// the string "NULL", so a null following an empty string
|
|
44
|
+
// decoded as four characters of text.
|
|
45
|
+
exactlyValue = false;
|
|
31
46
|
token = '';
|
|
32
47
|
if (c === '}')
|
|
33
48
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DataTypeMap } from '../data-type-map.js';
|
|
1
2
|
import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
|
|
2
3
|
import { Protocol } from '../protocol/protocol.js';
|
|
3
4
|
import type { Maybe, OID } from '../types.js';
|
|
@@ -23,7 +24,7 @@ export interface ColumnFormatOptions extends DataMappingOptions {
|
|
|
23
24
|
* Returns `columnFormat` untouched when there is nothing to do, so the
|
|
24
25
|
* common case allocates nothing and keeps sending a single format code.
|
|
25
26
|
*/
|
|
26
|
-
export declare function resolveColumnFormats(fields: Maybe<Protocol.RowDescription[]>, options: ColumnFormatOptions): Protocol.DataFormat | Protocol.DataFormat[];
|
|
27
|
+
export declare function resolveColumnFormats(fields: Maybe<Protocol.RowDescription[]>, options: ColumnFormatOptions, typeMap?: DataTypeMap): Protocol.DataFormat | Protocol.DataFormat[];
|
|
27
28
|
/**
|
|
28
29
|
* Whether two `fetchAsString` lists select the same columns. Used to tell
|
|
29
30
|
* a cached set of parsers apart from one built for a different list, which
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
|
|
2
2
|
import { Protocol } from '../protocol/protocol.js';
|
|
3
3
|
const DataFormat = Protocol.DataFormat;
|
|
4
|
+
/**
|
|
5
|
+
* Whether this client could read the column if it arrived in `format`.
|
|
6
|
+
* Only ever answers no for binary: a column with no registered type at
|
|
7
|
+
* all still decodes as text, into the string the server printed, which is
|
|
8
|
+
* the whole point of asking for it that way.
|
|
9
|
+
*/
|
|
10
|
+
function canDecode(typeMap, oid, format) {
|
|
11
|
+
if (format !== DataFormat.binary)
|
|
12
|
+
return true;
|
|
13
|
+
const reg = typeMap.get(oid);
|
|
14
|
+
return !!reg && typeof reg.decodeBinary === 'function';
|
|
15
|
+
}
|
|
4
16
|
/**
|
|
5
17
|
* Turns `fetchAsString`'s OID list into the positional result format codes
|
|
6
18
|
* a Bind message actually carries.
|
|
@@ -20,28 +32,43 @@ const DataFormat = Protocol.DataFormat;
|
|
|
20
32
|
* Returns `columnFormat` untouched when there is nothing to do, so the
|
|
21
33
|
* common case allocates nothing and keeps sending a single format code.
|
|
22
34
|
*/
|
|
23
|
-
export function resolveColumnFormats(fields, options) {
|
|
35
|
+
export function resolveColumnFormats(fields, options, typeMap) {
|
|
24
36
|
const base = options.columnFormat != null ? options.columnFormat : DEFAULT_COLUMN_FORMAT;
|
|
25
37
|
const asString = options.fetchAsString;
|
|
26
|
-
|
|
38
|
+
// unknownTypesAsString needs the type map to answer "could this be
|
|
39
|
+
// decoded", so it is inert without one - the call sites that have no
|
|
40
|
+
// map in hand are the ones that have no fields either.
|
|
41
|
+
const unknownAsString = !!options.unknownTypesAsString && !!typeMap;
|
|
42
|
+
const named = !!asString && asString.length > 0;
|
|
43
|
+
if (!fields || (!named && !unknownAsString))
|
|
27
44
|
return base;
|
|
28
45
|
const l = fields.length;
|
|
29
46
|
const baseIsArray = Array.isArray(base);
|
|
30
47
|
const out = new Array(l);
|
|
31
48
|
let hit = false;
|
|
32
49
|
let i;
|
|
50
|
+
let oid;
|
|
51
|
+
let format;
|
|
33
52
|
for (i = 0; i < l; i++) {
|
|
34
|
-
|
|
53
|
+
oid = fields[i].dataTypeId;
|
|
54
|
+
format = baseIsArray
|
|
55
|
+
? (base[i] ?? DEFAULT_COLUMN_FORMAT)
|
|
56
|
+
: base;
|
|
57
|
+
if (named && asString.includes(oid)) {
|
|
35
58
|
// fetchAsString names the value the caller wants verbatim, so it
|
|
36
59
|
// overrides an explicit columnFormat for its own columns - the two
|
|
37
60
|
// cannot both be honored and the OID list is the more specific ask.
|
|
38
|
-
|
|
61
|
+
format = DataFormat.text;
|
|
62
|
+
hit = true;
|
|
63
|
+
}
|
|
64
|
+
else if (unknownAsString && !canDecode(typeMap, oid, format)) {
|
|
65
|
+
// Nothing here could read the binary, so the bytes would reach the
|
|
66
|
+
// caller as a Buffer. Text at least arrives as what the server
|
|
67
|
+
// printed.
|
|
68
|
+
format = DataFormat.text;
|
|
39
69
|
hit = true;
|
|
40
70
|
}
|
|
41
|
-
|
|
42
|
-
out[i] = baseIsArray
|
|
43
|
-
? (base[i] ?? DEFAULT_COLUMN_FORMAT)
|
|
44
|
-
: base;
|
|
71
|
+
out[i] = format;
|
|
45
72
|
}
|
|
46
73
|
return hit ? out : base;
|
|
47
74
|
}
|
package/util/sql-tag.js
CHANGED
|
@@ -2,6 +2,9 @@ import { BindParam } from '../connection/bind-param.js';
|
|
|
2
2
|
import { DataTypeMap, GlobalTypeMap } from '../data-type-map.js';
|
|
3
3
|
import { escapeIdentifier } from './escape-identifier.js';
|
|
4
4
|
import { escapeLiteral } from './escape-literal.js';
|
|
5
|
+
import { formatDateParam } from './format-datetime.js';
|
|
6
|
+
import { stringifyArrayLiteral } from './stringify-arrayliteral.js';
|
|
7
|
+
import { arrayLeaf, isUnspecifiedParam } from './unspecified-param.js';
|
|
5
8
|
/**
|
|
6
9
|
* A statement built by the `sql` tag: its text with the values pulled out.
|
|
7
10
|
*
|
|
@@ -171,6 +174,18 @@ function encodeLiteral(value, typeMap, options) {
|
|
|
171
174
|
}
|
|
172
175
|
if (v == null)
|
|
173
176
|
return 'null';
|
|
177
|
+
// A Date and a string go in bare, exactly as they go out as
|
|
178
|
+
// parameters with no declared type (see unspecified-param.ts): an
|
|
179
|
+
// unadorned literal is `unknown` and takes the type of wherever it
|
|
180
|
+
// lands. The cast this path adds everywhere else is what keeps
|
|
181
|
+
// query() and execute() meaning the same thing, and for these two it
|
|
182
|
+
// is what would break that - `'{"a":1}'::varchar` is refused by a
|
|
183
|
+
// json column that takes the parameter without complaint.
|
|
184
|
+
if (oid == null && isUnspecifiedParam(v)) {
|
|
185
|
+
if (!Array.isArray(v))
|
|
186
|
+
return escapeLiteral(v instanceof Date ? formatDateParam(v, options) : '' + v);
|
|
187
|
+
return escapeLiteral(stringifyArrayLiteral(v, options, arrayLeaf(v) instanceof Date ? formatDateParam : undefined));
|
|
188
|
+
}
|
|
174
189
|
oid = oid ?? typeMap.determine(v);
|
|
175
190
|
/* c8 ignore start - typeMap.determine() always falls back to the
|
|
176
191
|
"unknown" oid rather than returning null/undefined, so this can't
|
|
@@ -1,9 +1,42 @@
|
|
|
1
|
+
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { GlobalTypeMap } from '../data-type-map.js';
|
|
1
3
|
import { UuidType } from '../data-types/uuid-type.js';
|
|
2
4
|
import { escapeLiteral } from './escape-literal.js';
|
|
5
|
+
import { formatDateParam } from './format-datetime.js';
|
|
3
6
|
export function stringifyArrayForSQL(v, options, encode) {
|
|
4
7
|
const arr = v.map(x => stringifyValueForSQL(x, options, encode));
|
|
5
8
|
return 'ARRAY[' + arr.join(',') + ']';
|
|
6
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Which type an object is is asked rather than assumed.
|
|
12
|
+
*
|
|
13
|
+
* Every geometric class, Interval, Range, Date and Buffer has a
|
|
14
|
+
* registered `encodeText()` that writes exactly the literal PostgreSQL
|
|
15
|
+
* reads back, and `determine()` already knows which type each one is -
|
|
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`, and since these classes
|
|
18
|
+
* have a `toJSON()` returning their own literal the result was not even
|
|
19
|
+
* a JSON object but a JSON string holding one, which casts to nothing:
|
|
20
|
+
* `'"(1,2)"'::json` where `'(1,2)'::point` was meant. A Date became its
|
|
21
|
+
* ISO string and a Buffer leaked its bytes as `{"type":"Buffer",...}`.
|
|
22
|
+
*/
|
|
23
|
+
function stringifyObjectForSQL(v, options) {
|
|
24
|
+
// determine() throws for a class that must carry a type OID and does
|
|
25
|
+
// not - a Range built without one - and that throw is deliberate: the
|
|
26
|
+
// alternative is writing a literal for a type nobody chose.
|
|
27
|
+
const oid = GlobalTypeMap.determine(v);
|
|
28
|
+
const dataType = GlobalTypeMap.get(oid);
|
|
29
|
+
// json and jsonb are the fallback below; reaching them through their
|
|
30
|
+
// own encodeText would only add a cast the literal does not need.
|
|
31
|
+
// Anything with no registered text encoder lands there too.
|
|
32
|
+
if (dataType?.encodeText &&
|
|
33
|
+
oid !== DataTypeOIDs.json &&
|
|
34
|
+
oid !== DataTypeOIDs.jsonb)
|
|
35
|
+
return (escapeLiteral(dataType.encodeText(v, options || {})) +
|
|
36
|
+
'::' +
|
|
37
|
+
dataType.name);
|
|
38
|
+
return escapeLiteral(JSON.stringify(v)) + '::json';
|
|
39
|
+
}
|
|
7
40
|
export function stringifyValueForSQL(v, options, encode) {
|
|
8
41
|
if (v == null)
|
|
9
42
|
return 'null';
|
|
@@ -19,7 +52,17 @@ export function stringifyValueForSQL(v, options, encode) {
|
|
|
19
52
|
return v.toString();
|
|
20
53
|
if (typeof v === 'string' && UuidType.isType(v))
|
|
21
54
|
return escapeLiteral('' + v) + '::uuid';
|
|
55
|
+
// A Date is written bare, with its offset and no cast, so that it
|
|
56
|
+
// means the same inlined as it does bound - the server resolves it
|
|
57
|
+
// from the column either way. See format-datetime.ts's
|
|
58
|
+
// formatDateParam().
|
|
59
|
+
if (v instanceof Date)
|
|
60
|
+
return escapeLiteral(formatDateParam(v, options));
|
|
22
61
|
if (typeof v === 'object')
|
|
23
|
-
return
|
|
62
|
+
return stringifyObjectForSQL(v, options);
|
|
63
|
+
// A string is deliberately written bare. determine() would answer
|
|
64
|
+
// varchar, but an unadorned literal is `unknown` and takes the type of
|
|
65
|
+
// wherever it lands - `insert into t(n) values('5')` works and
|
|
66
|
+
// `'5'::varchar` would not.
|
|
24
67
|
return escapeLiteral('' + v);
|
|
25
68
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first value inside `v` that is not itself an array - what an array
|
|
3
|
+
* parameter's element type would be read from. A scalar answers itself.
|
|
4
|
+
*/
|
|
5
|
+
export declare function arrayLeaf(v: any): any;
|
|
6
|
+
/**
|
|
7
|
+
* Whether a parameter goes out with no declared type (OID 0), as text,
|
|
8
|
+
* for the server to resolve from wherever it lands.
|
|
9
|
+
*
|
|
10
|
+
* Two kinds of value cannot say what they are:
|
|
11
|
+
*
|
|
12
|
+
* - A `Date` is an instant or a wall clock depending on the column, and
|
|
13
|
+
* declaring either one moves the other. See `format-datetime.ts`.
|
|
14
|
+
* - A string is whichever of PostgreSQL's text-input types the context
|
|
15
|
+
* calls for. `determine()` answers `varchar`, and declaring that stops
|
|
16
|
+
* the server inferring: `insert into t(j) values($1)` into a `json`
|
|
17
|
+
* column, `id = $1` against a `uuid`, `coalesce($1, 1)` and an enum
|
|
18
|
+
* column all fail with "expression is of type character varying".
|
|
19
|
+
* Unspecified, every one of them works.
|
|
20
|
+
*
|
|
21
|
+
* This is what `pg` sends for both, and the divergence from it is what
|
|
22
|
+
* the shapes above were failing on. It is not free: a parameter with no
|
|
23
|
+
* context to be resolved from - `$1 is null`, `array_agg($1)`,
|
|
24
|
+
* `concat($1, 1)` - now raises "could not determine data type", exactly
|
|
25
|
+
* as it does under `pg`. A cast (`$1::text`) or a named type
|
|
26
|
+
* (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
|
|
27
|
+
*
|
|
28
|
+
* Numbers, booleans and Buffers keep their declared types: they are
|
|
29
|
+
* already right nearly everywhere, and sending them as text would give
|
|
30
|
+
* up the binary encoding for no correctness gain.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isUnspecifiedParam(v: any): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first value inside `v` that is not itself an array - what an array
|
|
3
|
+
* parameter's element type would be read from. A scalar answers itself.
|
|
4
|
+
*/
|
|
5
|
+
export function arrayLeaf(v) {
|
|
6
|
+
let x = v;
|
|
7
|
+
while (Array.isArray(x))
|
|
8
|
+
x = x[0];
|
|
9
|
+
return x;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether a parameter goes out with no declared type (OID 0), as text,
|
|
13
|
+
* for the server to resolve from wherever it lands.
|
|
14
|
+
*
|
|
15
|
+
* Two kinds of value cannot say what they are:
|
|
16
|
+
*
|
|
17
|
+
* - A `Date` is an instant or a wall clock depending on the column, and
|
|
18
|
+
* declaring either one moves the other. See `format-datetime.ts`.
|
|
19
|
+
* - A string is whichever of PostgreSQL's text-input types the context
|
|
20
|
+
* calls for. `determine()` answers `varchar`, and declaring that stops
|
|
21
|
+
* the server inferring: `insert into t(j) values($1)` into a `json`
|
|
22
|
+
* column, `id = $1` against a `uuid`, `coalesce($1, 1)` and an enum
|
|
23
|
+
* column all fail with "expression is of type character varying".
|
|
24
|
+
* Unspecified, every one of them works.
|
|
25
|
+
*
|
|
26
|
+
* This is what `pg` sends for both, and the divergence from it is what
|
|
27
|
+
* the shapes above were failing on. It is not free: a parameter with no
|
|
28
|
+
* context to be resolved from - `$1 is null`, `array_agg($1)`,
|
|
29
|
+
* `concat($1, 1)` - now raises "could not determine data type", exactly
|
|
30
|
+
* as it does under `pg`. A cast (`$1::text`) or a named type
|
|
31
|
+
* (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
|
|
32
|
+
*
|
|
33
|
+
* Numbers, booleans and Buffers keep their declared types: they are
|
|
34
|
+
* already right nearly everywhere, and sending them as text would give
|
|
35
|
+
* up the binary encoding for no correctness gain.
|
|
36
|
+
*/
|
|
37
|
+
export function isUnspecifiedParam(v) {
|
|
38
|
+
const x = arrayLeaf(v);
|
|
39
|
+
return typeof x === 'string' || x instanceof Date;
|
|
40
|
+
}
|