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
@@ -1,5 +1,124 @@
1
1
  import type { OID } from '../types.js';
2
+ import type { PgDateStyle } from '../util/date-style.js';
3
+ /** How a server renders `money` - see `DataMappingOptions.moneyFormat`. */
4
+ export interface MoneyFormat {
5
+ /** Fraction digits: 2 for `$1.00`, 0 for `¥1`, 3 for a dinar. */
6
+ scale: number;
7
+ /** The decimal separator the server prints, `.` or `,`. */
8
+ decimalSeparator: string;
9
+ }
10
+ /**
11
+ * One entry of `fetchAsString`: an OID on its own, or an OID with what
12
+ * it should reach.
13
+ */
14
+ export type FetchAsStringItem = OID | FetchAsStringSelector;
15
+ /** An OID named by `fetchAsString`, with what it reaches. */
16
+ export interface FetchAsStringSelector {
17
+ oid: OID;
18
+ /**
19
+ * Whether naming a scalar type also asks for columns of *arrays* of
20
+ * it - which is what naming it on its own does, so this defaults to
21
+ * `true`.
22
+ *
23
+ * `false` is for reproducing something that is inconsistent on the
24
+ * other side: `pg` hands back a scalar `numeric` as a string and a
25
+ * `numeric[]` as numbers, and a `numeric[]` column already decodes
26
+ * that way here, so only the scalar wants asking for as text.
27
+ */
28
+ arrays?: boolean;
29
+ /**
30
+ * Reserved. An element OID does not reach range columns today - a
31
+ * `numrange` column is selected by naming `numrange` - so `false` is
32
+ * the only value this can be given, and `true` is refused rather than
33
+ * ignored.
34
+ */
35
+ ranges?: boolean;
36
+ }
2
37
  export interface DataMappingOptions {
38
+ /**
39
+ * Decode `numeric` and `money` into the exact decimal string they
40
+ * carry, instead of a number.
41
+ *
42
+ * `true` takes both; an array takes only the ones it names, and may
43
+ * name only those two (`DecimalAsStringOIDs`). Their array, range and
44
+ * multirange types follow, since those decode through the element.
45
+ *
46
+ * The string is the value and nothing else: an optional leading `-`,
47
+ * the digits, and - for money - a decimal point with exactly the scale
48
+ * the server reported. No currency symbol, no thousands separator, no
49
+ * locale. That is what tells it apart from
50
+ * `fetchAsString: [DataTypeOIDs.money]`, which asks the *server* for
51
+ * its own rendering and gets `-$1,234.50` back, because `lc_monetary`
52
+ * is what writes a money value out.
53
+ *
54
+ * There is no conversion behind this: both types build the exact
55
+ * decimal while decoding and then decide whether a double can carry it
56
+ * (a `Numeric` when it cannot). This hands back that same string, so
57
+ * nothing is rounded, re-parsed or formatted a second time - which is
58
+ * the thing a caller doing it themselves cannot avoid, and cannot get
59
+ * right for money without knowing the scale.
60
+ *
61
+ * `numeric`'s non-finite values keep the spelling PostgreSQL writes:
62
+ * `'NaN'`, `'Infinity'`, `'-Infinity'`.
63
+ *
64
+ * Off by default.
65
+ */
66
+ decimalAsString?: boolean | OID[];
67
+ /**
68
+ * Decode PostgreSQL's date/time types into `Temporal` values instead
69
+ * of `Date`.
70
+ *
71
+ * `true` takes all five - `date`, `time`, `timestamp`, `timestamptz`
72
+ * and `interval`; an array takes only the ones it names, and may name
73
+ * only those five (`DataTypeOIDs.timestamptz`, and so on - see
74
+ * `TemporalCapableOIDs`). Each one selected brings its own array and
75
+ * range types with it, because those carry a copy of the element's
76
+ * decoders rather than following it.
77
+ *
78
+ * This is decoding only: every type still accepts as a *parameter*
79
+ * everything it accepted before, so the `Date`s and strings already
80
+ * being passed keep working.
81
+ *
82
+ * What it buys: the microseconds PostgreSQL stores and a `Date` cannot
83
+ * hold, a `timestamp` that says it has no zone instead of guessing one
84
+ * from `utcDates`, and a `date` that stays a date. What it costs: no
85
+ * runtime ships `Temporal` yet, so an application turning this on
86
+ * installs a polyfill (`temporal-polyfill`) and imports it before
87
+ * connecting; building these values is also dearer than `new Date`.
88
+ *
89
+ * Off by default.
90
+ */
91
+ temporalTypes?: boolean | OID[];
92
+ /**
93
+ * The time zone a `timestamptz` is given when it is decoded as a
94
+ * `Temporal.ZonedDateTime` - see `temporalTypes`.
95
+ *
96
+ * A connection fills this in from the `TimeZone` the server reports,
97
+ * so a caller normally never sets it; set it to print those values in
98
+ * a zone of your own choosing. It decides only what wall clock the
99
+ * value shows: a timestamptz is an absolute instant and is the same
100
+ * moment in every zone.
101
+ */
102
+ timeZone?: string;
103
+ /**
104
+ * How the server renders dates, when it is not rendering them in ISO.
105
+ *
106
+ * Only the text wire format is affected - binary carries no formatting
107
+ * at all - and the connection fills this in from the `DateStyle` the
108
+ * server reports, so a caller normally never sets it. See
109
+ * `util/date-style.ts` for what each setting prints.
110
+ */
111
+ dateStyle?: PgDateStyle;
112
+ /**
113
+ * How the server renders money, which is what `money`'s wire format
114
+ * cannot say on its own: it is an int64 of the smallest currency unit,
115
+ * and how many of those make one is `lc_monetary` - a setting the
116
+ * server does not report. A connection asks it once on its way up (see
117
+ * `IntlConnection.ensureMoneyFormat()`) and fills this in; set it here
118
+ * to skip that question, or to read a value rendered by a different
119
+ * server than the one at hand.
120
+ */
121
+ moneyFormat?: MoneyFormat;
3
122
  /**
4
123
  * If true UTC time will be used for date decoding, else system time offset will be used
5
124
  * @default false
@@ -17,11 +136,14 @@ export interface DataMappingOptions {
17
136
  * a query whose statement is not prepared asks for the whole row as text
18
137
  * instead (see IntlConnection.queryOnce()).
19
138
  *
20
- * An array column is selected by its own array OID (`_timestamptz`),
21
- * never by its element's, and then the whole array literal is the string
22
- * that comes back.
139
+ * An array column can be named either way, and the two ask for
140
+ * different things. Its own array OID (`_timestamptz`) asks for the
141
+ * whole literal, one string. Its *element's* OID (`timestamptz`) asks
142
+ * for the elements: an array, of the strings the server wrote, with
143
+ * the nulls still null - the same thing naming that OID does for a
144
+ * column of one.
23
145
  */
24
- fetchAsString?: OID[];
146
+ fetchAsString?: FetchAsStringItem[];
25
147
  /**
26
148
  * Ask the server for text on any column this client has no way to
27
149
  * decode, instead of taking bytes it cannot read.
@@ -2,6 +2,8 @@ import type { PoolConfiguration as LPoolConfiguration } from 'lightning-pool';
2
2
  import type { ConnectionOptions as TlsConnectionOptions } from 'tls';
3
3
  import type { SmartBufferConfig } from '../protocol/smart-buffer.js';
4
4
  import type { DebugLogger } from '../types.js';
5
+ import type { DataMappingOptions } from './data-mapping-options.js';
6
+ import type { QueryOptions } from './query-options.js';
5
7
  export interface DatabaseConnectionParams {
6
8
  host?: string;
7
9
  port?: number;
@@ -147,8 +149,32 @@ export type TargetSessionAttrs = 'read-write' | 'read-only' | 'primary' | 'stand
147
149
  export interface SocketOptions {
148
150
  keepAlive?: boolean;
149
151
  }
150
- export interface ConnectionConfiguration extends DatabaseConnectionParams, SocketOptions {
152
+ /**
153
+ * The data-mapping options a connection can answer for every statement
154
+ * on it, so a caller that always wants the same shape says so once.
155
+ *
156
+ * Each is the default for `QueryOptions`'s field of the same name and
157
+ * is described there; a value on the call always wins. `fetchAsString`
158
+ * and `unknownTypesAsString` are the two with a cost worth knowing
159
+ * about - see `fetchAsString` in QueryOptions.
160
+ */
161
+ export type ConnectionMappingDefaults = DataMappingOptions & Pick<QueryOptions, 'objectRows' | 'rowDecoder' | 'typeMap' | 'columnFormat'>;
162
+ export interface ConnectionConfiguration extends DatabaseConnectionParams, SocketOptions, ConnectionMappingDefaults {
151
163
  buffer?: SmartBufferConfig;
164
+ /**
165
+ * Whether statements may share a connection with statements already in
166
+ * flight - the default for every call here, which
167
+ * `QueryOptions.pipeline` overrides per statement and describes in
168
+ * full.
169
+ *
170
+ * Defaults to **on for a `Connection`** (what one has always done) and
171
+ * **off for a `Pool`**, where sharing also means running on a
172
+ * connection other callers are using. `true` on a pool turns it on for
173
+ * every query that can take it; `false` on a connection gives the
174
+ * behaviour of a client that waits for each reply before sending the
175
+ * next statement.
176
+ */
177
+ pipeline?: boolean;
152
178
  }
153
179
  export interface PoolConfiguration extends ConnectionConfiguration, LPoolConfiguration {
154
180
  /**
@@ -162,10 +188,11 @@ export interface PoolConfiguration extends ConnectionConfiguration, LPoolConfigu
162
188
  * ceiling on connections - with the default pool of 10, a burst of 1000
163
189
  * queries no longer has to run as 100 sequential rounds of 10.
164
190
  *
165
- * Set to 1 for the older behaviour, where Pool.query() holds a
166
- * connection exclusively for the duration of each query. Connections
167
- * handed out by acquire() are never shared, whatever this is set to, so
168
- * transactions, cursors and prepared statements are unaffected.
191
+ * Only queries that asked to be pipelined are counted, since sharing a
192
+ * pooled connection is opt-in - see `pipeline`. Setting this to 1 is
193
+ * the other way to turn it off. Connections handed out by acquire()
194
+ * are never shared whatever either is set to, so transactions, cursors
195
+ * and prepared statements are unaffected.
169
196
  */
170
197
  pipelineMaxQueries?: number;
171
198
  /**
@@ -9,6 +9,38 @@ export interface QueryOptions extends DataMappingOptions {
9
9
  * @default true
10
10
  */
11
11
  autoCommit?: boolean;
12
+ /**
13
+ * Whether this statement may share a connection with statements that
14
+ * are already in flight.
15
+ *
16
+ * PostgreSQL correlates responses to requests by order, so several
17
+ * statements can be on the wire at once, each under its own Sync and
18
+ * therefore with its own error boundary.
19
+ *
20
+ * **The default differs by where the statement runs, because the two
21
+ * places are not the same question:**
22
+ *
23
+ * - On a `Connection`, **on**. The connection is the caller's own, and
24
+ * concurrent `query()` calls on one have always overlapped this way.
25
+ * - On a `Pool`, **off**. Here it also decides *which* connection the
26
+ * statement runs on: sharing puts it on a connection other callers
27
+ * are using, where session state - `SET`, an advisory lock, a
28
+ * temporary table - is no longer the caller's alone. That is opt-in,
29
+ * per call or with `pipeline: true` on the pool.
30
+ *
31
+ * `false` asks for the wire to itself: the statement waits until
32
+ * nothing else is running on the connection, and nothing else starts
33
+ * until it finishes, whichever mode those asked for. Nothing orders
34
+ * two pipelined statements against each other, so this is how a
35
+ * statement that depends on what another one left behind says so.
36
+ *
37
+ * Some statements never share a *pooled* connection however this is
38
+ * set, because they need one to themselves for longer than the call:
39
+ * anything carrying an `AbortSignal`, anything with
40
+ * `autoCommit: false`, a cursor, and SQL that opens a transaction or a
41
+ * COPY. On a `Connection` the setting is only about the wire.
42
+ */
43
+ pipeline?: boolean;
12
44
  /**
13
45
  * Specifies if rows will be fetched as <FieldName, Value> pair objects or array of values
14
46
  * @deprecated Use `rowDecoder: 'object'` instead - `objectRows: true` still works exactly
@@ -7,6 +7,38 @@ export interface ScriptExecuteOptions extends DataMappingOptions {
7
7
  * @default true
8
8
  */
9
9
  autoCommit?: boolean;
10
+ /**
11
+ * Whether this statement may share a connection with statements that
12
+ * are already in flight.
13
+ *
14
+ * PostgreSQL correlates responses to requests by order, so several
15
+ * statements can be on the wire at once, each under its own Sync and
16
+ * therefore with its own error boundary.
17
+ *
18
+ * **The default differs by where the statement runs, because the two
19
+ * places are not the same question:**
20
+ *
21
+ * - On a `Connection`, **on**. The connection is the caller's own, and
22
+ * concurrent `query()` calls on one have always overlapped this way.
23
+ * - On a `Pool`, **off**. Here it also decides *which* connection the
24
+ * statement runs on: sharing puts it on a connection other callers
25
+ * are using, where session state - `SET`, an advisory lock, a
26
+ * temporary table - is no longer the caller's alone. That is opt-in,
27
+ * per call or with `pipeline: true` on the pool.
28
+ *
29
+ * `false` asks for the wire to itself: the statement waits until
30
+ * nothing else is running on the connection, and nothing else starts
31
+ * until it finishes, whichever mode those asked for. Nothing orders
32
+ * two pipelined statements against each other, so this is how a
33
+ * statement that depends on what another one left behind says so.
34
+ *
35
+ * Some statements never share a *pooled* connection however this is
36
+ * set, because they need one to themselves for longer than the call:
37
+ * anything carrying an `AbortSignal`, anything with
38
+ * `autoCommit: false`, a cursor, and SQL that opens a transaction or a
39
+ * COPY. On a `Connection` the setting is only about the wire.
40
+ */
41
+ pipeline?: boolean;
10
42
  /**
11
43
  * Specifies if rows will be fetched as <FieldName, Value> pair objects or array of values
12
44
  * @deprecated Use `rowDecoder: 'object'` instead - `objectRows: true` still works exactly
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The modes PostgreSQL takes on the `BEGIN` itself.
3
+ *
4
+ * Setting them this way is one round trip where `BEGIN` followed by
5
+ * `SET TRANSACTION` is two - measured at 0.500ms against 0.740ms for
6
+ * opening and closing one isolated transaction, which is most of what a
7
+ * short transaction costs.
8
+ *
9
+ * `SET TRANSACTION SNAPSHOT` is deliberately absent: PostgreSQL does not
10
+ * accept it on `BEGIN`, so it still needs a statement of its own.
11
+ */
12
+ export interface TransactionOptions {
13
+ /**
14
+ * What this transaction may see of the ones running beside it.
15
+ * Defaults to the server's `default_transaction_isolation`, which is
16
+ * `read committed` unless the server says otherwise.
17
+ */
18
+ isolationLevel?: 'serializable' | 'repeatable read' | 'read committed' | 'read uncommitted';
19
+ /** Refuses anything that would write, at the server. */
20
+ readOnly?: boolean;
21
+ /**
22
+ * Lets a `serializable` `readOnly` transaction wait until it can run
23
+ * without the serialization failures that level otherwise risks -
24
+ * ignored by the server for any other combination.
25
+ */
26
+ deferrable?: boolean;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "postgrejs",
3
3
  "description": "Professional PostgreSQL client NodeJS",
4
- "version": "3.8.0",
4
+ "version": "3.10.0",
5
5
  "author": "Panates",
6
6
  "license": "BSD-3-Clause",
7
7
  "dependencies": {
@@ -16,6 +16,21 @@ export declare class DatabaseError extends Error {
16
16
  * explicit transaction was already open they are rolled back with it.
17
17
  */
18
18
  batchResults?: BatchCommandResult[];
19
+ /**
20
+ * The message exactly as PostgreSQL sent it.
21
+ *
22
+ * `message` is decorated where the server reported a position - the
23
+ * line and column, the offending source line and a caret under it -
24
+ * which is what makes an error readable in a terminal with no extra
25
+ * work, and would otherwise leave nothing holding the original. A
26
+ * consumer that parses the text needs the original: PostgreSQL does
27
+ * not always put the interesting part in a field, so an anchored
28
+ * pattern like `/^column (.+) does not exist$/` is how the column name
29
+ * is read, and a suffix defeats it.
30
+ *
31
+ * Always set, and equal to `message` when nothing was decorated.
32
+ */
33
+ readonly serverMessage: string;
19
34
  severity?: string;
20
35
  code?: string;
21
36
  detail?: string;
@@ -15,6 +15,21 @@ export class DatabaseError extends Error {
15
15
  * explicit transaction was already open they are rolled back with it.
16
16
  */
17
17
  batchResults;
18
+ /**
19
+ * The message exactly as PostgreSQL sent it.
20
+ *
21
+ * `message` is decorated where the server reported a position - the
22
+ * line and column, the offending source line and a caret under it -
23
+ * which is what makes an error readable in a terminal with no extra
24
+ * work, and would otherwise leave nothing holding the original. A
25
+ * consumer that parses the text needs the original: PostgreSQL does
26
+ * not always put the interesting part in a field, so an anchored
27
+ * pattern like `/^column (.+) does not exist$/` is how the column name
28
+ * is read, and a suffix defeats it.
29
+ *
30
+ * Always set, and equal to `message` when nothing was decorated.
31
+ */
32
+ serverMessage;
18
33
  severity;
19
34
  code;
20
35
  detail;
@@ -41,5 +56,9 @@ export class DatabaseError extends Error {
41
56
  });
42
57
  if (msg.position)
43
58
  this.position = parseInt(msg.position, 10) || undefined;
59
+ // Read off `message` rather than off `msg`, and after the assign
60
+ // above, so the two cannot drift apart - whatever this error was
61
+ // built to say, before anything decorates it.
62
+ this.serverMessage = this.message;
44
63
  }
45
64
  }
@@ -1,9 +1,9 @@
1
1
  import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
2
2
  import { DataTypeMap } from '../data-type-map.js';
3
+ import { arrayLeaf } from '../util/array-leaf.js';
3
4
  import { encodeBinaryArray } from '../util/encode-binaryarray.js';
4
5
  import { formatDateParam } from '../util/format-datetime.js';
5
6
  import { stringifyArrayLiteral } from '../util/stringify-arrayliteral.js';
6
- import { arrayLeaf } from '../util/unspecified-param.js';
7
7
  import { Protocol } from './protocol.js';
8
8
  import { SmartBuffer } from './smart-buffer.js';
9
9
  const DataFormat = Protocol.DataFormat;
@@ -899,16 +899,13 @@ export class PgSocket extends SafeEventEmitter {
899
899
  socket.write(data, cb);
900
900
  return true;
901
901
  }
902
- socket.cork();
903
- try {
904
- const l = data.length;
905
- let i;
906
- for (i = 0; i < l; i++)
907
- socket.write(data[i], i === l - 1 ? cb : undefined);
908
- }
909
- finally {
910
- socket.uncork();
911
- }
902
+ // Joined rather than written one by one under a cork: a statement is
903
+ // three messages (Bind, Execute, Sync) of a few dozen bytes, and a
904
+ // write apiece costs three trips through the stream machinery - a
905
+ // WriteReq, a buffer entry, a cork/uncork pair - to save a copy of
906
+ // 43 bytes. Measured at 50 concurrent queries, 150 writes against
907
+ // 50.
908
+ socket.write(data.length === 1 ? data[0] : Buffer.concat(data), cb);
912
909
  return true;
913
910
  }
914
911
  /**
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The first value inside `v` that is neither an array nor null - what an
3
+ * array's element type is read from. A scalar answers itself, and an
4
+ * array with nothing but nulls (or nothing at all) answers `undefined`.
5
+ *
6
+ * Reading `v[0]` instead is wrong twice over: `[[1, 2], [3, 4]]` asks
7
+ * what type `[1, 2]` is, and `[null, 2, 3]` asks what type `null` is.
8
+ * Neither has an answer, and both are ordinary values to send.
9
+ */
10
+ export declare function arrayLeaf(v: any): any;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The first value inside `v` that is neither an array nor null - what an
3
+ * array's element type is read from. A scalar answers itself, and an
4
+ * array with nothing but nulls (or nothing at all) answers `undefined`.
5
+ *
6
+ * Reading `v[0]` instead is wrong twice over: `[[1, 2], [3, 4]]` asks
7
+ * what type `[1, 2]` is, and `[null, 2, 3]` asks what type `null` is.
8
+ * Neither has an answer, and both are ordinary values to send.
9
+ */
10
+ export function arrayLeaf(v) {
11
+ if (!Array.isArray(v))
12
+ return v;
13
+ const l = v.length;
14
+ let i;
15
+ let x;
16
+ for (i = 0; i < l; i++) {
17
+ x = arrayLeaf(v[i]);
18
+ if (x != null)
19
+ return x;
20
+ }
21
+ return undefined;
22
+ }
@@ -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,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
+ }
@@ -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;