postgrejs 3.1.3 → 3.4.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 +79 -1
- package/README.md +79 -13
- package/connection/connection.d.ts +97 -0
- package/connection/connection.js +141 -2
- package/connection/cursor.d.ts +2 -1
- package/connection/cursor.js +6 -6
- package/connection/intl-connection.d.ts +113 -3
- package/connection/intl-connection.js +530 -45
- package/connection/prepared-statement.d.ts +64 -1
- package/connection/prepared-statement.js +97 -6
- package/data-types/bool-type.js +0 -1
- package/data-types/box-type.js +0 -1
- package/data-types/bytea-type.js +5 -3
- package/data-types/char-type.js +2 -2
- package/data-types/circle-type.js +0 -1
- package/data-types/date-type.js +1 -2
- package/data-types/float4-type.js +4 -5
- package/data-types/float8-type.js +4 -6
- package/data-types/int2-type.js +2 -2
- package/data-types/int2-vector-type.js +3 -2
- package/data-types/int4-type.js +2 -2
- package/data-types/int8-type.js +2 -2
- package/data-types/json-type.js +2 -2
- package/data-types/jsonb-type.js +6 -5
- package/data-types/lseg-type.js +0 -1
- package/data-types/numeric-type.js +9 -4
- package/data-types/oid-type.js +2 -2
- package/data-types/oid-vector-type.js +3 -2
- package/data-types/point-type.js +0 -1
- package/data-types/time-type.js +1 -2
- package/data-types/timestamp-type.js +13 -3
- package/data-types/timestamptz-type.js +27 -10
- package/data-types/uuid-type.js +1 -2
- package/data-types/varchar-type.js +2 -2
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/interfaces/batch-result.d.ts +42 -0
- package/interfaces/batch-result.js +1 -0
- package/interfaces/command-result.d.ts +3 -2
- package/interfaces/copy-from-rows-options.d.ts +58 -0
- package/interfaces/copy-from-rows-options.js +1 -0
- package/interfaces/data-type.d.ts +0 -1
- package/interfaces/database-connection-params.d.ts +25 -0
- package/interfaces/pipeline-request.d.ts +16 -0
- package/interfaces/pipeline-request.js +1 -0
- package/interfaces/query-options.d.ts +18 -0
- package/interfaces/script-execute-options.d.ts +12 -0
- package/package.json +18 -3
- package/protocol/backend.js +6 -6
- package/protocol/buffer-reader.d.ts +7 -12
- package/protocol/buffer-reader.js +11 -58
- package/protocol/database-error.d.ts +16 -0
- package/protocol/database-error.js +15 -0
- package/protocol/frontend.js +86 -96
- package/protocol/pg-socket.d.ts +82 -33
- package/protocol/pg-socket.js +143 -86
- package/protocol/pgoutput.js +2 -2
- package/protocol/smart-buffer.d.ts +11 -29
- package/protocol/smart-buffer.js +19 -162
- package/types.d.ts +13 -1
- package/util/assert-integer.d.ts +43 -0
- package/util/assert-integer.js +58 -0
- package/util/bigint-methods.d.ts +0 -1
- package/util/bigint-methods.js +0 -22
- package/util/copy-from-rows.d.ts +56 -0
- package/util/copy-from-rows.js +95 -0
- package/util/decode-binaryarray.d.ts +1 -1
- package/util/decode-binaryarray.js +7 -7
- package/util/encode-binaryarray.js +6 -6
- package/util/encode-copy-binary.d.ts +48 -0
- package/util/encode-copy-binary.js +112 -0
- package/util/fast-parsefloat.d.ts +15 -0
- package/util/fast-parsefloat.js +141 -0
- package/util/get-parsers.js +4 -8
- package/util/parse-datetime.js +7 -0
- package/util/parse-pg-timestamp.d.ts +31 -0
- package/util/parse-pg-timestamp.js +199 -0
- package/util/parse-row.js +120 -0
- package/util/row-decoder.d.ts +49 -0
- package/util/row-decoder.js +63 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BatchResult } from '../interfaces/batch-result.js';
|
|
1
2
|
import type { QueryOptions } from '../interfaces/query-options.js';
|
|
2
3
|
import type { QueryResult } from '../interfaces/query-result.js';
|
|
3
4
|
import type { StatementPrepareOptions } from '../interfaces/statement-prepare-options.js';
|
|
@@ -20,7 +21,69 @@ export declare class PreparedStatement extends SafeEventEmitter implements Async
|
|
|
20
21
|
get sql(): string;
|
|
21
22
|
get paramTypes(): Maybe<Maybe<OID>[]>;
|
|
22
23
|
execute(options?: QueryOptions): Promise<QueryResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Runs this statement once per parameter set, sending every Bind/Execute
|
|
26
|
+
* before waiting for any of them and closing the lot with a single Sync.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* const st = await connection.prepare('update users set name = $1 where id = $2');
|
|
30
|
+
* const batch = await st.executeBatch([
|
|
31
|
+
* ['John', 1],
|
|
32
|
+
* ['Jane', 2],
|
|
33
|
+
* ['Bob', 3],
|
|
34
|
+
* ]);
|
|
35
|
+
* batch.results.map(r => r.rowsAffected); // [1, 1, 0]
|
|
36
|
+
* batch.totalRowsAffected; // 2
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Three things follow from the single Sync, and they are the reasons to
|
|
40
|
+
* reach for this over a loop or a Promise.all() of execute() calls:
|
|
41
|
+
*
|
|
42
|
+
* - **Speed.** Per-set Sync makes the server close an implicit
|
|
43
|
+
* transaction and answer ReadyForQuery every time. Collapsing that to
|
|
44
|
+
* one turned 1000 updates from 194ms (pipelined execute() calls) into
|
|
45
|
+
* 21ms locally, with socket reads dropping from 972 to 3.
|
|
46
|
+
* - **One transaction.** Unless an explicit transaction is already open,
|
|
47
|
+
* the whole batch commits or rolls back together. This differs from N
|
|
48
|
+
* separate execute() calls, where each commits on its own.
|
|
49
|
+
* - **A failing set stops the rest.** PostgreSQL discards everything
|
|
50
|
+
* between an error and the Sync, so sets after a rejected one never
|
|
51
|
+
* run. The error carries `failedIndex` (which set was rejected) and
|
|
52
|
+
* `batchResults` (the ones that had completed) so the caller can see
|
|
53
|
+
* how far it got.
|
|
54
|
+
*
|
|
55
|
+
* Every set runs the same statement, so results map to sets by position.
|
|
56
|
+
* Sets that return rows get them decoded into `rows` exactly as
|
|
57
|
+
* `execute()` would, `rowDecoder` included; `fetchCount` does not apply,
|
|
58
|
+
* since a portal suspended mid-batch would break that mapping - each set
|
|
59
|
+
* runs to completion and buffers its rows.
|
|
60
|
+
*
|
|
61
|
+
* For a bulk INSERT specifically, `connection.copyFrom()` is faster
|
|
62
|
+
* still, and a single `UPDATE ... FROM (VALUES ...)` beats this for bulk
|
|
63
|
+
* updates of one shape - at the cost of PostgreSQL's 65535-parameter
|
|
64
|
+
* ceiling. This is the general answer when neither fits.
|
|
65
|
+
*
|
|
66
|
+
* @param paramSets One array of bind parameters per execution.
|
|
67
|
+
* @param options Applied to every set - the same options `execute()`
|
|
68
|
+
* takes, minus `fetchCount` and `cursor`, which a batch cannot honour.
|
|
69
|
+
*/
|
|
70
|
+
executeBatch(paramSets: any[][], options?: QueryOptions): Promise<BatchResult>;
|
|
71
|
+
protected _executeBatchWithTransaction(paramSets: any[][], options: QueryOptions): Promise<BatchResult>;
|
|
23
72
|
protected _executeWithTransaction(options?: QueryOptions): Promise<QueryResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Runs `fn` inside whatever transaction handling the connection's config
|
|
75
|
+
* and `options` call for: an implicit BEGIN when autoCommit is off, a
|
|
76
|
+
* SAVEPOINT to roll back to when one is already open, a COMMIT when the
|
|
77
|
+
* call is the one turning autoCommit back on.
|
|
78
|
+
*
|
|
79
|
+
* Generic over `fn` rather than calling _execute() directly so that
|
|
80
|
+
* executeBatch() gets the same handling for the batch as a whole. Per
|
|
81
|
+
* set would be wrong twice over: the savepoints alone would add two
|
|
82
|
+
* statements per set, and the batch already shares one transaction by
|
|
83
|
+
* virtue of its single Sync, so a per-set savepoint could not roll one
|
|
84
|
+
* set back independently anyway.
|
|
85
|
+
*/
|
|
86
|
+
protected _withTransaction<T>(options: QueryOptions | undefined, fn: (inlineSavepoint?: string) => Promise<T>, canInlineSavepoint?: boolean): Promise<T>;
|
|
24
87
|
close(): Promise<void>;
|
|
25
88
|
/**
|
|
26
89
|
* Asks the server to cancel whatever this statement's connection is
|
|
@@ -28,7 +91,7 @@ export declare class PreparedStatement extends SafeEventEmitter implements Async
|
|
|
28
91
|
* option, which reports the abort to the caller that asked for it.
|
|
29
92
|
*/
|
|
30
93
|
cancel(): Promise<void>;
|
|
31
|
-
protected _execute(options?: QueryOptions): Promise<QueryResult>;
|
|
94
|
+
protected _execute(options?: QueryOptions, savepoint?: string): Promise<QueryResult>;
|
|
32
95
|
/**
|
|
33
96
|
* Called by Cursor.close() instead of separately awaiting
|
|
34
97
|
* Portal.close() then this.close(). Returns false (no wire action taken,
|
|
@@ -4,6 +4,7 @@ import { Protocol } from '../protocol/protocol.js';
|
|
|
4
4
|
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
5
5
|
import { withAbortSignal } from '../util/abort-signal.js';
|
|
6
6
|
import { getParsers } from '../util/get-parsers.js';
|
|
7
|
+
import { resolveRowType } from '../util/row-decoder.js';
|
|
7
8
|
import { wrapRowDescription } from '../util/wrap-row-description.js';
|
|
8
9
|
import { Cursor } from './cursor.js';
|
|
9
10
|
import { getIntlConnection } from './intl-connection.js';
|
|
@@ -54,7 +55,89 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
54
55
|
return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeWithTransaction(options));
|
|
55
56
|
return this._executeWithTransaction(options);
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Runs this statement once per parameter set, sending every Bind/Execute
|
|
60
|
+
* before waiting for any of them and closing the lot with a single Sync.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const st = await connection.prepare('update users set name = $1 where id = $2');
|
|
64
|
+
* const batch = await st.executeBatch([
|
|
65
|
+
* ['John', 1],
|
|
66
|
+
* ['Jane', 2],
|
|
67
|
+
* ['Bob', 3],
|
|
68
|
+
* ]);
|
|
69
|
+
* batch.results.map(r => r.rowsAffected); // [1, 1, 0]
|
|
70
|
+
* batch.totalRowsAffected; // 2
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Three things follow from the single Sync, and they are the reasons to
|
|
74
|
+
* reach for this over a loop or a Promise.all() of execute() calls:
|
|
75
|
+
*
|
|
76
|
+
* - **Speed.** Per-set Sync makes the server close an implicit
|
|
77
|
+
* transaction and answer ReadyForQuery every time. Collapsing that to
|
|
78
|
+
* one turned 1000 updates from 194ms (pipelined execute() calls) into
|
|
79
|
+
* 21ms locally, with socket reads dropping from 972 to 3.
|
|
80
|
+
* - **One transaction.** Unless an explicit transaction is already open,
|
|
81
|
+
* the whole batch commits or rolls back together. This differs from N
|
|
82
|
+
* separate execute() calls, where each commits on its own.
|
|
83
|
+
* - **A failing set stops the rest.** PostgreSQL discards everything
|
|
84
|
+
* between an error and the Sync, so sets after a rejected one never
|
|
85
|
+
* run. The error carries `failedIndex` (which set was rejected) and
|
|
86
|
+
* `batchResults` (the ones that had completed) so the caller can see
|
|
87
|
+
* how far it got.
|
|
88
|
+
*
|
|
89
|
+
* Every set runs the same statement, so results map to sets by position.
|
|
90
|
+
* Sets that return rows get them decoded into `rows` exactly as
|
|
91
|
+
* `execute()` would, `rowDecoder` included; `fetchCount` does not apply,
|
|
92
|
+
* since a portal suspended mid-batch would break that mapping - each set
|
|
93
|
+
* runs to completion and buffers its rows.
|
|
94
|
+
*
|
|
95
|
+
* For a bulk INSERT specifically, `connection.copyFrom()` is faster
|
|
96
|
+
* still, and a single `UPDATE ... FROM (VALUES ...)` beats this for bulk
|
|
97
|
+
* updates of one shape - at the cost of PostgreSQL's 65535-parameter
|
|
98
|
+
* ceiling. This is the general answer when neither fits.
|
|
99
|
+
*
|
|
100
|
+
* @param paramSets One array of bind parameters per execution.
|
|
101
|
+
* @param options Applied to every set - the same options `execute()`
|
|
102
|
+
* takes, minus `fetchCount` and `cursor`, which a batch cannot honour.
|
|
103
|
+
*/
|
|
104
|
+
async executeBatch(paramSets, options = {}) {
|
|
105
|
+
if (!Array.isArray(paramSets))
|
|
106
|
+
throw new TypeError('executeBatch() requires an array of parameter sets');
|
|
107
|
+
if (options.cursor)
|
|
108
|
+
throw new Error('executeBatch() cannot return a cursor - a batch runs every set to ' +
|
|
109
|
+
'completion under one Sync, so there is no portal left to fetch from');
|
|
110
|
+
const intlCon = getIntlConnection(this.connection);
|
|
111
|
+
if (!paramSets.length)
|
|
112
|
+
return { results: [], totalRowsAffected: 0 };
|
|
113
|
+
if (options.signal)
|
|
114
|
+
return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeBatchWithTransaction(paramSets, options));
|
|
115
|
+
return this._executeBatchWithTransaction(paramSets, options);
|
|
116
|
+
}
|
|
117
|
+
_executeBatchWithTransaction(paramSets, options) {
|
|
118
|
+
const intlCon = getIntlConnection(this.connection);
|
|
119
|
+
return this._withTransaction(options, () => intlCon.executeBatchReused(this.name, this._fields, this.paramTypes, paramSets, options));
|
|
120
|
+
}
|
|
57
121
|
async _executeWithTransaction(options = {}) {
|
|
122
|
+
// The Bind+Execute+Sync path can carry the rollbackOnError savepoint
|
|
123
|
+
// in its own round trip; the cursor path cannot, because the portal it
|
|
124
|
+
// opens outlives this call and the savepoint has to outlive it too.
|
|
125
|
+
return this._withTransaction(options, savepoint => this._execute(options, savepoint), !(options.cursor && this._fields));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Runs `fn` inside whatever transaction handling the connection's config
|
|
129
|
+
* and `options` call for: an implicit BEGIN when autoCommit is off, a
|
|
130
|
+
* SAVEPOINT to roll back to when one is already open, a COMMIT when the
|
|
131
|
+
* call is the one turning autoCommit back on.
|
|
132
|
+
*
|
|
133
|
+
* Generic over `fn` rather than calling _execute() directly so that
|
|
134
|
+
* executeBatch() gets the same handling for the batch as a whole. Per
|
|
135
|
+
* set would be wrong twice over: the savepoints alone would add two
|
|
136
|
+
* statements per set, and the batch already shares one transaction by
|
|
137
|
+
* virtue of its single Sync, so a per-set savepoint could not roll one
|
|
138
|
+
* set back independently anyway.
|
|
139
|
+
*/
|
|
140
|
+
async _withTransaction(options = {}, fn, canInlineSavepoint = false) {
|
|
58
141
|
const intlCon = getIntlConnection(this.connection);
|
|
59
142
|
const transactionCommand = this.sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
|
|
60
143
|
let beginFirst = false;
|
|
@@ -76,13 +159,21 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
76
159
|
const rollbackOnError = !transactionCommand &&
|
|
77
160
|
intlCon.inTransaction &&
|
|
78
161
|
(options?.rollbackOnError ?? intlCon.config.rollbackOnError ?? true);
|
|
79
|
-
|
|
162
|
+
// When `fn` can put the savepoint on the wire itself, SAVEPOINT and
|
|
163
|
+
// RELEASE ride along inside its single Sync instead of costing a round
|
|
164
|
+
// trip each - measured at 51us for the pair that way against 511us as
|
|
165
|
+
// two separate round trips. The error path is unchanged either way:
|
|
166
|
+
// PostgreSQL discards everything between a failed statement and the
|
|
167
|
+
// Sync, so an inlined RELEASE never runs, and ROLLBACK TO goes out on
|
|
168
|
+
// its own below exactly as before.
|
|
169
|
+
const inlineSavepoint = canInlineSavepoint && rollbackOnError && intlCon.inTransaction;
|
|
170
|
+
if (rollbackOnError && intlCon.inTransaction && !inlineSavepoint)
|
|
80
171
|
await intlCon.execute('SAVEPOINT ' + this._onErrorSavePoint);
|
|
81
172
|
try {
|
|
82
|
-
const result = await this.
|
|
173
|
+
const result = await fn(inlineSavepoint ? this._onErrorSavePoint : undefined);
|
|
83
174
|
if (commitLast)
|
|
84
175
|
await intlCon.execute('COMMIT');
|
|
85
|
-
else if (rollbackOnError && intlCon.inTransaction) {
|
|
176
|
+
else if (rollbackOnError && intlCon.inTransaction && !inlineSavepoint) {
|
|
86
177
|
await intlCon.execute('RELEASE ' + this._onErrorSavePoint + ';');
|
|
87
178
|
}
|
|
88
179
|
return result;
|
|
@@ -111,7 +202,7 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
111
202
|
async cancel() {
|
|
112
203
|
return getIntlConnection(this.connection).cancel();
|
|
113
204
|
}
|
|
114
|
-
async _execute(options = {}) {
|
|
205
|
+
async _execute(options = {}, savepoint) {
|
|
115
206
|
const intlCon = getIntlConnection(this.connection);
|
|
116
207
|
if (options.cursor && this._fields) {
|
|
117
208
|
intlCon.ref();
|
|
@@ -124,7 +215,7 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
124
215
|
const result = {
|
|
125
216
|
command: undefined,
|
|
126
217
|
fields: resultFields,
|
|
127
|
-
rowType: options
|
|
218
|
+
rowType: resolveRowType(options),
|
|
128
219
|
cursor: new Cursor(this, portal, resultFields, parsers, options),
|
|
129
220
|
};
|
|
130
221
|
this._refCount++;
|
|
@@ -141,7 +232,7 @@ export class PreparedStatement extends SafeEventEmitter {
|
|
|
141
232
|
// unnamed portal, reusing the RowDescription prepare() already fetched
|
|
142
233
|
// instead of re-Describing (and re-Close-ing) a fresh named portal on
|
|
143
234
|
// every call.
|
|
144
|
-
return intlCon.executeReused(this.name, this._fields, this.paramTypes, options.params, options);
|
|
235
|
+
return intlCon.executeReused(this.name, this._fields, this.paramTypes, options.params, options, savepoint);
|
|
145
236
|
}
|
|
146
237
|
/**
|
|
147
238
|
* Called by Cursor.close() instead of separately awaiting
|
package/data-types/bool-type.js
CHANGED
|
@@ -3,7 +3,6 @@ export const BoolType = {
|
|
|
3
3
|
name: 'bool',
|
|
4
4
|
oid: DataTypeOIDs.bool,
|
|
5
5
|
jsType: 'boolean',
|
|
6
|
-
fixedBinarySize: 1,
|
|
7
6
|
// 't'/'f', the form decodeText above reads and the server itself
|
|
8
7
|
// emits - encodeText and decodeText are a public pair and have to stay
|
|
9
8
|
// each other's inverse.
|
package/data-types/box-type.js
CHANGED
package/data-types/bytea-type.js
CHANGED
|
@@ -9,10 +9,12 @@ export const ByteaType = {
|
|
|
9
9
|
return '\\x' + v.toString('hex');
|
|
10
10
|
},
|
|
11
11
|
encodeBinary(buf, v) {
|
|
12
|
-
buf.
|
|
12
|
+
buf.writeBytes(v);
|
|
13
13
|
},
|
|
14
|
-
decodeBinary(v, offset = 0) {
|
|
15
|
-
|
|
14
|
+
decodeBinary(v, offset = 0, len) {
|
|
15
|
+
// A view, not a copy - the value handed back is the caller's own
|
|
16
|
+
// window onto the wire bytes, which is what bytea has always returned.
|
|
17
|
+
return v.subarray(offset, offset + len);
|
|
16
18
|
},
|
|
17
19
|
decodeText: parseBytea,
|
|
18
20
|
decodeTextBuffer: parseByteaBuffer,
|
package/data-types/char-type.js
CHANGED
|
@@ -9,8 +9,8 @@ export const CharType = {
|
|
|
9
9
|
encodeBinary(buf, v) {
|
|
10
10
|
buf.writeString((v ? '' + v : ' ')[0], 'utf8');
|
|
11
11
|
},
|
|
12
|
-
decodeBinary(v, offset = 0) {
|
|
13
|
-
return v.toString('utf8', offset);
|
|
12
|
+
decodeBinary(v, offset = 0, len) {
|
|
13
|
+
return v.toString('utf8', offset, offset + len);
|
|
14
14
|
},
|
|
15
15
|
decodeText(v) {
|
|
16
16
|
return v;
|
package/data-types/date-type.js
CHANGED
|
@@ -6,7 +6,6 @@ export const DateType = {
|
|
|
6
6
|
name: 'date',
|
|
7
7
|
oid: DataTypeOIDs.date,
|
|
8
8
|
jsType: 'Date',
|
|
9
|
-
fixedBinarySize: 4,
|
|
10
9
|
encodeText(v, options) {
|
|
11
10
|
return formatDate(v, options);
|
|
12
11
|
},
|
|
@@ -30,7 +29,7 @@ export const DateType = {
|
|
|
30
29
|
const t = Math.trunc(n + Number.EPSILON);
|
|
31
30
|
buf.writeInt32BE(t);
|
|
32
31
|
},
|
|
33
|
-
decodeBinary(v, offset = 0, options) {
|
|
32
|
+
decodeBinary(v, offset = 0, _len, options) {
|
|
34
33
|
const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.date);
|
|
35
34
|
const t = v.readInt32BE(offset);
|
|
36
35
|
if (t === 0x7fffffff)
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertCoercedNumber } from '../util/assert-integer.js';
|
|
3
|
+
import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
|
|
2
4
|
export const Float4Type = {
|
|
3
5
|
name: 'float4',
|
|
4
6
|
oid: DataTypeOIDs.float4,
|
|
5
7
|
jsType: 'number',
|
|
6
|
-
fixedBinarySize: 4,
|
|
7
8
|
encodeText(v) {
|
|
8
9
|
return '' + v;
|
|
9
10
|
},
|
|
10
11
|
encodeBinary(buf, v) {
|
|
11
|
-
buf.writeFloatBE(typeof v === 'number' ? v : parseFloat(v));
|
|
12
|
+
buf.writeFloatBE(typeof v === 'number' ? v : assertCoercedNumber(parseFloat(v), 'float4'));
|
|
12
13
|
},
|
|
13
14
|
decodeBinary(v, offset = 0) {
|
|
14
15
|
return v.readFloatBE(offset);
|
|
15
16
|
},
|
|
16
17
|
decodeText: parseFloat,
|
|
17
|
-
decodeTextBuffer
|
|
18
|
-
return parseFloat(buf.toString('latin1', offset, offset + len));
|
|
19
|
-
},
|
|
18
|
+
decodeTextBuffer: fastParseFloatBuffer,
|
|
20
19
|
isType(v) {
|
|
21
20
|
return typeof v === 'number';
|
|
22
21
|
},
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertCoercedNumber } from '../util/assert-integer.js';
|
|
3
|
+
import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
|
|
2
4
|
export const Float8Type = {
|
|
3
5
|
name: 'float8',
|
|
4
6
|
oid: DataTypeOIDs.float8,
|
|
5
7
|
jsType: 'number',
|
|
6
|
-
fixedBinarySize: 8,
|
|
7
8
|
// Infinity/NaN stringify to the words PostgreSQL itself uses.
|
|
8
9
|
encodeText(v) {
|
|
9
10
|
return '' + v;
|
|
10
11
|
},
|
|
11
12
|
encodeBinary(buf, v) {
|
|
12
|
-
buf.writeDoubleBE(typeof v === 'number' ? v : parseFloat(v));
|
|
13
|
+
buf.writeDoubleBE(typeof v === 'number' ? v : assertCoercedNumber(parseFloat(v), 'float8'));
|
|
13
14
|
},
|
|
14
15
|
decodeBinary(v, offset = 0) {
|
|
15
16
|
return v.readDoubleBE(offset);
|
|
16
17
|
},
|
|
17
18
|
decodeText: parseFloat,
|
|
18
|
-
|
|
19
|
-
decodeTextBuffer(buf, offset, len) {
|
|
20
|
-
return parseFloat(buf.toString('latin1', offset, offset + len));
|
|
21
|
-
},
|
|
19
|
+
decodeTextBuffer: fastParseFloatBuffer,
|
|
22
20
|
isType(v) {
|
|
23
21
|
return typeof v === 'number';
|
|
24
22
|
},
|
package/data-types/int2-type.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertInteger } from '../util/assert-integer.js';
|
|
2
3
|
import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
|
|
3
4
|
export const Int2Type = {
|
|
4
5
|
name: 'int2',
|
|
5
6
|
oid: DataTypeOIDs.int2,
|
|
6
7
|
jsType: 'number',
|
|
7
|
-
fixedBinarySize: 2,
|
|
8
8
|
encodeText(v) {
|
|
9
9
|
return '' + v;
|
|
10
10
|
},
|
|
11
11
|
encodeBinary(buf, v) {
|
|
12
|
-
buf.writeInt16BE(fastParseInt(v));
|
|
12
|
+
buf.writeInt16BE(assertInteger(fastParseInt(v), 'int2'));
|
|
13
13
|
},
|
|
14
14
|
decodeBinary(v, offset = 0) {
|
|
15
15
|
return v.readInt16BE(offset);
|
|
@@ -15,8 +15,9 @@ export const Int2VectorType = {
|
|
|
15
15
|
encodeCalculateDim(v) {
|
|
16
16
|
return [v.length];
|
|
17
17
|
},
|
|
18
|
-
decodeBinary(v) {
|
|
19
|
-
return decodeBinaryArray(v,
|
|
18
|
+
decodeBinary(v, offset = 0) {
|
|
19
|
+
return (decodeBinaryArray(v, offset, (b, off) => b.readInt16BE(off)) ||
|
|
20
|
+
undefined);
|
|
20
21
|
},
|
|
21
22
|
decodeText(str) {
|
|
22
23
|
return str.split(' ').map(fastParseInt);
|
package/data-types/int4-type.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertInteger } from '../util/assert-integer.js';
|
|
2
3
|
import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
|
|
3
4
|
export const Int4Type = {
|
|
4
5
|
name: 'int4',
|
|
5
6
|
oid: DataTypeOIDs.int4,
|
|
6
7
|
jsType: 'number',
|
|
7
|
-
fixedBinarySize: 4,
|
|
8
8
|
encodeText(v) {
|
|
9
9
|
return '' + v;
|
|
10
10
|
},
|
|
11
11
|
encodeBinary(buf, v) {
|
|
12
|
-
buf.writeInt32BE(fastParseInt(v));
|
|
12
|
+
buf.writeInt32BE(assertInteger(fastParseInt(v), 'int4'));
|
|
13
13
|
},
|
|
14
14
|
decodeBinary(v, offset = 0) {
|
|
15
15
|
return v.readInt32BE(offset);
|
package/data-types/int8-type.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertBigIntSource } from '../util/assert-integer.js';
|
|
2
3
|
import { readBigInt64BE } from '../util/bigint-methods.js';
|
|
3
4
|
import { fastParseIntBuffer } from '../util/fast-parseint.js';
|
|
4
5
|
const maxSafeInteger = BigInt(Number.MAX_SAFE_INTEGER);
|
|
@@ -6,12 +7,11 @@ export const Int8Type = {
|
|
|
6
7
|
name: 'int8',
|
|
7
8
|
oid: DataTypeOIDs.int8,
|
|
8
9
|
jsType: 'BigInt',
|
|
9
|
-
fixedBinarySize: 8,
|
|
10
10
|
encodeText(v) {
|
|
11
11
|
return '' + v;
|
|
12
12
|
},
|
|
13
13
|
encodeBinary(buf, v) {
|
|
14
|
-
buf.writeBigInt64BE(v);
|
|
14
|
+
buf.writeBigInt64BE(assertBigIntSource(v, 'int8'));
|
|
15
15
|
},
|
|
16
16
|
decodeBinary(buf, offset = 0) {
|
|
17
17
|
const v = typeof buf.readBigInt64BE === 'function'
|
package/data-types/json-type.js
CHANGED
|
@@ -3,8 +3,8 @@ export const JsonType = {
|
|
|
3
3
|
name: 'json',
|
|
4
4
|
oid: DataTypeOIDs.json,
|
|
5
5
|
jsType: 'string',
|
|
6
|
-
decodeBinary(v, offset = 0, options) {
|
|
7
|
-
const content = v.toString('utf8', offset);
|
|
6
|
+
decodeBinary(v, offset = 0, len, options) {
|
|
7
|
+
const content = v.toString('utf8', offset, offset + len);
|
|
8
8
|
const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.json);
|
|
9
9
|
if (fetchAsString)
|
|
10
10
|
return content;
|
package/data-types/jsonb-type.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
-
import { BufferReader } from '../protocol/buffer-reader.js';
|
|
3
2
|
export const JsonbType = {
|
|
4
3
|
name: 'jsonb',
|
|
5
4
|
oid: DataTypeOIDs.jsonb,
|
|
@@ -15,12 +14,14 @@ export const JsonbType = {
|
|
|
15
14
|
buf.writeUInt8(1);
|
|
16
15
|
buf.writeString(JsonbType.encodeText(v, {}), 'utf8');
|
|
17
16
|
},
|
|
18
|
-
decodeBinary(v, offset = 0, options) {
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
decodeBinary(v, offset = 0, len, options) {
|
|
18
|
+
// jsonb's binary form is a one-byte version followed by the JSON text,
|
|
19
|
+
// which carries no terminator of its own - so its end is whatever
|
|
20
|
+
// `len` says, not the end of the buffer it happens to sit in.
|
|
21
|
+
if (v[offset] !== 1)
|
|
21
22
|
throw new Error('Unexpected Jsonb version value in header');
|
|
23
|
+
const content = v.toString('utf8', offset + 1, offset + len);
|
|
22
24
|
const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.jsonb);
|
|
23
|
-
const content = buf.readLString(buf.length - buf.offset);
|
|
24
25
|
if (fetchAsString)
|
|
25
26
|
return content;
|
|
26
27
|
return content ? JSON.parse(content) : undefined;
|
package/data-types/lseg-type.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertCoercedNumber } from '../util/assert-integer.js';
|
|
3
|
+
import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
|
|
2
4
|
const NUMERIC_NEG = 0x4000;
|
|
3
5
|
const NUMERIC_NAN = 0xc000;
|
|
4
6
|
const NUMERIC_PINF = 0xd000;
|
|
@@ -36,6 +38,12 @@ export const NumericType = {
|
|
|
36
38
|
return writeHeader(0, 0, NUMERIC_PINF, 0);
|
|
37
39
|
if (str === '-Infinity')
|
|
38
40
|
return writeHeader(0, 0, NUMERIC_NINF, 0);
|
|
41
|
+
// Anything with no numeric reading at all - `{}` stringifies to
|
|
42
|
+
// "[object Object]", `true` to "true" - would otherwise be walked
|
|
43
|
+
// digit by digit below and stored as 0.
|
|
44
|
+
// Number('') is 0, not NaN, so an empty string - what `[]` and `null`
|
|
45
|
+
// stringify to - would slip past the NaN check below.
|
|
46
|
+
assertCoercedNumber(str.length ? Number(str) : NaN, 'numeric');
|
|
39
47
|
// Exponent form has no place in the wire format; go through Number to
|
|
40
48
|
// get it back into plain notation. toFixed() cannot do this itself -
|
|
41
49
|
// per spec it falls back to exponential notation once the magnitude
|
|
@@ -109,10 +117,7 @@ export const NumericType = {
|
|
|
109
117
|
return '' + n;
|
|
110
118
|
},
|
|
111
119
|
decodeText: parseFloat,
|
|
112
|
-
|
|
113
|
-
decodeTextBuffer(buf, offset, len) {
|
|
114
|
-
return parseFloat(buf.toString('latin1', offset, offset + len));
|
|
115
|
-
},
|
|
120
|
+
decodeTextBuffer: fastParseFloatBuffer,
|
|
116
121
|
isType(v) {
|
|
117
122
|
return typeof v === 'number';
|
|
118
123
|
},
|
package/data-types/oid-type.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { DataTypeNames, DataTypeOIDs } from '../constants.js';
|
|
2
|
+
import { assertInteger } from '../util/assert-integer.js';
|
|
2
3
|
import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
|
|
3
4
|
export const OidType = {
|
|
4
5
|
name: 'oid',
|
|
5
6
|
oid: DataTypeOIDs.oid,
|
|
6
7
|
jsType: 'number',
|
|
7
|
-
fixedBinarySize: 4,
|
|
8
8
|
encodeText(v) {
|
|
9
9
|
return '' + v;
|
|
10
10
|
},
|
|
11
11
|
encodeBinary(buf, v) {
|
|
12
|
-
buf.writeUInt32BE(fastParseInt(v));
|
|
12
|
+
buf.writeUInt32BE(assertInteger(fastParseInt(v), 'oid'));
|
|
13
13
|
},
|
|
14
14
|
decodeBinary(v, offset = 0) {
|
|
15
15
|
return v.readUInt32BE(offset);
|
|
@@ -21,8 +21,9 @@ export const OidVectorType = {
|
|
|
21
21
|
encodeCalculateDim(v) {
|
|
22
22
|
return [v.length];
|
|
23
23
|
},
|
|
24
|
-
decodeBinary(v) {
|
|
25
|
-
return decodeBinaryArray(v,
|
|
24
|
+
decodeBinary(v, offset = 0) {
|
|
25
|
+
return (decodeBinaryArray(v, offset, (b, off) => b.readUInt32BE(off)) ||
|
|
26
|
+
undefined);
|
|
26
27
|
},
|
|
27
28
|
decodeText(str) {
|
|
28
29
|
return str.split(' ').map(fastParseInt);
|
package/data-types/point-type.js
CHANGED
package/data-types/time-type.js
CHANGED
|
@@ -6,7 +6,6 @@ export const TimeType = {
|
|
|
6
6
|
name: 'time',
|
|
7
7
|
oid: DataTypeOIDs.time,
|
|
8
8
|
jsType: 'string',
|
|
9
|
-
fixedBinarySize: 8,
|
|
10
9
|
encodeText(v, options) {
|
|
11
10
|
return formatTime(v, options);
|
|
12
11
|
},
|
|
@@ -25,7 +24,7 @@ export const TimeType = {
|
|
|
25
24
|
buf.writeInt32BE(hi);
|
|
26
25
|
buf.writeUInt32BE(lo);
|
|
27
26
|
},
|
|
28
|
-
decodeBinary(v, offset = 0, options) {
|
|
27
|
+
decodeBinary(v, offset = 0, _len, options) {
|
|
29
28
|
const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.time);
|
|
30
29
|
const hi = v.readInt32BE(offset);
|
|
31
30
|
const lo = v.readUInt32BE(offset + 4);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { DataTypeOIDs } from '../constants.js';
|
|
2
2
|
import { formatTimestamp } from '../util/format-datetime.js';
|
|
3
3
|
import { parseDateTime } from '../util/parse-datetime.js';
|
|
4
|
+
import { parsePgTimestampBuffer } from '../util/parse-pg-timestamp.js';
|
|
4
5
|
const timeShift = 946684800000;
|
|
5
6
|
const timeMul = 4294967296;
|
|
6
7
|
export const TimestampType = {
|
|
7
8
|
name: 'timestamp',
|
|
8
9
|
oid: DataTypeOIDs.timestamp,
|
|
9
10
|
jsType: 'Date',
|
|
10
|
-
fixedBinarySize: 8,
|
|
11
11
|
encodeText(v, options) {
|
|
12
12
|
return formatTimestamp(v, options);
|
|
13
13
|
},
|
|
@@ -36,7 +36,7 @@ export const TimestampType = {
|
|
|
36
36
|
buf.writeInt32BE(hi);
|
|
37
37
|
buf.writeUInt32BE(lo);
|
|
38
38
|
},
|
|
39
|
-
decodeBinary(v, offset = 0, options) {
|
|
39
|
+
decodeBinary(v, offset = 0, _len, options) {
|
|
40
40
|
const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.timestamp);
|
|
41
41
|
const hi = v.readInt32BE(offset);
|
|
42
42
|
const lo = v.readUInt32BE(offset + 4);
|
|
@@ -56,8 +56,18 @@ export const TimestampType = {
|
|
|
56
56
|
return v;
|
|
57
57
|
return parseDateTime(v, options.utcDates);
|
|
58
58
|
},
|
|
59
|
-
//
|
|
59
|
+
// Reads PostgreSQL's own timestamp shape straight from the wire bytes -
|
|
60
|
+
// see timestamptz-type.ts's decodeTextBuffer for why. The two guards are
|
|
61
|
+
// what decodeText()/parseDateTime() would have applied anyway: with
|
|
62
|
+
// fetchAsString the raw string is the answer, and with utcDates the
|
|
63
|
+
// string has to go through the regex path that reads it as UTC.
|
|
60
64
|
decodeTextBuffer(buf, offset, len, options) {
|
|
65
|
+
if (!options.utcDates &&
|
|
66
|
+
!options.fetchAsString?.includes(DataTypeOIDs.timestamp)) {
|
|
67
|
+
const d = parsePgTimestampBuffer(buf, offset, offset + len);
|
|
68
|
+
if (d !== undefined)
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
61
71
|
return TimestampType.decodeText(buf.toString('latin1', offset, offset + len), options);
|
|
62
72
|
},
|
|
63
73
|
isType(v) {
|