postgrejs 2.23.1 → 3.0.1

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 (105) hide show
  1. package/CHANGELOG.md +152 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +152 -13
  5. package/connection/connection.js +296 -49
  6. package/connection/copy-stream.d.ts +88 -0
  7. package/connection/copy-stream.js +244 -0
  8. package/connection/cursor.js +20 -13
  9. package/connection/intl-connection.d.ts +68 -3
  10. package/connection/intl-connection.js +414 -57
  11. package/connection/large-object.d.ts +65 -0
  12. package/connection/large-object.js +151 -0
  13. package/connection/logical-replication.d.ts +121 -0
  14. package/connection/logical-replication.js +279 -0
  15. package/connection/pool.d.ts +61 -4
  16. package/connection/pool.js +177 -29
  17. package/connection/portal.d.ts +18 -4
  18. package/connection/portal.js +53 -59
  19. package/connection/prepared-statement.d.ts +22 -1
  20. package/connection/prepared-statement.js +117 -97
  21. package/data-type-map.js +4 -2
  22. package/data-types/bool-type.js +14 -10
  23. package/data-types/box-type.js +15 -8
  24. package/data-types/bytea-type.js +9 -5
  25. package/data-types/char-type.js +11 -5
  26. package/data-types/circle-type.js +15 -7
  27. package/data-types/date-type.js +25 -19
  28. package/data-types/float4-type.js +10 -3
  29. package/data-types/float8-type.js +12 -3
  30. package/data-types/int2-type.js +9 -4
  31. package/data-types/int2-vector-type.js +9 -4
  32. package/data-types/int4-type.js +11 -5
  33. package/data-types/int8-type.js +22 -7
  34. package/data-types/json-type.js +17 -8
  35. package/data-types/jsonb-type.js +20 -14
  36. package/data-types/lseg-type.js +16 -8
  37. package/data-types/numeric-type.d.ts +8 -0
  38. package/data-types/numeric-type.js +112 -8
  39. package/data-types/oid-type.d.ts +0 -1
  40. package/data-types/oid-type.js +9 -10
  41. package/data-types/oid-vector-type.d.ts +9 -0
  42. package/data-types/oid-vector-type.js +51 -0
  43. package/data-types/point-type.js +14 -6
  44. package/data-types/time-type.js +20 -13
  45. package/data-types/timestamp-type.js +32 -25
  46. package/data-types/timestamptz-type.js +33 -21
  47. package/data-types/uuid-type.js +21 -11
  48. package/data-types/varchar-type.js +16 -4
  49. package/index.d.ts +5 -0
  50. package/index.js +5 -0
  51. package/interfaces/data-type.d.ts +5 -3
  52. package/interfaces/database-connection-params.d.ts +95 -0
  53. package/interfaces/query-options.d.ts +17 -0
  54. package/interfaces/script-execute-options.d.ts +17 -0
  55. package/package.json +2 -3
  56. package/protocol/backend.d.ts +6 -1
  57. package/protocol/backend.js +70 -45
  58. package/protocol/buffer-reader.d.ts +0 -2
  59. package/protocol/buffer-reader.js +3 -10
  60. package/protocol/cert-signature.d.ts +12 -0
  61. package/protocol/cert-signature.js +114 -0
  62. package/protocol/frontend.d.ts +16 -0
  63. package/protocol/frontend.js +57 -6
  64. package/protocol/pg-socket.d.ts +220 -9
  65. package/protocol/pg-socket.js +539 -102
  66. package/protocol/pgoutput.d.ts +76 -0
  67. package/protocol/pgoutput.js +155 -0
  68. package/protocol/protocol.d.ts +3 -4
  69. package/protocol/protocol.js +1 -1
  70. package/protocol/sasl.d.ts +11 -1
  71. package/protocol/sasl.js +35 -4
  72. package/protocol/smart-buffer.d.ts +0 -2
  73. package/protocol/smart-buffer.js +11 -16
  74. package/types.d.ts +5 -3
  75. package/util/abort-signal.d.ts +24 -0
  76. package/util/abort-signal.js +63 -0
  77. package/util/array-calculatedim.js +3 -1
  78. package/util/connection-config.js +77 -3
  79. package/util/decode-binaryarray.d.ts +1 -1
  80. package/util/decode-binaryarray.js +15 -6
  81. package/util/encode-binaryarray.js +7 -3
  82. package/util/escape-identifier.d.ts +14 -0
  83. package/util/escape-identifier.js +32 -0
  84. package/util/escape-literal.js +3 -0
  85. package/util/fast-parseint.d.ts +1 -0
  86. package/util/fast-parseint.js +12 -17
  87. package/util/format-datetime.d.ts +10 -0
  88. package/util/format-datetime.js +71 -0
  89. package/util/get-parsers.js +31 -10
  90. package/util/parse-array.js +2 -2
  91. package/util/parse-bytea.d.ts +2 -0
  92. package/util/parse-bytea.js +83 -0
  93. package/util/parse-datetime.d.ts +3 -1
  94. package/util/parse-datetime.js +86 -17
  95. package/util/parse-row.d.ts +3 -1
  96. package/util/parse-row.js +60 -5
  97. package/util/parse-time.js +8 -1
  98. package/util/sql-tag.d.ts +60 -0
  99. package/util/sql-tag.js +202 -0
  100. package/util/starts-transaction.d.ts +38 -0
  101. package/util/starts-transaction.js +148 -0
  102. package/util/stringify-arrayliteral.js +13 -1
  103. package/util/wrap-row-description.js +13 -4
  104. package/util/convert-row-to-object.d.ts +0 -2
  105. package/util/convert-row-to-object.js +0 -9
@@ -0,0 +1,244 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import { Protocol } from '../protocol/protocol.js';
3
+ const BackendMessageCode = Protocol.BackendMessageCode;
4
+ /**
5
+ * A COPY ... TO STDOUT in progress, as a Readable of the raw bytes the
6
+ * server sends - text, CSV or binary, whichever the statement asked for.
7
+ * No decoding happens here: the payload is handed on exactly as it arrived,
8
+ * without a copy.
9
+ *
10
+ * The stream must be consumed (or destroyed): until the copy finishes the
11
+ * connection is still mid-statement, and an unread stream pauses the socket
12
+ * rather than buffering the whole export in memory.
13
+ */
14
+ export class CopyToStream extends Readable {
15
+ /** Format of the copy as a whole, from CopyOutResponse. */
16
+ overallFormat;
17
+ /** Per-column formats, from CopyOutResponse. */
18
+ columnFormats;
19
+ /**
20
+ * Rows the server reported for the copy. Set before 'end' is emitted, so
21
+ * it is always readable once the stream has finished.
22
+ */
23
+ rowCount;
24
+ _socket;
25
+ _error;
26
+ _started;
27
+ _discarding = false;
28
+ _settled = false;
29
+ _paused = false;
30
+ constructor(socket) {
31
+ super();
32
+ this._socket = socket;
33
+ }
34
+ /**
35
+ * Handles the copy's backend messages. Passed straight to
36
+ * PgSocket.sendQueryMessage() by IntlConnection.copyTo().
37
+ */
38
+ capture = (code, msg, done) => {
39
+ switch (code) {
40
+ case BackendMessageCode.CopyOutResponse:
41
+ this.overallFormat = msg.overallFormat;
42
+ this.columnFormats = msg.columnFormats;
43
+ this._started?.();
44
+ this._started = undefined;
45
+ break;
46
+ case BackendMessageCode.CopyData:
47
+ if (this._discarding)
48
+ break;
49
+ if (!this.push(msg.data) && !this._paused) {
50
+ this._paused = true;
51
+ this._socket.pause();
52
+ }
53
+ break;
54
+ case BackendMessageCode.CopyDone:
55
+ break;
56
+ case BackendMessageCode.CommandComplete:
57
+ this.rowCount = msg.rowCount;
58
+ break;
59
+ case BackendMessageCode.ErrorResponse:
60
+ this._error = msg;
61
+ break;
62
+ case BackendMessageCode.ReadyForQuery:
63
+ this._finish(done);
64
+ break;
65
+ default:
66
+ if (!this._error && !this.overallFormat)
67
+ this._error = new Error('Statement did not start a COPY TO STDOUT - use query() or execute() instead');
68
+ break;
69
+ }
70
+ };
71
+ /**
72
+ * Resolves once the server has accepted the copy, rejects if it never
73
+ * starts one.
74
+ */
75
+ waitStarted() {
76
+ if (this.overallFormat != null)
77
+ return Promise.resolve();
78
+ if (this._error)
79
+ return Promise.reject(this._error);
80
+ return new Promise((resolve, reject) => {
81
+ this._started = (err) => (err ? reject(err) : resolve());
82
+ });
83
+ }
84
+ /**
85
+ * Reports a failure that happened outside the message flow (the socket
86
+ * dying, say). A no-op once the copy has already settled, so the normal
87
+ * error path is never reported twice.
88
+ */
89
+ fail(err) {
90
+ if (this._settled)
91
+ return;
92
+ this._settled = true;
93
+ this._error = this._error || err;
94
+ const started = this._started;
95
+ this._started = undefined;
96
+ if (started)
97
+ started(this._error);
98
+ else if (!this.destroyed)
99
+ this.destroy(this._error);
100
+ }
101
+ _read() {
102
+ if (!this._paused)
103
+ return;
104
+ this._paused = false;
105
+ this._socket.resume();
106
+ }
107
+ _destroy(err, callback) {
108
+ this._discarding = true;
109
+ this._paused = false;
110
+ this._socket.resume();
111
+ callback(err);
112
+ }
113
+ _finish(done) {
114
+ this._settled = true;
115
+ const err = this._error;
116
+ const started = this._started;
117
+ this._started = undefined;
118
+ done(err);
119
+ if (started) {
120
+ started(err || new Error('Copy ended before it started'));
121
+ return;
122
+ }
123
+ if (this._discarding)
124
+ return;
125
+ if (err)
126
+ this.destroy(err);
127
+ else
128
+ this.push(null);
129
+ }
130
+ }
131
+ /**
132
+ * A COPY ... FROM STDIN in progress, as a Writable. Whatever is written is
133
+ * forwarded as CopyData without being copied or reinterpreted, so the
134
+ * caller decides the encoding by the format its statement asked for.
135
+ *
136
+ * 'finish' means the server accepted the whole copy: the stream waits for
137
+ * CommandComplete rather than for the last byte to leave, so rowCount is
138
+ * set by the time a `pipeline()` over it resolves. Destroying the stream
139
+ * (which `pipeline()` does when the source fails) sends CopyFail, leaving
140
+ * the connection usable rather than stuck waiting for data.
141
+ */
142
+ export class CopyFromStream extends Writable {
143
+ /** Rows the server accepted. Set before 'finish' is emitted. */
144
+ rowCount;
145
+ _socket;
146
+ _error;
147
+ _started;
148
+ _completed;
149
+ _copyEnded = false;
150
+ _settled = false;
151
+ constructor(socket) {
152
+ super();
153
+ this._socket = socket;
154
+ }
155
+ /**
156
+ * Handles the copy's backend messages. Passed straight to
157
+ * PgSocket.sendQueryMessage() by IntlConnection.copyFrom().
158
+ */
159
+ capture = (code, msg, done) => {
160
+ switch (code) {
161
+ case BackendMessageCode.CopyInResponse:
162
+ this._started?.();
163
+ this._started = undefined;
164
+ break;
165
+ case BackendMessageCode.CommandComplete:
166
+ this.rowCount = msg.rowCount;
167
+ break;
168
+ case BackendMessageCode.ErrorResponse:
169
+ this._error = msg;
170
+ if (!this._copyEnded && !this.destroyed)
171
+ this.destroy(this._error);
172
+ break;
173
+ case BackendMessageCode.ReadyForQuery:
174
+ this._finish(done);
175
+ break;
176
+ default:
177
+ if (!this._error && !this._copyEnded && this._started)
178
+ this._error = new Error('Statement did not start a COPY FROM STDIN - use query() or execute() instead');
179
+ break;
180
+ }
181
+ };
182
+ /**
183
+ * Resolves once the server is ready for data, rejects if it never asks
184
+ * for any.
185
+ */
186
+ waitStarted() {
187
+ if (this._error)
188
+ return Promise.reject(this._error);
189
+ return new Promise((resolve, reject) => {
190
+ this._started = (err) => (err ? reject(err) : resolve());
191
+ });
192
+ }
193
+ /** See CopyToStream.fail(). */
194
+ fail(err) {
195
+ if (this._settled)
196
+ return;
197
+ this._settled = true;
198
+ this._error = this._error || err;
199
+ const started = this._started;
200
+ this._started = undefined;
201
+ const completed = this._completed;
202
+ this._completed = undefined;
203
+ if (started)
204
+ started(this._error);
205
+ else if (completed)
206
+ completed(this._error);
207
+ else if (!this.destroyed)
208
+ this.destroy(this._error);
209
+ }
210
+ _write(chunk, encoding, callback) {
211
+ if (this._error)
212
+ return callback(this._error);
213
+ this._socket.sendCopyData(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding), callback);
214
+ }
215
+ _final(callback) {
216
+ if (this._error)
217
+ return callback(this._error);
218
+ this._copyEnded = true;
219
+ this._socket.sendCopyDone();
220
+ this._completed = callback;
221
+ }
222
+ _destroy(err, callback) {
223
+ if (this._copyEnded)
224
+ return callback(err);
225
+ this._copyEnded = true;
226
+ this._socket.sendCopyFail(err ? err.message : 'aborted by client');
227
+ callback(err);
228
+ }
229
+ _finish(done) {
230
+ this._settled = true;
231
+ const err = this._error;
232
+ const started = this._started;
233
+ this._started = undefined;
234
+ const completed = this._completed;
235
+ this._completed = undefined;
236
+ done(err);
237
+ if (started)
238
+ return started(err || new Error('Copy ended before it started'));
239
+ if (completed)
240
+ return completed(err);
241
+ if (err && !this.destroyed)
242
+ this.destroy(err);
243
+ }
244
+ }
@@ -1,8 +1,7 @@
1
1
  import DoublyLinked from 'doublylinked';
2
2
  import { TaskQueue } from 'power-tasks';
3
3
  import { SafeEventEmitter } from '../safe-event-emitter.js';
4
- import { convertRowToObject } from '../util/convert-row-to-object.js';
5
- import { parseRow } from '../util/parse-row.js';
4
+ import { parseObjectRow, parseRow } from '../util/parse-row.js';
6
5
  export class Cursor extends SafeEventEmitter {
7
6
  _statement;
8
7
  _portal;
@@ -38,7 +37,8 @@ export class Cursor extends SafeEventEmitter {
38
37
  const out = [];
39
38
  if (this._closed)
40
39
  return out;
41
- for (let i = 0; i < nRows; i++) {
40
+ let i;
41
+ for (i = 0; i < nRows; i++) {
42
42
  if (!this._rows.length)
43
43
  await this._fetchRows();
44
44
  if (this._rows.length)
@@ -51,12 +51,16 @@ export class Cursor extends SafeEventEmitter {
51
51
  async close() {
52
52
  if (this._closed)
53
53
  return;
54
- await this._portal.close();
55
- await this._statement.close();
54
+ const combined = await this._statement._maybeCloseWithPortal(this._portal.name);
55
+ if (!combined)
56
+ await this._portal.close();
56
57
  this.emit('close');
57
58
  this._closed = true;
58
59
  }
59
60
  async _fetchRows() {
61
+ // Both callers (next()/fetch()) already gate on _closed with no await
62
+ // in between, so this can't currently observe a change - kept as a
63
+ // defensive backstop for this private method's own contract.
60
64
  if (this._closed)
61
65
  return;
62
66
  const portal = this._portal;
@@ -65,19 +69,22 @@ export class Cursor extends SafeEventEmitter {
65
69
  const queryOptions = this._queryOptions;
66
70
  const r = await portal.execute(queryOptions.fetchCount || 100);
67
71
  if (r && r.rows && r.rows.length) {
72
+ const rows = r.rows;
68
73
  if (this._parsers) {
69
74
  const objectRows = queryOptions.objectRows;
70
75
  const fields = this.fields;
71
- const rows = r.rows;
72
- for (let i = 0; i < rows.length; i++) {
73
- const row = rows[i];
74
- parseRow(this._parsers, row, this._queryOptions);
75
- if (objectRows)
76
- rows[i] = convertRowToObject(fields, row);
76
+ const parsers = this._parsers;
77
+ const rowLen = rows.length;
78
+ let i;
79
+ for (i = 0; i < rowLen; i++) {
80
+ const { data, columnCount } = rows[i];
81
+ rows[i] = objectRows
82
+ ? parseObjectRow(parsers, data, columnCount, this._queryOptions, fields)
83
+ : parseRow(parsers, data, columnCount, this._queryOptions);
77
84
  }
78
85
  }
79
- this._rows.push(...r.rows);
80
- this.emit('fetch', r.rows);
86
+ this._rows.push(...rows);
87
+ this.emit('fetch', rows);
81
88
  }
82
89
  else {
83
90
  await this.close();
@@ -1,19 +1,24 @@
1
- import { TaskQueue } from 'power-tasks';
2
1
  import { ConnectionState } from '../constants.js';
3
2
  import type { ConnectionConfiguration } from '../interfaces/database-connection-params.js';
3
+ import type { QueryOptions } from '../interfaces/query-options.js';
4
+ import type { QueryResult } from '../interfaces/query-result.js';
4
5
  import type { ScriptExecuteOptions } from '../interfaces/script-execute-options.js';
5
6
  import type { ScriptResult } from '../interfaces/script-result.js';
6
7
  import { PgSocket } from '../protocol/pg-socket.js';
8
+ import { Protocol } from '../protocol/protocol.js';
7
9
  import { SafeEventEmitter } from '../safe-event-emitter.js';
8
- import type { Maybe } from '../types.js';
10
+ import type { Maybe, OID } from '../types.js';
9
11
  import type { Connection } from './connection.js';
12
+ import { CopyFromStream, CopyToStream } from './copy-stream.js';
10
13
  export declare class IntlConnection extends SafeEventEmitter {
14
+ private _executeReusedParserCache;
11
15
  protected _refCount: number;
12
16
  protected _config: ConnectionConfiguration;
13
17
  protected _onErrorSavePoint: string;
14
18
  transactionStatus: string;
15
19
  socket: PgSocket;
16
- statementQueue: TaskQueue;
20
+ owner?: SafeEventEmitter;
21
+ runningQueryCount: number;
17
22
  constructor(config?: ConnectionConfiguration | string);
18
23
  get config(): ConnectionConfiguration;
19
24
  get inTransaction(): boolean;
@@ -29,12 +34,72 @@ export declare class IntlConnection extends SafeEventEmitter {
29
34
  savepoint(name: string): Promise<void>;
30
35
  commit(): Promise<void>;
31
36
  rollback(): Promise<void>;
37
+ /**
38
+ * Ends the current transaction as a prepared one: it stops being tied to
39
+ * this session and waits under `name` until some connection - not
40
+ * necessarily this one, and not necessarily this process - finishes it
41
+ * with commitPrepared() or rollbackPrepared().
42
+ *
43
+ * Goes through _execute() rather than execute(): PREPARE TRANSACTION is
44
+ * not in the latter's list of transaction commands, so it would be
45
+ * wrapped in a savepoint whose RELEASE then fails, the session no longer
46
+ * being in a transaction by that point.
47
+ *
48
+ * Requires `max_prepared_transactions` above zero on the server, which is
49
+ * not the default; the server's own error says so if it is not.
50
+ */
51
+ prepareTransaction(name: string): Promise<void>;
52
+ /** Commits a transaction left waiting by prepareTransaction(). */
53
+ commitPrepared(name: string): Promise<void>;
54
+ /** Discards a transaction left waiting by prepareTransaction(). */
55
+ rollbackPrepared(name: string): Promise<void>;
32
56
  rollbackToSavepoint(name: string): Promise<void>;
33
57
  releaseSavepoint(name: string): Promise<void>;
58
+ /** Asks the server to cancel whatever this session is running. */
59
+ cancel(): Promise<void>;
34
60
  ref(): void;
35
61
  unref(): boolean;
36
62
  assertConnected(): void;
63
+ /**
64
+ * Runs a COPY ... TO STDOUT and hands back its bytes as they arrive.
65
+ * Resolves once the server has accepted the copy, not once it has
66
+ * finished - the whole point is not to hold the export in memory.
67
+ */
68
+ copyTo(sql: string): Promise<CopyToStream>;
69
+ /**
70
+ * Runs a COPY ... FROM STDIN and hands back a stream to feed it.
71
+ * Resolves once the server is ready for data.
72
+ */
73
+ copyFrom(sql: string): Promise<CopyFromStream>;
37
74
  protected _execute(sql: string, options?: ScriptExecuteOptions, cb?: (event: string, ...args: any[]) => void): Promise<ScriptResult>;
75
+ /**
76
+ * One-shot Extended Query fast path: Parse+Bind+Describe+Execute+Sync as
77
+ * a single round trip (unnamed statement/portal, no Close needed), used
78
+ * by Connection.query() instead of prepare()+PreparedStatement.execute()
79
+ * +close()'s 7 round trips whenever there's no cursor and no explicit/
80
+ * active transaction to wrap (see Connection.query() for that gating).
81
+ */
82
+ queryOnce(sql: string, paramTypes: Maybe<Maybe<OID>[]>, params: Maybe<Maybe<any>[]>, options: QueryOptions): Promise<QueryResult>;
83
+ /**
84
+ * Parse+Describe(statement)+Sync as a single round trip - used by
85
+ * PreparedStatement.prepare() instead of a separately-awaited Parse then
86
+ * a separately-awaited Sync, and fetches the RowDescription/NoData in the
87
+ * same round trip so the caller can cache it (see executeReused()) rather
88
+ * than every later execute() re-Describing its own portal.
89
+ */
90
+ prepareOnce(sql: string, paramTypes: Maybe<Maybe<OID>[]>, statementName: string): Promise<{
91
+ fields?: Protocol.RowDescription[];
92
+ }>;
93
+ /**
94
+ * Bind+Execute+Sync as a single round trip against an unnamed portal, for
95
+ * an already-prepared (named) statement - used by
96
+ * PreparedStatement._execute()'s non-cursor path instead of the 4-round-
97
+ * trip Portal.bind()/retrieveFields()/execute()/close() sequence. Takes
98
+ * the RowDescription fields prepareOnce() already fetched instead of
99
+ * re-Describing a fresh portal on every call.
100
+ */
101
+ executeReused(statementName: string, cachedFields: Protocol.RowDescription[] | undefined, paramTypes: Maybe<Maybe<OID>[]>, params: Maybe<Maybe<any>[]>, options: QueryOptions): Promise<QueryResult>;
102
+ emit(event: string | symbol, ...args: any[]): boolean;
38
103
  protected _onError(err: Error): void;
39
104
  }
40
105
  export declare function getIntlConnection(connection: Connection): IntlConnection;