postgrejs 3.7.0 → 3.9.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 +53 -0
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +70 -10
- package/connection/intl-connection.d.ts +146 -5
- package/connection/intl-connection.js +485 -35
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +28 -3
- package/connection/prepared-statement.d.ts +11 -0
- package/connection/prepared-statement.js +39 -5
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/classes/numeric.d.ts +8 -4
- package/data-types/classes/numeric.js +8 -4
- package/data-types/date-type.js +2 -2
- package/data-types/int2-vector-type.js +4 -1
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +203 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +1 -1
- package/data-types/oid-vector-type.js +4 -1
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +11 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/data-mapping-options.d.ts +27 -0
- 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 +17 -0
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- 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-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.js +4 -0
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +16 -0
- package/util/stringify-for-sql.js +7 -0
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +27 -0
- package/util/unspecified-param.js +31 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session's `DateStyle`, when it is not the ISO default.
|
|
3
|
+
*
|
|
4
|
+
* PostgreSQL renders a date four ways, and the client cannot tell which
|
|
5
|
+
* one it is looking at from the string alone - `05.03.2024` is the fifth
|
|
6
|
+
* of March in every style that prints it, but `05/03/2024` is the fifth
|
|
7
|
+
* of March under `SQL, DMY` and the third of May under `SQL, MDY`. The
|
|
8
|
+
* server reports `DateStyle` in its startup parameters and again on
|
|
9
|
+
* every `SET`, so the convention is known rather than guessed.
|
|
10
|
+
*
|
|
11
|
+
* Measured against PostgreSQL 18.4, one value in every combination:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ISO, any 2024-11-25 01:02:03.456 2024-03-05
|
|
15
|
+
* SQL, MDY 11/25/2024 01:02:03.456 UTC 03/05/2024
|
|
16
|
+
* SQL, DMY 25/11/2024 01:02:03.456 UTC 05/03/2024
|
|
17
|
+
* Postgres, MDY Mon Nov 25 01:02:03.456 2024 03-05-2024
|
|
18
|
+
* Postgres, DMY Mon 25 Nov 01:02:03.456 2024 05-03-2024
|
|
19
|
+
* German, any 25.11.2024 01:02:03.456 UTC 05.03.2024
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Two things in that table are not what the setting's name suggests:
|
|
23
|
+
* German ignores the field order entirely, and `YMD` prints as `MDY`
|
|
24
|
+
* for every style. Both are read off the measurement rather than off
|
|
25
|
+
* the documentation.
|
|
26
|
+
*/
|
|
27
|
+
export interface PgDateStyle {
|
|
28
|
+
style: 'SQL' | 'Postgres' | 'German';
|
|
29
|
+
/** Whether the numeric day comes before the month. */
|
|
30
|
+
dayFirst: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The `DateStyle` the server reported, or undefined for ISO - which is
|
|
34
|
+
* the default, is what every existing parser here expects, and is the
|
|
35
|
+
* one shape that needs no extra work.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseDateStyleSetting(value: string | undefined): PgDateStyle | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Reads a date or timestamp the server rendered in `style`.
|
|
40
|
+
*
|
|
41
|
+
* Returns undefined when the string is not that style's shape - a `BC`
|
|
42
|
+
* year, an `infinity`, or a zone abbreviation nobody can resolve - so
|
|
43
|
+
* the caller decides what to do with it. What it must never do is guess:
|
|
44
|
+
* handing `05.03.2024` to `new Date()` is how the fifth of March became
|
|
45
|
+
* the third of May, silently, for every day of the month up to twelve.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseStyledDateTime(str: string, style: PgDateStyle, utc?: boolean): Date | undefined;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const MONTHS = {
|
|
2
|
+
jan: 0,
|
|
3
|
+
feb: 1,
|
|
4
|
+
mar: 2,
|
|
5
|
+
apr: 3,
|
|
6
|
+
may: 4,
|
|
7
|
+
jun: 5,
|
|
8
|
+
jul: 6,
|
|
9
|
+
aug: 7,
|
|
10
|
+
sep: 8,
|
|
11
|
+
oct: 9,
|
|
12
|
+
nov: 10,
|
|
13
|
+
dec: 11,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* `Postgres` style: a weekday, then the month and day in the configured
|
|
17
|
+
* order, the time, the year, and for timestamptz a zone.
|
|
18
|
+
*/
|
|
19
|
+
const POSTGRES_PATTERN = /^[A-Za-z]{3} (?:([A-Za-z]{3}) (\d{1,2})|(\d{1,2}) ([A-Za-z]{3})) (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))? (\d{1,6})(?: (.+))?$/;
|
|
20
|
+
/** `SQL` and `German`: three numbers, a time, and for timestamptz a zone. */
|
|
21
|
+
const NUMERIC_PATTERN = /^(\d{1,2})([./])(\d{1,2})\2(\d{1,6})(?: (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?)?(?: (.+))?$/;
|
|
22
|
+
/** `Postgres` style writes a date on its own with dashes. */
|
|
23
|
+
const POSTGRES_DATE_PATTERN = /^(\d{1,2})-(\d{1,2})-(\d{1,6})$/;
|
|
24
|
+
/**
|
|
25
|
+
* The `DateStyle` the server reported, or undefined for ISO - which is
|
|
26
|
+
* the default, is what every existing parser here expects, and is the
|
|
27
|
+
* one shape that needs no extra work.
|
|
28
|
+
*/
|
|
29
|
+
export function parseDateStyleSetting(value) {
|
|
30
|
+
if (!value)
|
|
31
|
+
return undefined;
|
|
32
|
+
const comma = value.indexOf(',');
|
|
33
|
+
const style = (comma < 0 ? value : value.substring(0, comma)).trim();
|
|
34
|
+
if (style === 'ISO')
|
|
35
|
+
return undefined;
|
|
36
|
+
if (style !== 'SQL' && style !== 'Postgres' && style !== 'German')
|
|
37
|
+
return undefined;
|
|
38
|
+
const order = comma < 0 ? '' : value.substring(comma + 1).trim();
|
|
39
|
+
// German prints day-first whatever the order says, and YMD prints as
|
|
40
|
+
// MDY - both measured, neither obvious.
|
|
41
|
+
return { style, dayFirst: style === 'German' || order === 'DMY' };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The offset a rendered zone stands for, in minutes, or undefined when
|
|
45
|
+
* it cannot be known.
|
|
46
|
+
*
|
|
47
|
+
* Outside ISO the server writes the zone's *abbreviation* where it has
|
|
48
|
+
* one - `EST`, `IST` - and those do not identify an offset: IST is India
|
|
49
|
+
* at +05:30, Israel at +02:00 and Ireland at +01:00. A numeric offset
|
|
50
|
+
* and UTC/GMT are exact, and anything else is refused rather than
|
|
51
|
+
* guessed at.
|
|
52
|
+
*/
|
|
53
|
+
function zoneOffsetMinutes(zone) {
|
|
54
|
+
if (zone === 'UTC' || zone === 'GMT' || zone === 'Z')
|
|
55
|
+
return 0;
|
|
56
|
+
const m = /^([+-])(\d{1,2})(?::?(\d{2}))?$/.exec(zone);
|
|
57
|
+
if (!m)
|
|
58
|
+
return undefined;
|
|
59
|
+
const sign = m[1] === '-' ? -1 : 1;
|
|
60
|
+
return sign * (+m[2] * 60 + (m[3] ? +m[3] : 0));
|
|
61
|
+
}
|
|
62
|
+
function build(y, mon, d, h, mi, s, ms, offsetMinutes, utc) {
|
|
63
|
+
if (offsetMinutes !== undefined)
|
|
64
|
+
return new Date(Date.UTC(y, mon, d, h, mi - offsetMinutes, s, ms));
|
|
65
|
+
if (utc)
|
|
66
|
+
return new Date(Date.UTC(y, mon, d, h, mi, s, ms));
|
|
67
|
+
return new Date(y, mon, d, h, mi, s, ms);
|
|
68
|
+
}
|
|
69
|
+
function fraction(f) {
|
|
70
|
+
return f ? +(f + '000').slice(0, 3) : 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Reads a date or timestamp the server rendered in `style`.
|
|
74
|
+
*
|
|
75
|
+
* Returns undefined when the string is not that style's shape - a `BC`
|
|
76
|
+
* year, an `infinity`, or a zone abbreviation nobody can resolve - so
|
|
77
|
+
* the caller decides what to do with it. What it must never do is guess:
|
|
78
|
+
* handing `05.03.2024` to `new Date()` is how the fifth of March became
|
|
79
|
+
* the third of May, silently, for every day of the month up to twelve.
|
|
80
|
+
*/
|
|
81
|
+
export function parseStyledDateTime(str, style, utc) {
|
|
82
|
+
// Every style writes it the same way, and it is not a date.
|
|
83
|
+
if (str.endsWith(' BC'))
|
|
84
|
+
return undefined;
|
|
85
|
+
if (style.style === 'Postgres') {
|
|
86
|
+
const m = POSTGRES_PATTERN.exec(str);
|
|
87
|
+
if (m) {
|
|
88
|
+
const mon = MONTHS[(m[1] || m[4]).toLowerCase()];
|
|
89
|
+
if (mon === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
const day = +(m[2] || m[3]);
|
|
92
|
+
let offset;
|
|
93
|
+
if (m[10]) {
|
|
94
|
+
offset = zoneOffsetMinutes(m[10]);
|
|
95
|
+
if (offset === undefined)
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return build(+m[9], mon, day, +m[5], +m[6], +m[7], fraction(m[8]), offset, utc);
|
|
99
|
+
}
|
|
100
|
+
const d = POSTGRES_DATE_PATTERN.exec(str);
|
|
101
|
+
if (!d)
|
|
102
|
+
return undefined;
|
|
103
|
+
const day = +(style.dayFirst ? d[1] : d[2]);
|
|
104
|
+
const mon = +(style.dayFirst ? d[2] : d[1]) - 1;
|
|
105
|
+
return build(+d[3], mon, day, 0, 0, 0, 0, undefined, utc);
|
|
106
|
+
}
|
|
107
|
+
const m = NUMERIC_PATTERN.exec(str);
|
|
108
|
+
if (!m)
|
|
109
|
+
return undefined;
|
|
110
|
+
// German writes dots and SQL writes slashes; a string using the other
|
|
111
|
+
// one is not this session's rendering and is not read as if it were.
|
|
112
|
+
if (m[2] !== (style.style === 'German' ? '.' : '/'))
|
|
113
|
+
return undefined;
|
|
114
|
+
const day = +(style.dayFirst ? m[1] : m[3]);
|
|
115
|
+
const mon = +(style.dayFirst ? m[3] : m[1]) - 1;
|
|
116
|
+
let offset;
|
|
117
|
+
if (m[9]) {
|
|
118
|
+
offset = zoneOffsetMinutes(m[9]);
|
|
119
|
+
if (offset === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return build(+m[4], mon, day, m[5] ? +m[5] : 0, m[6] ? +m[6] : 0, m[7] ? +m[7] : 0, fraction(m[8]), offset, utc);
|
|
123
|
+
}
|
|
@@ -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-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);
|
|
@@ -42,6 +42,10 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
42
42
|
const named = !!asString && asString.length > 0;
|
|
43
43
|
if (!fields || (!named && !unknownAsString))
|
|
44
44
|
return base;
|
|
45
|
+
// Both options do one thing: ask for a column as text. A row that is
|
|
46
|
+
// already all text has nothing left for them to ask.
|
|
47
|
+
if (base === DataFormat.text)
|
|
48
|
+
return base;
|
|
45
49
|
const l = fields.length;
|
|
46
50
|
const baseIsArray = Array.isArray(base);
|
|
47
51
|
const out = new Array(l);
|
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,7 +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';
|
|
6
|
+
import { formatDateParam } from './format-datetime.js';
|
|
7
|
+
import { stringifyArrayLiteral } from './stringify-arrayliteral.js';
|
|
8
|
+
import { isUnspecifiedParam } from './unspecified-param.js';
|
|
5
9
|
/**
|
|
6
10
|
* A statement built by the `sql` tag: its text with the values pulled out.
|
|
7
11
|
*
|
|
@@ -171,6 +175,18 @@ function encodeLiteral(value, typeMap, options) {
|
|
|
171
175
|
}
|
|
172
176
|
if (v == null)
|
|
173
177
|
return 'null';
|
|
178
|
+
// A Date and a string go in bare, exactly as they go out as
|
|
179
|
+
// parameters with no declared type (see unspecified-param.ts): an
|
|
180
|
+
// unadorned literal is `unknown` and takes the type of wherever it
|
|
181
|
+
// lands. The cast this path adds everywhere else is what keeps
|
|
182
|
+
// query() and execute() meaning the same thing, and for these two it
|
|
183
|
+
// is what would break that - `'{"a":1}'::varchar` is refused by a
|
|
184
|
+
// json column that takes the parameter without complaint.
|
|
185
|
+
if (oid == null && isUnspecifiedParam(v)) {
|
|
186
|
+
if (!Array.isArray(v))
|
|
187
|
+
return escapeLiteral(v instanceof Date ? formatDateParam(v, options) : '' + v);
|
|
188
|
+
return escapeLiteral(stringifyArrayLiteral(v, options, arrayLeaf(v) instanceof Date ? formatDateParam : undefined));
|
|
189
|
+
}
|
|
174
190
|
oid = oid ?? typeMap.determine(v);
|
|
175
191
|
/* c8 ignore start - typeMap.determine() always falls back to the
|
|
176
192
|
"unknown" oid rather than returning null/undefined, so this can't
|
|
@@ -2,6 +2,7 @@ import { DataTypeOIDs } from '../constants.js';
|
|
|
2
2
|
import { GlobalTypeMap } from '../data-type-map.js';
|
|
3
3
|
import { UuidType } from '../data-types/uuid-type.js';
|
|
4
4
|
import { escapeLiteral } from './escape-literal.js';
|
|
5
|
+
import { formatDateParam } from './format-datetime.js';
|
|
5
6
|
export function stringifyArrayForSQL(v, options, encode) {
|
|
6
7
|
const arr = v.map(x => stringifyValueForSQL(x, options, encode));
|
|
7
8
|
return 'ARRAY[' + arr.join(',') + ']';
|
|
@@ -51,6 +52,12 @@ export function stringifyValueForSQL(v, options, encode) {
|
|
|
51
52
|
return v.toString();
|
|
52
53
|
if (typeof v === 'string' && UuidType.isType(v))
|
|
53
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));
|
|
54
61
|
if (typeof v === 'object')
|
|
55
62
|
return stringifyObjectForSQL(v, options);
|
|
56
63
|
// A string is deliberately written bare. determine() would answer
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Whether `sql` opens or closes a transaction. */
|
|
2
|
+
export declare function isTransactionCommand(sql: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Whether `sql` must not run inside a savepoint - it is a transaction
|
|
5
|
+
* command, or it configures the transaction.
|
|
6
|
+
*/
|
|
7
|
+
export declare function refusesSavepoint(sql: string): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statements that open or close a transaction, and so manage their own
|
|
3
|
+
* boundaries: an implicit BEGIN or COMMIT around one would be wrong, and
|
|
4
|
+
* wrapping one in a savepoint - COMMIT, say - is nonsense.
|
|
5
|
+
*/
|
|
6
|
+
const TRANSACTION_COMMAND_PATTERN = /^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i;
|
|
7
|
+
/**
|
|
8
|
+
* `SET TRANSACTION`, which configures the transaction it runs in rather
|
|
9
|
+
* than opening or closing one.
|
|
10
|
+
*
|
|
11
|
+
* It needs an answer of its own because the two are not the same
|
|
12
|
+
* question. PostgreSQL refuses two of its forms inside a subtransaction
|
|
13
|
+
* - `SET TRANSACTION ISOLATION LEVEL` and `SET TRANSACTION [NOT]
|
|
14
|
+
* DEFERRABLE`, both 25001 - so the savepoint `rollbackOnError` puts
|
|
15
|
+
* around every statement in a transaction made them impossible to run
|
|
16
|
+
* at all. But the implicit BEGIN that `autoCommit: false` adds is
|
|
17
|
+
* exactly what they need, so answering "transaction command" for them
|
|
18
|
+
* and suppressing both would trade one failure for another.
|
|
19
|
+
*/
|
|
20
|
+
const CONFIGURES_TRANSACTION_PATTERN = /^\bSET\s+TRANSACTION\b/i;
|
|
21
|
+
/** Whether `sql` opens or closes a transaction. */
|
|
22
|
+
export function isTransactionCommand(sql) {
|
|
23
|
+
return TRANSACTION_COMMAND_PATTERN.test(sql);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Whether `sql` must not run inside a savepoint - it is a transaction
|
|
27
|
+
* command, or it configures the transaction.
|
|
28
|
+
*/
|
|
29
|
+
export function refusesSavepoint(sql) {
|
|
30
|
+
return (TRANSACTION_COMMAND_PATTERN.test(sql) ||
|
|
31
|
+
CONFIGURES_TRANSACTION_PATTERN.test(sql));
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a parameter goes out with no declared type (OID 0), as text,
|
|
3
|
+
* for the server to resolve from wherever it lands.
|
|
4
|
+
*
|
|
5
|
+
* Two kinds of value cannot say what they are:
|
|
6
|
+
*
|
|
7
|
+
* - A `Date` is an instant or a wall clock depending on the column, and
|
|
8
|
+
* declaring either one moves the other. See `format-datetime.ts`.
|
|
9
|
+
* - A string is whichever of PostgreSQL's text-input types the context
|
|
10
|
+
* calls for. `determine()` answers `varchar`, and declaring that stops
|
|
11
|
+
* the server inferring: `insert into t(j) values($1)` into a `json`
|
|
12
|
+
* column, `id = $1` against a `uuid`, `coalesce($1, 1)` and an enum
|
|
13
|
+
* column all fail with "expression is of type character varying".
|
|
14
|
+
* Unspecified, every one of them works.
|
|
15
|
+
*
|
|
16
|
+
* This is what `pg` sends for both, and the divergence from it is what
|
|
17
|
+
* the shapes above were failing on. It is not free: a parameter with no
|
|
18
|
+
* context to be resolved from - `$1 is null`, `array_agg($1)`,
|
|
19
|
+
* `concat($1, 1)` - now raises "could not determine data type", exactly
|
|
20
|
+
* as it does under `pg`. A cast (`$1::text`) or a named type
|
|
21
|
+
* (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
|
|
22
|
+
*
|
|
23
|
+
* Numbers, booleans and Buffers keep their declared types: they are
|
|
24
|
+
* already right nearly everywhere, and sending them as text would give
|
|
25
|
+
* up the binary encoding for no correctness gain.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isUnspecifiedParam(v: any): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { arrayLeaf } from './array-leaf.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a parameter goes out with no declared type (OID 0), as text,
|
|
4
|
+
* for the server to resolve from wherever it lands.
|
|
5
|
+
*
|
|
6
|
+
* Two kinds of value cannot say what they are:
|
|
7
|
+
*
|
|
8
|
+
* - A `Date` is an instant or a wall clock depending on the column, and
|
|
9
|
+
* declaring either one moves the other. See `format-datetime.ts`.
|
|
10
|
+
* - A string is whichever of PostgreSQL's text-input types the context
|
|
11
|
+
* calls for. `determine()` answers `varchar`, and declaring that stops
|
|
12
|
+
* the server inferring: `insert into t(j) values($1)` into a `json`
|
|
13
|
+
* column, `id = $1` against a `uuid`, `coalesce($1, 1)` and an enum
|
|
14
|
+
* column all fail with "expression is of type character varying".
|
|
15
|
+
* Unspecified, every one of them works.
|
|
16
|
+
*
|
|
17
|
+
* This is what `pg` sends for both, and the divergence from it is what
|
|
18
|
+
* the shapes above were failing on. It is not free: a parameter with no
|
|
19
|
+
* context to be resolved from - `$1 is null`, `array_agg($1)`,
|
|
20
|
+
* `concat($1, 1)` - now raises "could not determine data type", exactly
|
|
21
|
+
* as it does under `pg`. A cast (`$1::text`) or a named type
|
|
22
|
+
* (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
|
|
23
|
+
*
|
|
24
|
+
* Numbers, booleans and Buffers keep their declared types: they are
|
|
25
|
+
* already right nearly everywhere, and sending them as text would give
|
|
26
|
+
* up the binary encoding for no correctness gain.
|
|
27
|
+
*/
|
|
28
|
+
export function isUnspecifiedParam(v) {
|
|
29
|
+
const x = arrayLeaf(v);
|
|
30
|
+
return typeof x === 'string' || x instanceof Date;
|
|
31
|
+
}
|