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.
- package/CHANGELOG.md +38 -1
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +57 -9
- package/connection/intl-connection.d.ts +146 -5
- package/connection/intl-connection.js +458 -30
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +22 -3
- package/connection/prepared-statement.js +22 -2
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/date-type.js +2 -2
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +203 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +1 -1
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +2 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/data-mapping-options.d.ts +27 -0
- package/interfaces/database-connection-params.d.ts +32 -5
- package/interfaces/query-options.d.ts +32 -0
- package/interfaces/script-execute-options.d.ts +32 -0
- package/interfaces/transaction-options.d.ts +27 -0
- package/interfaces/transaction-options.js +1 -0
- package/package.json +1 -1
- package/protocol/database-error.d.ts +15 -0
- package/protocol/database-error.js +19 -0
- package/protocol/frontend.js +1 -1
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.js +4 -0
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +2 -1
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +0 -5
- 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
|
-
|
|
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
|
-
*
|
|
166
|
-
* connection
|
|
167
|
-
*
|
|
168
|
-
*
|
|
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
|
@@ -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
|
}
|
package/protocol/frontend.js
CHANGED
|
@@ -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;
|
package/protocol/pg-socket.js
CHANGED
|
@@ -899,16 +899,13 @@ export class PgSocket extends SafeEventEmitter {
|
|
|
899
899
|
socket.write(data, cb);
|
|
900
900
|
return true;
|
|
901
901
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
+
}
|
package/util/parse-datetime.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
1
|
+
import { type PgDateStyle } from './date-style.js';
|
|
2
|
+
export declare function parseDate(str: string, utc?: boolean, dateStyle?: PgDateStyle): Date | number;
|
|
3
|
+
export declare function parseDateTime(str: string, utc?: boolean, dateStyle?: PgDateStyle): Date | number;
|
|
4
|
+
export declare function parseDateTimeTz(str: string, utc?: boolean, dateStyle?: PgDateStyle): Date | number;
|
package/util/parse-datetime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// noinspection RegExpUnnecessaryNonCapturingGroup
|
|
2
|
+
import { parseStyledDateTime } from './date-style.js';
|
|
2
3
|
import { fastParseInt } from './fast-parseint.js';
|
|
3
4
|
import { parsePgTimestampString } from './parse-pg-timestamp.js';
|
|
4
5
|
// noinspection RegExpUnnecessaryNonCapturingGroup
|
|
@@ -21,7 +22,12 @@ function fillTimeArgs(m, args) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
|
-
export function parseDate(str, utc) {
|
|
25
|
+
export function parseDate(str, utc, dateStyle) {
|
|
26
|
+
if (dateStyle) {
|
|
27
|
+
const styled = parseStyledDateTime(str, dateStyle, utc);
|
|
28
|
+
if (styled)
|
|
29
|
+
return styled;
|
|
30
|
+
}
|
|
25
31
|
const m = str.match(TIMESTAMP_PATTERN);
|
|
26
32
|
if (!m)
|
|
27
33
|
return noMatch(str);
|
|
@@ -40,7 +46,12 @@ export function parseDate(str, utc) {
|
|
|
40
46
|
return new Date(Date.UTC(...args));
|
|
41
47
|
return new Date(...args);
|
|
42
48
|
}
|
|
43
|
-
export function parseDateTime(str, utc) {
|
|
49
|
+
export function parseDateTime(str, utc, dateStyle) {
|
|
50
|
+
if (dateStyle) {
|
|
51
|
+
const styled = parseStyledDateTime(str, dateStyle, utc);
|
|
52
|
+
if (styled)
|
|
53
|
+
return styled;
|
|
54
|
+
}
|
|
44
55
|
if (!utc) {
|
|
45
56
|
if (str === 'infinity')
|
|
46
57
|
return Infinity;
|
|
@@ -49,9 +60,14 @@ export function parseDateTime(str, utc) {
|
|
|
49
60
|
const fast = parsePgTimestampString(str);
|
|
50
61
|
if (fast)
|
|
51
62
|
return fast;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
// Only reached under the ISO DateStyle: `new Date()` reads
|
|
64
|
+
// `05.03.2024` as the third of May, and a session that renders dates
|
|
65
|
+
// that way has said so above.
|
|
66
|
+
if (!dateStyle) {
|
|
67
|
+
const d = new Date(str);
|
|
68
|
+
if (!isNaN(d.getTime()))
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
55
71
|
}
|
|
56
72
|
const m = str.match(TIMESTAMP_PATTERN);
|
|
57
73
|
if (!m)
|
|
@@ -72,7 +88,12 @@ export function parseDateTime(str, utc) {
|
|
|
72
88
|
return new Date(Date.UTC(...args));
|
|
73
89
|
return new Date(...args);
|
|
74
90
|
}
|
|
75
|
-
export function parseDateTimeTz(str, utc) {
|
|
91
|
+
export function parseDateTimeTz(str, utc, dateStyle) {
|
|
92
|
+
if (dateStyle) {
|
|
93
|
+
const styled = parseStyledDateTime(str, dateStyle, utc);
|
|
94
|
+
if (styled)
|
|
95
|
+
return styled;
|
|
96
|
+
}
|
|
76
97
|
if (str === 'infinity')
|
|
77
98
|
return Infinity;
|
|
78
99
|
if (str === '-infinity')
|
|
@@ -80,9 +101,13 @@ export function parseDateTimeTz(str, utc) {
|
|
|
80
101
|
const fast = parsePgTimestampString(str);
|
|
81
102
|
if (fast)
|
|
82
103
|
return fast;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
// See parseDateTime(): the engine's guess is only trusted where the
|
|
105
|
+
// session renders ISO.
|
|
106
|
+
if (!dateStyle) {
|
|
107
|
+
const native = new Date(str);
|
|
108
|
+
if (!isNaN(native.getTime()))
|
|
109
|
+
return native;
|
|
110
|
+
}
|
|
86
111
|
const m = str.match(TIMESTAMP_PATTERN);
|
|
87
112
|
if (!m)
|
|
88
113
|
return noMatch(str);
|
|
@@ -42,6 +42,10 @@ export function resolveColumnFormats(fields, options, typeMap) {
|
|
|
42
42
|
const named = !!asString && asString.length > 0;
|
|
43
43
|
if (!fields || (!named && !unknownAsString))
|
|
44
44
|
return base;
|
|
45
|
+
// Both options do one thing: ask for a column as text. A row that is
|
|
46
|
+
// already all text has nothing left for them to ask.
|
|
47
|
+
if (base === DataFormat.text)
|
|
48
|
+
return base;
|
|
45
49
|
const l = fields.length;
|
|
46
50
|
const baseIsArray = Array.isArray(base);
|
|
47
51
|
const out = new Array(l);
|