postgrejs 3.8.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README.md +50 -0
  3. package/connection/connection.d.ts +14 -2
  4. package/connection/connection.js +63 -11
  5. package/connection/intl-connection.d.ts +164 -6
  6. package/connection/intl-connection.js +558 -37
  7. package/connection/pool.d.ts +30 -6
  8. package/connection/pool.js +22 -3
  9. package/connection/prepared-statement.js +22 -2
  10. package/data-type-map.d.ts +16 -0
  11. package/data-type-map.js +56 -12
  12. package/data-types/circle-type.js +5 -2
  13. package/data-types/classes/geometric.d.ts +49 -7
  14. package/data-types/classes/geometric.js +59 -11
  15. package/data-types/classes/interval.d.ts +40 -16
  16. package/data-types/classes/interval.js +68 -40
  17. package/data-types/classes/numeric.d.ts +6 -0
  18. package/data-types/classes/numeric.js +8 -0
  19. package/data-types/classes/range.d.ts +6 -1
  20. package/data-types/classes/range.js +6 -1
  21. package/data-types/date-type.js +2 -2
  22. package/data-types/interval-type.js +1 -1
  23. package/data-types/money-type.d.ts +38 -0
  24. package/data-types/money-type.js +212 -0
  25. package/data-types/numeric-type.d.ts +2 -0
  26. package/data-types/numeric-type.js +22 -11
  27. package/data-types/temporal-types.d.ts +19 -0
  28. package/data-types/temporal-types.js +520 -0
  29. package/data-types/timestamp-type.js +2 -2
  30. package/data-types/timestamptz-type.js +2 -2
  31. package/index.d.ts +3 -0
  32. package/index.js +3 -0
  33. package/interfaces/data-mapping-options.d.ts +126 -4
  34. package/interfaces/database-connection-params.d.ts +32 -5
  35. package/interfaces/query-options.d.ts +32 -0
  36. package/interfaces/script-execute-options.d.ts +32 -0
  37. package/interfaces/transaction-options.d.ts +27 -0
  38. package/interfaces/transaction-options.js +1 -0
  39. package/package.json +1 -1
  40. package/protocol/database-error.d.ts +15 -0
  41. package/protocol/database-error.js +19 -0
  42. package/protocol/frontend.js +1 -1
  43. package/protocol/pg-socket.js +7 -10
  44. package/util/array-leaf.d.ts +10 -0
  45. package/util/array-leaf.js +22 -0
  46. package/util/connection-config.js +11 -1
  47. package/util/date-style.d.ts +47 -0
  48. package/util/date-style.js +123 -0
  49. package/util/decimal-as-string.d.ts +11 -0
  50. package/util/decimal-as-string.js +30 -0
  51. package/util/fetch-as-string.d.ts +33 -0
  52. package/util/fetch-as-string.js +122 -0
  53. package/util/get-parsers.js +18 -1
  54. package/util/parse-datetime.d.ts +4 -3
  55. package/util/parse-datetime.js +34 -9
  56. package/util/resolve-column-formats.d.ts +5 -11
  57. package/util/resolve-column-formats.js +28 -24
  58. package/util/row-decoder.d.ts +24 -4
  59. package/util/row-decoder.js +25 -10
  60. package/util/sql-tag.js +2 -1
  61. package/util/stringify-for-sql.js +6 -5
  62. package/util/temporal.d.ts +47 -0
  63. package/util/temporal.js +61 -0
  64. package/util/transaction-command.d.ts +7 -0
  65. package/util/transaction-command.js +32 -0
  66. package/util/unspecified-param.d.ts +18 -8
  67. package/util/unspecified-param.js +22 -14
@@ -0,0 +1,30 @@
1
+ import { DataTypeNames, DataTypeOIDs } from '../constants.js';
2
+ /**
3
+ * The types that can decode to an exact decimal string: the two that
4
+ * carry a decimal the wire format states exactly, rather than a double's
5
+ * nearest approximation of one.
6
+ */
7
+ export const DecimalAsStringOIDs = Object.freeze([
8
+ DataTypeOIDs.numeric,
9
+ DataTypeOIDs.money,
10
+ ]);
11
+ /** Whether this type's values are wanted as their exact decimal string. */
12
+ export function wantsDecimalString(options, oid) {
13
+ const v = options?.decimalAsString;
14
+ if (v === undefined || v === false)
15
+ return false;
16
+ return v === true || v.includes(oid);
17
+ }
18
+ export function validateDecimalAsString(v) {
19
+ if (v === undefined || typeof v === 'boolean')
20
+ return;
21
+ if (!Array.isArray(v))
22
+ throw new TypeError('`decimalAsString` is either a boolean or an array of OIDs, not ' +
23
+ typeof v);
24
+ const l = v.length;
25
+ let i;
26
+ for (i = 0; i < l; i++)
27
+ if (!DecimalAsStringOIDs.includes(v[i]))
28
+ throw new TypeError('`decimalAsString` accepts only numeric (1700) and money (790). ' +
29
+ `Received ${v[i]}${DataTypeNames[v[i]] ? ' (' + DataTypeNames[v[i]] + ')' : ''}.`);
30
+ }
@@ -0,0 +1,33 @@
1
+ import type { DataTypeMap } from '../data-type-map.js';
2
+ import type { FetchAsStringItem } from '../interfaces/data-mapping-options.js';
3
+ import type { Maybe, OID } from '../types.js';
4
+ /**
5
+ * What `fetchAsString`'s entries mean, in one place - three call sites
6
+ * read the list and they have to agree: the Bind's result format codes
7
+ * (resolve-column-formats.ts), the parser chosen for each column
8
+ * (get-parsers.ts), and whether a cached set of parsers was built for
9
+ * the same ask (fetchAsStringEqual, below).
10
+ */
11
+ /** Whether the list names this OID itself, in either form. */
12
+ export declare function fetchAsStringNamesOid(list: Maybe<FetchAsStringItem[]>, oid: OID): boolean;
13
+ /**
14
+ * Whether the list names the element type of an array column, and lets
15
+ * that reach the array - `{ oid, arrays: false }` is how a caller asks
16
+ * for a scalar column as text and leaves its array columns decoding.
17
+ *
18
+ * `numeric` is why: `pg` hands back a scalar `numeric` as a string and a
19
+ * `numeric[]` as numbers, so reproducing it needs the two said
20
+ * separately.
21
+ */
22
+ export declare function fetchAsStringNamesElement(list: Maybe<FetchAsStringItem[]>, elementsOID: OID): boolean;
23
+ /** Whether two lists select the same columns the same way. */
24
+ export declare function fetchAsStringEqual(a: Maybe<FetchAsStringItem[]>, b: Maybe<FetchAsStringItem[]>): boolean;
25
+ /**
26
+ * Refuses a selector that asks for something this does not do, rather
27
+ * than quietly not doing it.
28
+ *
29
+ * Checked once per list object, which is what a connection-level default
30
+ * or a module-level constant is; an inline list is a walk of a handful
31
+ * of entries.
32
+ */
33
+ export declare function validateFetchAsString(list: Maybe<FetchAsStringItem[]>, typeMap?: Maybe<DataTypeMap>): void;
@@ -0,0 +1,122 @@
1
+ import { DataTypeNames } from '../constants.js';
2
+ /**
3
+ * What `fetchAsString`'s entries mean, in one place - three call sites
4
+ * read the list and they have to agree: the Bind's result format codes
5
+ * (resolve-column-formats.ts), the parser chosen for each column
6
+ * (get-parsers.ts), and whether a cached set of parsers was built for
7
+ * the same ask (fetchAsStringEqual, below).
8
+ */
9
+ /** Whether the list names this OID itself, in either form. */
10
+ export function fetchAsStringNamesOid(list, oid) {
11
+ if (!list)
12
+ return false;
13
+ const l = list.length;
14
+ let i;
15
+ let item;
16
+ for (i = 0; i < l; i++) {
17
+ item = list[i];
18
+ if (typeof item === 'number' ? item === oid : item.oid === oid)
19
+ return true;
20
+ }
21
+ return false;
22
+ }
23
+ /**
24
+ * Whether the list names the element type of an array column, and lets
25
+ * that reach the array - `{ oid, arrays: false }` is how a caller asks
26
+ * for a scalar column as text and leaves its array columns decoding.
27
+ *
28
+ * `numeric` is why: `pg` hands back a scalar `numeric` as a string and a
29
+ * `numeric[]` as numbers, so reproducing it needs the two said
30
+ * separately.
31
+ */
32
+ export function fetchAsStringNamesElement(list, elementsOID) {
33
+ if (!list)
34
+ return false;
35
+ const l = list.length;
36
+ let i;
37
+ let item;
38
+ for (i = 0; i < l; i++) {
39
+ item = list[i];
40
+ if (typeof item === 'number') {
41
+ if (item === elementsOID)
42
+ return true;
43
+ }
44
+ else if (item.oid === elementsOID && item.arrays !== false)
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ /** Whether two lists select the same columns the same way. */
50
+ export function fetchAsStringEqual(a, b) {
51
+ if (a === b)
52
+ return true;
53
+ if (!a || !b)
54
+ return !a?.length && !b?.length;
55
+ const l = a.length;
56
+ if (l !== b.length)
57
+ return false;
58
+ let i;
59
+ let x;
60
+ let y;
61
+ for (i = 0; i < l; i++) {
62
+ x = a[i];
63
+ y = b[i];
64
+ if (x === y)
65
+ continue;
66
+ // A bare OID and a selector for the same OID are only the same ask
67
+ // while the selector says nothing; anything else decodes differently
68
+ // and must not be handed the other one's cached parsers.
69
+ if (typeof x === 'number' || typeof y === 'number') {
70
+ const num = typeof x === 'number' ? x : y;
71
+ const sel = (typeof x === 'number' ? y : x);
72
+ if (sel.oid !== num || sel.arrays === false || sel.ranges)
73
+ return false;
74
+ continue;
75
+ }
76
+ if (x.oid !== y.oid || (x.arrays === false) !== (y.arrays === false))
77
+ return false;
78
+ if (!!x.ranges !== !!y.ranges)
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ const _checked = new WeakSet();
84
+ /**
85
+ * Refuses a selector that asks for something this does not do, rather
86
+ * than quietly not doing it.
87
+ *
88
+ * Checked once per list object, which is what a connection-level default
89
+ * or a module-level constant is; an inline list is a walk of a handful
90
+ * of entries.
91
+ */
92
+ export function validateFetchAsString(list, typeMap) {
93
+ if (!list || _checked.has(list))
94
+ return;
95
+ const l = list.length;
96
+ let i;
97
+ let item;
98
+ for (i = 0; i < l; i++) {
99
+ item = list[i];
100
+ if (typeof item === 'number')
101
+ continue;
102
+ if (!item || typeof item.oid !== 'number')
103
+ throw new TypeError('`fetchAsString` takes an OID or `{ oid, arrays }`; received ' +
104
+ JSON.stringify(item));
105
+ if (item.ranges)
106
+ throw new TypeError('`fetchAsString` does not reach range columns through their ' +
107
+ "element's OID yet, so `ranges: true` would ask for something " +
108
+ 'that will not happen. Name the range type itself instead - ' +
109
+ '`numrange` rather than `numeric`.');
110
+ // The flags say what happens to the *arrays of* the named type, so
111
+ // naming an array type and then talking about its arrays is not an
112
+ // ask anything can honour.
113
+ if (typeMap?.get(item.oid)?.elementsOID)
114
+ throw new TypeError(`\`fetchAsString\` selector ${name(item.oid)} is an array type, and ` +
115
+ 'the flags describe what happens to a type’s array columns. ' +
116
+ 'Name the element type instead, or list the array OID on its own.');
117
+ }
118
+ _checked.add(list);
119
+ }
120
+ function name(oid) {
121
+ return DataTypeNames[oid] ? `${oid} (${DataTypeNames[oid]})` : String(oid);
122
+ }
@@ -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.includes(f.dataTypeId)) {
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,3 +1,4 @@
1
- export declare function parseDate(str: string, utc?: boolean): Date | number;
2
- export declare function parseDateTime(str: string, utc?: boolean): Date | number;
3
- export declare function parseDateTimeTz(str: string, utc?: boolean): Date | number;
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;
@@ -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
- const d = new Date(str);
53
- if (!isNaN(d.getTime()))
54
- return d;
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
- const native = new Date(str);
84
- if (!isNaN(native.getTime()))
85
- return native;
104
+ // See parseDateTime(): the engine's guess is only trusted where the
105
+ // session renders ISO.
106
+ if (!dateStyle) {
107
+ const native = new Date(str);
108
+ if (!isNaN(native.getTime()))
109
+ return native;
110
+ }
86
111
  const m = str.match(TIMESTAMP_PATTERN);
87
112
  if (!m)
88
113
  return noMatch(str);
@@ -1,7 +1,7 @@
1
1
  import type { DataTypeMap } from '../data-type-map.js';
2
2
  import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
3
3
  import { Protocol } from '../protocol/protocol.js';
4
- import type { Maybe, OID } from '../types.js';
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
- * A column matches on its own `dataTypeId`, so an array column is only
21
- * ever selected by its own array OID (`_timestamptz`), never by its
22
- * element's. Listing the element OID leaves array columns alone.
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
- * A column matches on its own `dataTypeId`, so an array column is only
29
- * ever selected by its own array OID (`_timestamptz`), never by its
30
- * element's. Listing the element OID leaves array columns alone.
41
+ * An array column is selected by its own array OID (`_timestamptz`) and
42
+ * by its element's (`timestamptz`) - both need the column as text, and
43
+ * which of the two was named is what get-parsers.ts reads to decide
44
+ * between the whole literal and the elements.
31
45
  *
32
46
  * Returns `columnFormat` untouched when there is nothing to do, so the
33
47
  * common case allocates nothing and keeps sending a single format code.
@@ -42,6 +56,10 @@ export function resolveColumnFormats(fields, options, typeMap) {
42
56
  const named = !!asString && asString.length > 0;
43
57
  if (!fields || (!named && !unknownAsString))
44
58
  return base;
59
+ // Both options do one thing: ask for a column as text. A row that is
60
+ // already all text has nothing left for them to ask.
61
+ if (base === DataFormat.text)
62
+ return base;
45
63
  const l = fields.length;
46
64
  const baseIsArray = Array.isArray(base);
47
65
  const out = new Array(l);
@@ -54,10 +72,16 @@ export function resolveColumnFormats(fields, options, typeMap) {
54
72
  format = baseIsArray
55
73
  ? (base[i] ?? DEFAULT_COLUMN_FORMAT)
56
74
  : base;
57
- if (named && asString.includes(oid)) {
75
+ if (named &&
76
+ (fetchAsStringNamesOid(asString, oid) ||
77
+ namesElementOf(typeMap, asString, oid))) {
58
78
  // fetchAsString names the value the caller wants verbatim, so it
59
79
  // overrides an explicit columnFormat for its own columns - the two
60
80
  // cannot both be honored and the OID list is the more specific ask.
81
+ // An array column is selected either way: by its own OID, which
82
+ // asks for the whole literal, or by its element's, which asks for
83
+ // the elements - both need the column as text, and get-parsers.ts
84
+ // is where the two part company.
61
85
  format = DataFormat.text;
62
86
  hit = true;
63
87
  }
@@ -72,23 +96,3 @@ export function resolveColumnFormats(fields, options, typeMap) {
72
96
  }
73
97
  return hit ? out : base;
74
98
  }
75
- /**
76
- * Whether two `fetchAsString` lists select the same columns. Used to tell
77
- * a cached set of parsers apart from one built for a different list, which
78
- * `columnFormat` alone cannot do: an explicitly text column and one turned
79
- * text by fetchAsString carry the same format code but decode differently.
80
- */
81
- export function fetchAsStringEqual(a, b) {
82
- if (a === b)
83
- return true;
84
- if (!a || !b)
85
- return !a?.length && !b?.length;
86
- const l = a.length;
87
- if (l !== b.length)
88
- return false;
89
- let i;
90
- for (i = 0; i < l; i++)
91
- if (a[i] !== b[i])
92
- return false;
93
- return true;
94
- }
@@ -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`. `'custom'`
44
- * when the caller supplied their own RowDecoder subclass - there's no way to
45
- * know what shape it returns - otherwise the same `'array' | 'object'` this
46
- * option has always reported.
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 {};
@@ -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`. `'custom'`
52
- * when the caller supplied their own RowDecoder subclass - there's no way to
53
- * know what shape it returns - otherwise the same `'array' | 'object'` this
54
- * option has always reported.
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
- const decoder = resolveRowDecoder(options);
58
- if (decoder instanceof ObjectRowDecoder)
59
- return 'object';
60
- if (decoder instanceof ArrayRowDecoder)
61
- return 'array';
62
- return 'custom';
77
+ return resolveRowDecoder(options).rowType;
63
78
  }
package/util/sql-tag.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { BindParam } from '../connection/bind-param.js';
2
2
  import { DataTypeMap, GlobalTypeMap } from '../data-type-map.js';
3
+ import { arrayLeaf } from './array-leaf.js';
3
4
  import { escapeIdentifier } from './escape-identifier.js';
4
5
  import { escapeLiteral } from './escape-literal.js';
5
6
  import { formatDateParam } from './format-datetime.js';
6
7
  import { stringifyArrayLiteral } from './stringify-arrayliteral.js';
7
- import { arrayLeaf, isUnspecifiedParam } from './unspecified-param.js';
8
+ import { isUnspecifiedParam } from './unspecified-param.js';
8
9
  /**
9
10
  * A statement built by the `sql` tag: its text with the values pulled out.
10
11
  *
@@ -14,11 +14,12 @@ export function stringifyArrayForSQL(v, options, encode) {
14
14
  * registered `encodeText()` that writes exactly the literal PostgreSQL
15
15
  * reads back, and `determine()` already knows which type each one is -
16
16
  * it is what the same value is sent as when it is bound as a parameter.
17
- * Without asking, all of them became `::json`, 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",...}`.
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;