postgrejs 3.9.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.
@@ -61,6 +61,16 @@ function splitHosts(value, fallbackPort) {
61
61
  return { host: entry, port: fallbackPort };
62
62
  });
63
63
  }
64
+ /**
65
+ * The schemes whose path is the database name.
66
+ *
67
+ * PostgreSQL gives two URI forms of its own, `postgres://` and
68
+ * `postgresql://`, and the longer one is what Prisma, TypeORM and most
69
+ * tutorials write - it was missing here, so a string naming a database
70
+ * connected to the default one instead, with nothing reported. `pg:`
71
+ * has been accepted for longer than either and stays.
72
+ */
73
+ const DATABASE_PATH_SCHEMES = ['pg:', 'postgres:', 'postgresql:'];
64
74
  export function parseConnectionString(str) {
65
75
  if (str.startsWith('/'))
66
76
  str = 'socket:/' + str;
@@ -89,7 +99,7 @@ export function parseConnectionString(str) {
89
99
  if (parsed.searchParams.get('db'))
90
100
  cfg.database = decodeURI(getFirst(parsed.searchParams.get('db')));
91
101
  }
92
- else if (parsed.protocol === 'pg:' || parsed.protocol === 'postgres:') {
102
+ else if (DATABASE_PATH_SCHEMES.includes(parsed.protocol)) {
93
103
  if (parsed.pathname)
94
104
  cfg.database = decodeURI(parsed.pathname.substring(1));
95
105
  }
@@ -0,0 +1,11 @@
1
+ import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
2
+ import type { Maybe, OID } from '../types.js';
3
+ /**
4
+ * The types that can decode to an exact decimal string: the two that
5
+ * carry a decimal the wire format states exactly, rather than a double's
6
+ * nearest approximation of one.
7
+ */
8
+ export declare const DecimalAsStringOIDs: readonly OID[];
9
+ /** Whether this type's values are wanted as their exact decimal string. */
10
+ export declare function wantsDecimalString(options: Maybe<DataMappingOptions>, oid: OID): boolean;
11
+ export declare function validateDecimalAsString(v: Maybe<boolean | OID[]>): void;
@@ -0,0 +1,30 @@
1
+ import { DataTypeNames, DataTypeOIDs } from '../constants.js';
2
+ /**
3
+ * The types that can decode to an exact decimal string: the two that
4
+ * carry a decimal the wire format states exactly, rather than a double's
5
+ * nearest approximation of one.
6
+ */
7
+ export const DecimalAsStringOIDs = Object.freeze([
8
+ DataTypeOIDs.numeric,
9
+ DataTypeOIDs.money,
10
+ ]);
11
+ /** Whether this type's values are wanted as their exact decimal string. */
12
+ export function wantsDecimalString(options, oid) {
13
+ const v = options?.decimalAsString;
14
+ if (v === undefined || v === false)
15
+ return false;
16
+ return v === true || v.includes(oid);
17
+ }
18
+ export function validateDecimalAsString(v) {
19
+ if (v === undefined || typeof v === 'boolean')
20
+ return;
21
+ if (!Array.isArray(v))
22
+ throw new TypeError('`decimalAsString` is either a boolean or an array of OIDs, not ' +
23
+ typeof v);
24
+ const l = v.length;
25
+ let i;
26
+ for (i = 0; i < l; i++)
27
+ if (!DecimalAsStringOIDs.includes(v[i]))
28
+ throw new TypeError('`decimalAsString` accepts only numeric (1700) and money (790). ' +
29
+ `Received ${v[i]}${DataTypeNames[v[i]] ? ' (' + DataTypeNames[v[i]] + ')' : ''}.`);
30
+ }
@@ -0,0 +1,33 @@
1
+ import type { DataTypeMap } from '../data-type-map.js';
2
+ import type { FetchAsStringItem } from '../interfaces/data-mapping-options.js';
3
+ import type { Maybe, OID } from '../types.js';
4
+ /**
5
+ * What `fetchAsString`'s entries mean, in one place - three call sites
6
+ * read the list and they have to agree: the Bind's result format codes
7
+ * (resolve-column-formats.ts), the parser chosen for each column
8
+ * (get-parsers.ts), and whether a cached set of parsers was built for
9
+ * the same ask (fetchAsStringEqual, below).
10
+ */
11
+ /** Whether the list names this OID itself, in either form. */
12
+ export declare function fetchAsStringNamesOid(list: Maybe<FetchAsStringItem[]>, oid: OID): boolean;
13
+ /**
14
+ * Whether the list names the element type of an array column, and lets
15
+ * that reach the array - `{ oid, arrays: false }` is how a caller asks
16
+ * for a scalar column as text and leaves its array columns decoding.
17
+ *
18
+ * `numeric` is why: `pg` hands back a scalar `numeric` as a string and a
19
+ * `numeric[]` as numbers, so reproducing it needs the two said
20
+ * separately.
21
+ */
22
+ export declare function fetchAsStringNamesElement(list: Maybe<FetchAsStringItem[]>, elementsOID: OID): boolean;
23
+ /** Whether two lists select the same columns the same way. */
24
+ export declare function fetchAsStringEqual(a: Maybe<FetchAsStringItem[]>, b: Maybe<FetchAsStringItem[]>): boolean;
25
+ /**
26
+ * Refuses a selector that asks for something this does not do, rather
27
+ * than quietly not doing it.
28
+ *
29
+ * Checked once per list object, which is what a connection-level default
30
+ * or a module-level constant is; an inline list is a walk of a handful
31
+ * of entries.
32
+ */
33
+ export declare function validateFetchAsString(list: Maybe<FetchAsStringItem[]>, typeMap?: Maybe<DataTypeMap>): void;
@@ -0,0 +1,122 @@
1
+ import { DataTypeNames } from '../constants.js';
2
+ /**
3
+ * What `fetchAsString`'s entries mean, in one place - three call sites
4
+ * read the list and they have to agree: the Bind's result format codes
5
+ * (resolve-column-formats.ts), the parser chosen for each column
6
+ * (get-parsers.ts), and whether a cached set of parsers was built for
7
+ * the same ask (fetchAsStringEqual, below).
8
+ */
9
+ /** Whether the list names this OID itself, in either form. */
10
+ export function fetchAsStringNamesOid(list, oid) {
11
+ if (!list)
12
+ return false;
13
+ const l = list.length;
14
+ let i;
15
+ let item;
16
+ for (i = 0; i < l; i++) {
17
+ item = list[i];
18
+ if (typeof item === 'number' ? item === oid : item.oid === oid)
19
+ return true;
20
+ }
21
+ return false;
22
+ }
23
+ /**
24
+ * Whether the list names the element type of an array column, and lets
25
+ * that reach the array - `{ oid, arrays: false }` is how a caller asks
26
+ * for a scalar column as text and leaves its array columns decoding.
27
+ *
28
+ * `numeric` is why: `pg` hands back a scalar `numeric` as a string and a
29
+ * `numeric[]` as numbers, so reproducing it needs the two said
30
+ * separately.
31
+ */
32
+ export function fetchAsStringNamesElement(list, elementsOID) {
33
+ if (!list)
34
+ return false;
35
+ const l = list.length;
36
+ let i;
37
+ let item;
38
+ for (i = 0; i < l; i++) {
39
+ item = list[i];
40
+ if (typeof item === 'number') {
41
+ if (item === elementsOID)
42
+ return true;
43
+ }
44
+ else if (item.oid === elementsOID && item.arrays !== false)
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ /** Whether two lists select the same columns the same way. */
50
+ export function fetchAsStringEqual(a, b) {
51
+ if (a === b)
52
+ return true;
53
+ if (!a || !b)
54
+ return !a?.length && !b?.length;
55
+ const l = a.length;
56
+ if (l !== b.length)
57
+ return false;
58
+ let i;
59
+ let x;
60
+ let y;
61
+ for (i = 0; i < l; i++) {
62
+ x = a[i];
63
+ y = b[i];
64
+ if (x === y)
65
+ continue;
66
+ // A bare OID and a selector for the same OID are only the same ask
67
+ // while the selector says nothing; anything else decodes differently
68
+ // and must not be handed the other one's cached parsers.
69
+ if (typeof x === 'number' || typeof y === 'number') {
70
+ const num = typeof x === 'number' ? x : y;
71
+ const sel = (typeof x === 'number' ? y : x);
72
+ if (sel.oid !== num || sel.arrays === false || sel.ranges)
73
+ return false;
74
+ continue;
75
+ }
76
+ if (x.oid !== y.oid || (x.arrays === false) !== (y.arrays === false))
77
+ return false;
78
+ if (!!x.ranges !== !!y.ranges)
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ const _checked = new WeakSet();
84
+ /**
85
+ * Refuses a selector that asks for something this does not do, rather
86
+ * than quietly not doing it.
87
+ *
88
+ * Checked once per list object, which is what a connection-level default
89
+ * or a module-level constant is; an inline list is a walk of a handful
90
+ * of entries.
91
+ */
92
+ export function validateFetchAsString(list, typeMap) {
93
+ if (!list || _checked.has(list))
94
+ return;
95
+ const l = list.length;
96
+ let i;
97
+ let item;
98
+ for (i = 0; i < l; i++) {
99
+ item = list[i];
100
+ if (typeof item === 'number')
101
+ continue;
102
+ if (!item || typeof item.oid !== 'number')
103
+ throw new TypeError('`fetchAsString` takes an OID or `{ oid, arrays }`; received ' +
104
+ JSON.stringify(item));
105
+ if (item.ranges)
106
+ throw new TypeError('`fetchAsString` does not reach range columns through their ' +
107
+ "element's OID yet, so `ranges: true` would ask for something " +
108
+ 'that will not happen. Name the range type itself instead - ' +
109
+ '`numrange` rather than `numeric`.');
110
+ // The flags say what happens to the *arrays of* the named type, so
111
+ // naming an array type and then talking about its arrays is not an
112
+ // ask anything can honour.
113
+ if (typeMap?.get(item.oid)?.elementsOID)
114
+ throw new TypeError(`\`fetchAsString\` selector ${name(item.oid)} is an array type, and ` +
115
+ 'the flags describe what happens to a type’s array columns. ' +
116
+ 'Name the element type instead, or list the array OID on its own.');
117
+ }
118
+ _checked.add(list);
119
+ }
120
+ function name(oid) {
121
+ return DataTypeNames[oid] ? `${oid} (${DataTypeNames[oid]})` : String(oid);
122
+ }
@@ -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,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.
@@ -58,10 +72,16 @@ export function resolveColumnFormats(fields, options, typeMap) {
58
72
  format = baseIsArray
59
73
  ? (base[i] ?? DEFAULT_COLUMN_FORMAT)
60
74
  : base;
61
- if (named && asString.includes(oid)) {
75
+ if (named &&
76
+ (fetchAsStringNamesOid(asString, oid) ||
77
+ namesElementOf(typeMap, asString, oid))) {
62
78
  // fetchAsString names the value the caller wants verbatim, so it
63
79
  // overrides an explicit columnFormat for its own columns - the two
64
80
  // cannot both be honored and the OID list is the more specific ask.
81
+ // An array column is selected either way: by its own OID, which
82
+ // asks for the whole literal, or by its element's, which asks for
83
+ // the elements - both need the column as text, and get-parsers.ts
84
+ // is where the two part company.
65
85
  format = DataFormat.text;
66
86
  hit = true;
67
87
  }
@@ -76,23 +96,3 @@ export function resolveColumnFormats(fields, options, typeMap) {
76
96
  }
77
97
  return hit ? out : base;
78
98
  }
79
- /**
80
- * Whether two `fetchAsString` lists select the same columns. Used to tell
81
- * a cached set of parsers apart from one built for a different list, which
82
- * `columnFormat` alone cannot do: an explicitly text column and one turned
83
- * text by fetchAsString carry the same format code but decode differently.
84
- */
85
- export function fetchAsStringEqual(a, b) {
86
- if (a === b)
87
- return true;
88
- if (!a || !b)
89
- return !a?.length && !b?.length;
90
- const l = a.length;
91
- if (l !== b.length)
92
- return false;
93
- let i;
94
- for (i = 0; i < l; i++)
95
- if (a[i] !== b[i])
96
- return false;
97
- return true;
98
- }
@@ -14,11 +14,12 @@ export function stringifyArrayForSQL(v, options, encode) {
14
14
  * registered `encodeText()` that writes exactly the literal PostgreSQL
15
15
  * reads back, and `determine()` already knows which type each one is -
16
16
  * it is what the same value is sent as when it is bound as a parameter.
17
- * Without asking, all of them became `::json`, 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;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Reaching the `Temporal` the application provides, without depending on
3
+ * one.
4
+ *
5
+ * No runtime ships Temporal yet - Node 24.15 and Bun 1.3.10 both answer
6
+ * `undefined`, with or without `--harmony` - so today every caller that
7
+ * turns `temporalTypes` on has installed a polyfill. That is why this is
8
+ * read off `globalThis` when it is needed rather than imported: this
9
+ * package takes no dependency, a polyfill loaded after this module is
10
+ * still seen, and the day a runtime ships Temporal nothing here changes.
11
+ */
12
+ let _systemTimeZone;
13
+ const _knownZones = new Set();
14
+ /**
15
+ * The runtime's `Temporal`, or an error saying how to get one.
16
+ *
17
+ * Called when a type map is built - which for a connection is while it
18
+ * is being constructed - so a missing Temporal is reported before any
19
+ * I/O rather than in the middle of decoding a row.
20
+ */
21
+ export function getTemporal() {
22
+ const t = globalThis.Temporal;
23
+ if (!t || !t.ZonedDateTime)
24
+ throw new Error('Temporal is not available in this runtime. postgrejs reads ' +
25
+ 'globalThis.Temporal and takes no dependency of its own: install ' +
26
+ '`temporal-polyfill` and import it before connecting, or leave ' +
27
+ '`temporalTypes` off.');
28
+ return t;
29
+ }
30
+ /**
31
+ * The zone a `timestamptz` is printed in when the caller named none.
32
+ *
33
+ * Only the printing: a timestamptz is an absolute instant and arrives as
34
+ * one, so the zone decides what wall clock the ZonedDateTime shows and
35
+ * nothing about which moment it is.
36
+ */
37
+ export function systemTimeZone() {
38
+ if (_systemTimeZone === undefined)
39
+ _systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
40
+ return _systemTimeZone;
41
+ }
42
+ /**
43
+ * Checks a zone name before it is handed to Temporal, so the complaint
44
+ * names the setting it came from instead of surfacing from inside a
45
+ * decode. `Intl` accepts exactly what Temporal does and is here whether
46
+ * Temporal is or not.
47
+ */
48
+ export function requireTimeZone(zone, where) {
49
+ if (_knownZones.has(zone))
50
+ return zone;
51
+ try {
52
+ new Intl.DateTimeFormat('en-US', { timeZone: zone });
53
+ }
54
+ catch {
55
+ throw new Error(`"${zone}" (${where}) is not a time zone this runtime knows, so a ` +
56
+ 'timestamptz cannot be given one. Pass `timeZone` in the mapping ' +
57
+ 'options to name one yourself.');
58
+ }
59
+ _knownZones.add(zone);
60
+ return zone;
61
+ }
@@ -20,8 +20,23 @@
20
20
  * as it does under `pg`. A cast (`$1::text`) or a named type
21
21
  * (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
22
22
  *
23
- * 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.
23
+ * - An array of numbers is the same question, and the answer matters
24
+ * more: `[1, 2]` is `int2[]`, `int4[]`, `int8[]`, `numeric[]`,
25
+ * `float4[]` or `float8[]` depending on where it lands, and unlike
26
+ * their scalars those types have no operators or implicit casts
27
+ * between them. A declared `int4[]` is therefore not merely a guess
28
+ * but a wrong answer wherever the column is one of the other five:
29
+ * `array[1,2]::int8[] = $1` is `42883 operator does not exist:
30
+ * bigint[] = integer[]`, and `numeric[] = double precision[]` the
31
+ * same. The scalars are left declared because they do have those
32
+ * operators - `1::int8 = $1` and `1.5::numeric = $1` both resolve.
33
+ *
34
+ * A scalar number, a boolean, a Buffer, and an array of anything but
35
+ * numbers keep their declared types: an array of booleans, of Buffers
36
+ * or of one of this client's own classes has one type it can be, so
37
+ * naming it says nothing the server would have decided differently -
38
+ * and it keeps the binary encoding, which the text literal gives up.
39
+ * A caller who wants that back for a large numeric array names the type
40
+ * with `new BindParam(DataTypeOIDs._float8, v)`.
26
41
  */
27
42
  export declare function isUnspecifiedParam(v: any): boolean;
@@ -21,11 +21,28 @@ import { arrayLeaf } from './array-leaf.js';
21
21
  * as it does under `pg`. A cast (`$1::text`) or a named type
22
22
  * (`new BindParam(DataTypeOIDs.varchar, v)`) says which type is meant.
23
23
  *
24
- * 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.
24
+ * - An array of numbers is the same question, and the answer matters
25
+ * more: `[1, 2]` is `int2[]`, `int4[]`, `int8[]`, `numeric[]`,
26
+ * `float4[]` or `float8[]` depending on where it lands, and unlike
27
+ * their scalars those types have no operators or implicit casts
28
+ * between them. A declared `int4[]` is therefore not merely a guess
29
+ * but a wrong answer wherever the column is one of the other five:
30
+ * `array[1,2]::int8[] = $1` is `42883 operator does not exist:
31
+ * bigint[] = integer[]`, and `numeric[] = double precision[]` the
32
+ * same. The scalars are left declared because they do have those
33
+ * operators - `1::int8 = $1` and `1.5::numeric = $1` both resolve.
34
+ *
35
+ * A scalar number, a boolean, a Buffer, and an array of anything but
36
+ * numbers keep their declared types: an array of booleans, of Buffers
37
+ * or of one of this client's own classes has one type it can be, so
38
+ * naming it says nothing the server would have decided differently -
39
+ * and it keeps the binary encoding, which the text literal gives up.
40
+ * A caller who wants that back for a large numeric array names the type
41
+ * with `new BindParam(DataTypeOIDs._float8, v)`.
27
42
  */
28
43
  export function isUnspecifiedParam(v) {
29
44
  const x = arrayLeaf(v);
30
- return typeof x === 'string' || x instanceof Date;
45
+ if (typeof x === 'string' || x instanceof Date)
46
+ return true;
47
+ return Array.isArray(v) && (typeof x === 'number' || typeof x === 'bigint');
31
48
  }