postgrejs 3.8.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +50 -0
  3. package/connection/connection.d.ts +14 -2
  4. package/connection/connection.js +57 -9
  5. package/connection/intl-connection.d.ts +146 -5
  6. package/connection/intl-connection.js +458 -30
  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/date-type.js +2 -2
  13. package/data-types/money-type.d.ts +38 -0
  14. package/data-types/money-type.js +203 -0
  15. package/data-types/numeric-type.d.ts +2 -0
  16. package/data-types/numeric-type.js +1 -1
  17. package/data-types/timestamp-type.js +2 -2
  18. package/data-types/timestamptz-type.js +2 -2
  19. package/index.d.ts +1 -0
  20. package/index.js +1 -0
  21. package/interfaces/data-mapping-options.d.ts +27 -0
  22. package/interfaces/database-connection-params.d.ts +32 -5
  23. package/interfaces/query-options.d.ts +32 -0
  24. package/interfaces/script-execute-options.d.ts +32 -0
  25. package/interfaces/transaction-options.d.ts +27 -0
  26. package/interfaces/transaction-options.js +1 -0
  27. package/package.json +1 -1
  28. package/protocol/database-error.d.ts +15 -0
  29. package/protocol/database-error.js +19 -0
  30. package/protocol/frontend.js +1 -1
  31. package/protocol/pg-socket.js +7 -10
  32. package/util/array-leaf.d.ts +10 -0
  33. package/util/array-leaf.js +22 -0
  34. package/util/date-style.d.ts +47 -0
  35. package/util/date-style.js +123 -0
  36. package/util/parse-datetime.d.ts +4 -3
  37. package/util/parse-datetime.js +34 -9
  38. package/util/resolve-column-formats.js +4 -0
  39. package/util/row-decoder.d.ts +24 -4
  40. package/util/row-decoder.js +25 -10
  41. package/util/sql-tag.js +2 -1
  42. package/util/transaction-command.d.ts +7 -0
  43. package/util/transaction-command.js +32 -0
  44. package/util/unspecified-param.d.ts +0 -5
  45. package/util/unspecified-param.js +1 -10
@@ -1,5 +1,32 @@
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
+ }
2
10
  export interface DataMappingOptions {
11
+ /**
12
+ * How the server renders dates, when it is not rendering them in ISO.
13
+ *
14
+ * Only the text wire format is affected - binary carries no formatting
15
+ * at all - and the connection fills this in from the `DateStyle` the
16
+ * server reports, so a caller normally never sets it. See
17
+ * `util/date-style.ts` for what each setting prints.
18
+ */
19
+ dateStyle?: PgDateStyle;
20
+ /**
21
+ * How the server renders money, which is what `money`'s wire format
22
+ * cannot say on its own: it is an int64 of the smallest currency unit,
23
+ * and how many of those make one is `lc_monetary` - a setting the
24
+ * server does not report. A connection asks it once on its way up (see
25
+ * `IntlConnection.ensureMoneyFormat()`) and fills this in; set it here
26
+ * to skip that question, or to read a value rendered by a different
27
+ * server than the one at hand.
28
+ */
29
+ moneyFormat?: MoneyFormat;
3
30
  /**
4
31
  * If true UTC time will be used for date decoding, else system time offset will be used
5
32
  * @default false
@@ -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.9.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
+ }
@@ -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
+ }
@@ -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);
@@ -42,6 +42,10 @@ export function resolveColumnFormats(fields, options, typeMap) {
42
42
  const named = !!asString && asString.length > 0;
43
43
  if (!fields || (!named && !unknownAsString))
44
44
  return base;
45
+ // Both options do one thing: ask for a column as text. A row that is
46
+ // already all text has nothing left for them to ask.
47
+ if (base === DataFormat.text)
48
+ return base;
45
49
  const l = fields.length;
46
50
  const baseIsArray = Array.isArray(base);
47
51
  const out = new Array(l);