postgrejs 2.23.0 → 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 (109) hide show
  1. package/CHANGELOG.md +162 -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.d.ts +2 -2
  22. package/data-type-map.js +4 -2
  23. package/data-types/bool-type.js +14 -10
  24. package/data-types/box-type.js +15 -8
  25. package/data-types/bytea-type.js +9 -5
  26. package/data-types/char-type.js +11 -5
  27. package/data-types/circle-type.js +15 -7
  28. package/data-types/date-type.js +25 -19
  29. package/data-types/float4-type.js +10 -3
  30. package/data-types/float8-type.js +12 -3
  31. package/data-types/int2-type.js +9 -4
  32. package/data-types/int2-vector-type.js +10 -4
  33. package/data-types/int4-type.js +11 -5
  34. package/data-types/int8-type.js +22 -7
  35. package/data-types/json-type.js +17 -8
  36. package/data-types/jsonb-type.js +20 -14
  37. package/data-types/lseg-type.js +16 -8
  38. package/data-types/numeric-type.d.ts +8 -0
  39. package/data-types/numeric-type.js +112 -8
  40. package/data-types/oid-type.d.ts +0 -1
  41. package/data-types/oid-type.js +9 -10
  42. package/data-types/oid-vector-type.d.ts +9 -0
  43. package/data-types/oid-vector-type.js +51 -0
  44. package/data-types/point-type.js +14 -6
  45. package/data-types/time-type.js +20 -13
  46. package/data-types/timestamp-type.js +32 -25
  47. package/data-types/timestamptz-type.d.ts +1 -1
  48. package/data-types/timestamptz-type.js +34 -21
  49. package/data-types/uuid-type.js +21 -11
  50. package/data-types/varchar-type.js +16 -4
  51. package/index.d.ts +5 -0
  52. package/index.js +5 -0
  53. package/interfaces/data-type.d.ts +5 -3
  54. package/interfaces/database-connection-params.d.ts +95 -0
  55. package/interfaces/query-options.d.ts +17 -0
  56. package/interfaces/script-execute-options.d.ts +17 -0
  57. package/package.json +3 -4
  58. package/protocol/backend.d.ts +6 -1
  59. package/protocol/backend.js +70 -45
  60. package/protocol/buffer-reader.d.ts +0 -2
  61. package/protocol/buffer-reader.js +3 -10
  62. package/protocol/cert-signature.d.ts +12 -0
  63. package/protocol/cert-signature.js +114 -0
  64. package/protocol/database-error.js +1 -0
  65. package/protocol/frontend.d.ts +16 -0
  66. package/protocol/frontend.js +58 -6
  67. package/protocol/pg-socket.d.ts +221 -10
  68. package/protocol/pg-socket.js +539 -102
  69. package/protocol/pgoutput.d.ts +76 -0
  70. package/protocol/pgoutput.js +155 -0
  71. package/protocol/protocol.d.ts +3 -4
  72. package/protocol/protocol.js +1 -1
  73. package/protocol/sasl.d.ts +11 -1
  74. package/protocol/sasl.js +35 -4
  75. package/protocol/smart-buffer.d.ts +0 -2
  76. package/protocol/smart-buffer.js +11 -16
  77. package/types.d.ts +5 -3
  78. package/util/abort-signal.d.ts +24 -0
  79. package/util/abort-signal.js +63 -0
  80. package/util/array-calculatedim.js +3 -1
  81. package/util/bigint-methods.js +1 -1
  82. package/util/connection-config.js +77 -3
  83. package/util/decode-binaryarray.d.ts +1 -1
  84. package/util/decode-binaryarray.js +15 -6
  85. package/util/encode-binaryarray.js +7 -3
  86. package/util/escape-identifier.d.ts +14 -0
  87. package/util/escape-identifier.js +32 -0
  88. package/util/escape-literal.js +3 -0
  89. package/util/fast-parseint.d.ts +1 -0
  90. package/util/fast-parseint.js +12 -17
  91. package/util/format-datetime.d.ts +10 -0
  92. package/util/format-datetime.js +71 -0
  93. package/util/get-parsers.js +31 -10
  94. package/util/parse-array.js +2 -2
  95. package/util/parse-bytea.d.ts +2 -0
  96. package/util/parse-bytea.js +83 -0
  97. package/util/parse-datetime.d.ts +3 -1
  98. package/util/parse-datetime.js +86 -17
  99. package/util/parse-row.d.ts +3 -1
  100. package/util/parse-row.js +60 -5
  101. package/util/parse-time.js +8 -1
  102. package/util/sql-tag.d.ts +60 -0
  103. package/util/sql-tag.js +202 -0
  104. package/util/starts-transaction.d.ts +38 -0
  105. package/util/starts-transaction.js +148 -0
  106. package/util/stringify-arrayliteral.js +13 -1
  107. package/util/wrap-row-description.js +13 -4
  108. package/util/convert-row-to-object.d.ts +0 -2
  109. package/util/convert-row-to-object.js +0 -9
@@ -11,7 +11,5 @@ export declare class BufferReader {
11
11
  readCString(encoding?: BufferEncoding): string;
12
12
  readLString(len: number, encoding?: BufferEncoding): string | null;
13
13
  readBuffer(len?: number): Buffer;
14
- moveBy(n: number): this;
15
- moveTo(pos: number): this;
16
14
  private _checkReadable;
17
15
  }
@@ -39,6 +39,8 @@ export class BufferReader {
39
39
  }
40
40
  readCString(encoding) {
41
41
  const idx = this.buffer.indexOf(0, this.offset);
42
+ if (idx === -1)
43
+ throw new Error('Eof in buffer detected (readCString)');
42
44
  const v = this.buffer.toString(encoding, this.offset, idx);
43
45
  this.offset = idx + 1;
44
46
  return v;
@@ -55,19 +57,10 @@ export class BufferReader {
55
57
  if (len)
56
58
  this._checkReadable(len);
57
59
  const end = len !== undefined ? this.offset + len : this.length;
58
- const buf = this.buffer.slice(this.offset, end);
60
+ const buf = this.buffer.subarray(this.offset, end);
59
61
  this.offset = end;
60
62
  return buf;
61
63
  }
62
- moveBy(n) {
63
- return this.moveTo(this.offset + n);
64
- }
65
- moveTo(pos) {
66
- if (pos >= this.length)
67
- throw new Error('Eof in buffer detected');
68
- this.offset = pos;
69
- return this;
70
- }
71
64
  _checkReadable(size) {
72
65
  if (this.offset + size - 1 >= this.length)
73
66
  throw new Error('Eof in buffer detected');
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Finds the hash a certificate was signed with, which is the hash
3
+ * `tls-server-end-point` channel binding has to use (RFC 5929).
4
+ *
5
+ * Node exposes a certificate's SHA-1, SHA-256 and SHA-512 fingerprints but
6
+ * not the algorithm it was signed with, and X509Certificate.toLegacyObject()
7
+ * does not carry it either - so the DER has to be read. Only as far as the
8
+ * signature algorithm's OID: a Certificate is a SEQUENCE of tbsCertificate,
9
+ * signatureAlgorithm and signatureValue, so this skips the first and reads
10
+ * the OID out of the second.
11
+ */
12
+ export declare function signatureHashOfCertificate(der: Buffer): string;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Finds the hash a certificate was signed with, which is the hash
3
+ * `tls-server-end-point` channel binding has to use (RFC 5929).
4
+ *
5
+ * Node exposes a certificate's SHA-1, SHA-256 and SHA-512 fingerprints but
6
+ * not the algorithm it was signed with, and X509Certificate.toLegacyObject()
7
+ * does not carry it either - so the DER has to be read. Only as far as the
8
+ * signature algorithm's OID: a Certificate is a SEQUENCE of tbsCertificate,
9
+ * signatureAlgorithm and signatureValue, so this skips the first and reads
10
+ * the OID out of the second.
11
+ */
12
+ function readLength(data, index) {
13
+ let length = data[index++];
14
+ if (length < 0x80)
15
+ return { length, index };
16
+ const lengthBytes = length & 0x7f;
17
+ if (lengthBytes > 4)
18
+ throw certError('length field too large', data);
19
+ length = 0;
20
+ for (let i = 0; i < lengthBytes; i++)
21
+ length = (length << 8) | data[index++];
22
+ return { length, index };
23
+ }
24
+ function expectSequence(data, index) {
25
+ if (data[index++] !== 0x30)
26
+ throw certError('expected a SEQUENCE', data);
27
+ return { index: readLength(data, index).index };
28
+ }
29
+ function readOID(data, index) {
30
+ if (data[index++] !== 0x06)
31
+ throw certError('expected an OID', data);
32
+ const { length, index: start } = readLength(data, index);
33
+ index = start;
34
+ const end = index + length;
35
+ // The first byte packs the first two arcs as 40 * a + b.
36
+ const first = data[index++];
37
+ let oid = Math.floor(first / 40) + '.' + (first % 40);
38
+ let value = 0;
39
+ while (index < end) {
40
+ const byte = data[index++];
41
+ value = (value << 7) | (byte & 0x7f);
42
+ // The high bit marks a continuation, so an arc ends when it is clear.
43
+ if (!(byte & 0x80)) {
44
+ oid += '.' + value;
45
+ value = 0;
46
+ }
47
+ }
48
+ return { oid, index };
49
+ }
50
+ function certError(message, data) {
51
+ return new Error(`Channel binding: ${message} while reading the server certificate (${data.length} bytes)`);
52
+ }
53
+ // OID -> the Node hash name to feed createHash().
54
+ const SIGNATURE_HASHES = {
55
+ // RSA
56
+ '1.2.840.113549.1.1.4': 'md5',
57
+ '1.2.840.113549.1.1.5': 'sha1',
58
+ '1.2.840.113549.1.1.11': 'sha256',
59
+ '1.2.840.113549.1.1.12': 'sha384',
60
+ '1.2.840.113549.1.1.13': 'sha512',
61
+ '1.2.840.113549.1.1.14': 'sha224',
62
+ '1.2.840.113549.1.1.15': 'sha512-224',
63
+ '1.2.840.113549.1.1.16': 'sha512-256',
64
+ // ECDSA
65
+ '1.2.840.10045.4.1': 'sha1',
66
+ '1.2.840.10045.4.3.1': 'sha224',
67
+ '1.2.840.10045.4.3.2': 'sha256',
68
+ '1.2.840.10045.4.3.3': 'sha384',
69
+ '1.2.840.10045.4.3.4': 'sha512',
70
+ // Ed25519, whose signature hashes with SHA-512.
71
+ '1.3.101.110': 'sha512',
72
+ '1.3.101.112': 'sha512',
73
+ };
74
+ // RSASSA-PSS names its hash separately, in the algorithm parameters.
75
+ const PSS_HASHES = {
76
+ '1.2.840.113549.2.5': 'md5',
77
+ '1.3.14.3.2.26': 'sha1',
78
+ '2.16.840.1.101.3.4.2.1': 'sha256',
79
+ '2.16.840.1.101.3.4.2.2': 'sha384',
80
+ '2.16.840.1.101.3.4.2.3': 'sha512',
81
+ };
82
+ export function signatureHashOfCertificate(der) {
83
+ let index = expectSequence(der, 0).index; // Certificate
84
+ const tbs = readLength(der, index + 1); // tbsCertificate's own header
85
+ if (der[index] !== 0x30)
86
+ throw certError('expected tbsCertificate', der);
87
+ index = tbs.index + tbs.length; // skip it whole
88
+ index = expectSequence(der, index).index; // AlgorithmIdentifier
89
+ const { oid, index: afterOID } = readOID(der, index);
90
+ if (oid === '1.2.840.113549.1.1.10') {
91
+ // RSASSA-PSS: parameters SEQUENCE, then [0] holding the hash algorithm.
92
+ let i = expectSequence(der, afterOID).index;
93
+ if (der[i++] !== 0xa0)
94
+ throw certError('expected the PSS hash tag', der);
95
+ i = readLength(der, i).index;
96
+ i = expectSequence(der, i).index;
97
+ const { oid: hashOID } = readOID(der, i);
98
+ const hash = PSS_HASHES[hashOID];
99
+ if (!hash)
100
+ throw certError(`unknown PSS hash OID ${hashOID}`, der);
101
+ return upgradeWeak(hash);
102
+ }
103
+ const hash = SIGNATURE_HASHES[oid];
104
+ if (!hash) {
105
+ if (oid === '1.3.101.111' || oid === '1.3.101.113')
106
+ throw certError('Ed448 certificates are not supported by PostgreSQL', der);
107
+ throw certError(`unknown signature algorithm OID ${oid}`, der);
108
+ }
109
+ return upgradeWeak(hash);
110
+ }
111
+ /** RFC 5929: a certificate signed with MD5 or SHA-1 binds with SHA-256. */
112
+ function upgradeWeak(hash) {
113
+ return hash === 'md5' || hash === 'sha1' ? 'sha256' : hash;
114
+ }
@@ -1,3 +1,4 @@
1
+ import { Protocol } from './protocol.js';
1
2
  export class DatabaseError extends Error {
2
3
  severity;
3
4
  code;
@@ -43,6 +43,13 @@ export declare class Frontend {
43
43
  constructor(options?: FrontendOptions);
44
44
  getSSLRequestMessage(): Buffer;
45
45
  getStartupMessage(args: Frontend.StartupMessageArgs): Buffer;
46
+ /**
47
+ * Asks the server to cancel whatever the given session is currently
48
+ * running. Sent on its own fresh connection - a backend busy with a query
49
+ * is not reading its own socket, which is the whole reason this cannot go
50
+ * down the normal one.
51
+ */
52
+ getCancelRequestMessage(processID: number, secretKey: number): Buffer;
46
53
  getPasswordMessage(password: string): Buffer;
47
54
  getSASLMessage(sasl: SASL.Session): Buffer;
48
55
  getSASLFinalMessage(session: SASL.Session): Buffer;
@@ -52,6 +59,15 @@ export declare class Frontend {
52
59
  getExecuteMessage(args: Frontend.ExecuteMessageArgs): Buffer;
53
60
  getCloseMessage(args: Frontend.CloseMessageArgs): Buffer;
54
61
  getQueryMessage(sql: string): Buffer;
62
+ /**
63
+ * Returns the CopyData header and the caller's payload as two buffers
64
+ * rather than one, so a bulk import never copies the caller's bytes.
65
+ * PgSocket writes an array of buffers as a single corked write anyway,
66
+ * so this costs nothing on the wire.
67
+ */
68
+ getCopyDataMessage(data: Buffer): Buffer[];
69
+ getCopyDoneMessage(): Buffer;
70
+ getCopyFailMessage(message: string): Buffer;
55
71
  getFlushMessage(): Buffer;
56
72
  getTerminateMessage(): Buffer;
57
73
  getSyncMessage(): Buffer;
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
2
+ import { DataTypeMap } from '../data-type-map.js';
2
3
  import { encodeBinaryArray } from '../util/encode-binaryarray.js';
3
4
  import { stringifyArrayLiteral } from '../util/stringify-arrayliteral.js';
4
5
  import { Protocol } from './protocol.js';
@@ -25,6 +26,13 @@ const StaticSyncBuffer = Buffer.from([
25
26
  0x00,
26
27
  0x04,
27
28
  ]);
29
+ const StaticCopyDoneBuffer = Buffer.from([
30
+ Protocol.FrontendMessageCode.CopyDone,
31
+ 0x00,
32
+ 0x00,
33
+ 0x00,
34
+ 0x04,
35
+ ]);
28
36
  export class Frontend {
29
37
  _io;
30
38
  constructor(options) {
@@ -53,6 +61,22 @@ export class Frontend {
53
61
  .writeUInt8(0);
54
62
  return setLengthAndFlush(io, 0);
55
63
  }
64
+ /**
65
+ * Asks the server to cancel whatever the given session is currently
66
+ * running. Sent on its own fresh connection - a backend busy with a query
67
+ * is not reading its own socket, which is the whole reason this cannot go
68
+ * down the normal one.
69
+ */
70
+ getCancelRequestMessage(processID, secretKey) {
71
+ return this._io
72
+ .start()
73
+ .writeUInt32BE(16) // Length of message contents in bytes, including self.
74
+ .writeUInt16BE(1234)
75
+ .writeUInt16BE(5678)
76
+ .writeUInt32BE(processID)
77
+ .writeUInt32BE(secretKey)
78
+ .flush();
79
+ }
56
80
  getPasswordMessage(password) {
57
81
  const io = this._io
58
82
  .start()
@@ -113,13 +137,16 @@ export class Frontend {
113
137
  if (params && params.length) {
114
138
  io.writeInt16BE(params.length);
115
139
  const formatOffset = io.offset;
116
- for (let i = 0; i < params.length; i++) {
140
+ const l = params.length;
141
+ let i;
142
+ for (i = 0; i < l; i++) {
117
143
  io.writeInt16BE(0); // Preserve
118
144
  }
119
145
  // Write parameter values
120
- io.writeUInt16BE(params.length);
121
- for (let i = 0; i < params?.length; i++) {
122
- let v = params[i];
146
+ io.writeUInt16BE(l);
147
+ let v;
148
+ for (i = 0; i < l; i++) {
149
+ v = params[i];
123
150
  if (v === null || v === undefined) {
124
151
  io.writeInt32BE(-1);
125
152
  continue;
@@ -174,8 +201,10 @@ export class Frontend {
174
201
  io.writeUInt16BE(0);
175
202
  }
176
203
  if (Array.isArray(columnFormat)) {
177
- io.writeUInt16BE(columnFormat.length);
178
- for (let i = 0; i < columnFormat.length; i++) {
204
+ const l = columnFormat.length;
205
+ io.writeUInt16BE(l);
206
+ let i;
207
+ for (i = 0; i < l; i++) {
179
208
  io.writeUInt16BE(columnFormat[i]);
180
209
  }
181
210
  }
@@ -236,6 +265,29 @@ export class Frontend {
236
265
  .writeCString(sql || '', 'utf8');
237
266
  return setLengthAndFlush(io, 1);
238
267
  }
268
+ /**
269
+ * Returns the CopyData header and the caller's payload as two buffers
270
+ * rather than one, so a bulk import never copies the caller's bytes.
271
+ * PgSocket writes an array of buffers as a single corked write anyway,
272
+ * so this costs nothing on the wire.
273
+ */
274
+ getCopyDataMessage(data) {
275
+ const header = Buffer.allocUnsafe(5);
276
+ header[0] = Protocol.FrontendMessageCode.CopyData;
277
+ header.writeUInt32BE(data.length + 4, 1);
278
+ return [header, data];
279
+ }
280
+ getCopyDoneMessage() {
281
+ return StaticCopyDoneBuffer;
282
+ }
283
+ getCopyFailMessage(message) {
284
+ const io = this._io
285
+ .start()
286
+ .writeInt8(Protocol.FrontendMessageCode.CopyFail)
287
+ .writeInt32BE(0) // Preserve header
288
+ .writeCString(message || '', 'utf8');
289
+ return setLengthAndFlush(io, 1);
290
+ }
239
291
  getFlushMessage() {
240
292
  return StaticFlushBuffer;
241
293
  }
@@ -1,7 +1,9 @@
1
+ import net from 'node:net';
2
+ import tls from 'node:tls';
1
3
  import { ConnectionState } from '../constants.js';
2
4
  import type { ConnectionConfiguration } from '../interfaces/database-connection-params.js';
3
5
  import { SafeEventEmitter } from '../safe-event-emitter.js';
4
- import { Callback, Maybe } from '../types.js';
6
+ import type { Callback, Maybe } from '../types.js';
5
7
  import { Frontend } from './frontend.js';
6
8
  import { Protocol } from './protocol.js';
7
9
  export type CaptureCallback = (code: Protocol.BackendMessageCode, msg: any, done: (err: Maybe<Error>, result?: any) => void) => void | Promise<void>;
@@ -18,33 +20,242 @@ export declare class PgSocket extends SafeEventEmitter {
18
20
  private _saslSession?;
19
21
  private _processID?;
20
22
  private _secretKey?;
23
+ private _captureQueue;
24
+ private _pendingWrites;
25
+ private _flushScheduled;
26
+ private _hosts;
27
+ private _hostIndex;
28
+ private _sessionAttrsChecked;
29
+ private _standbyState?;
21
30
  constructor(options: ConnectionConfiguration);
22
31
  get state(): ConnectionState;
23
32
  get processID(): Maybe<number>;
24
33
  get secretKey(): Maybe<number>;
25
34
  get sessionParameters(): Record<string, string>;
26
35
  connect(): void;
36
+ protected _connectToHost(): void;
37
+ /**
38
+ * Negotiates TLS on an already TCP-connected `socket`, if configured -
39
+ * shared between the long-lived connection above (_connectToHost) and
40
+ * cancel() below, which needs the exact same host/TLS handling for its
41
+ * own short-lived socket, just without ever sending a StartupMessage
42
+ * afterwards. Resolves `onReady` with whichever socket ends up ready to
43
+ * speak the PostgreSQL protocol: the plain one, or its TLS upgrade.
44
+ */
45
+ protected _negotiateTls(socket: net.Socket, target: {
46
+ host: string;
47
+ port?: number;
48
+ }, onReady: (socket: net.Socket | tls.TLSSocket) => void, onError: (err: Error) => void): void;
49
+ /**
50
+ * Asks the server to cancel whatever this session is currently running.
51
+ *
52
+ * Opens its own short-lived connection and closes it again: a backend busy
53
+ * with a query is not reading its own socket, so the request cannot travel
54
+ * down the connection it is meant to interrupt. The server answers nothing
55
+ * - it either finds a matching session and signals it or does not - so
56
+ * this resolves once the bytes are out, and the cancelled query reports
57
+ * the outcome itself, as an ordinary error on its own connection.
58
+ *
59
+ * No StartupMessage or authentication is ever sent here - the
60
+ * CancelRequest is the only message this socket carries, and it is
61
+ * self-authorized by the processID/secretKey pair BackendKeyData handed
62
+ * out to the session being cancelled, not by a separate login. TLS is
63
+ * still negotiated via _negotiateTls() when configured, because a
64
+ * `hostssl` pg_hba.conf rule can reject a plaintext connection outright
65
+ * before the server ever reads what request it carries - and the target
66
+ * is `_hosts[_hostIndex]`, the host this session actually ended up
67
+ * connected to, not `options.host`, which after a multi-host failover may
68
+ * name a candidate this session never used.
69
+ */
70
+ cancel(): Promise<void>;
27
71
  close(): void;
28
- sendParseMessage(args: Frontend.ParseMessageArgs, cb?: Callback): void;
29
- sendBindMessage(args: Frontend.BindMessageArgs, cb?: Callback): void;
30
- sendDescribeMessage(args: Frontend.DescribeMessageArgs, cb?: Callback): void;
31
- sendExecuteMessage(args: Frontend.ExecuteMessageArgs, cb?: Callback): void;
32
- sendCloseMessage(args: Frontend.CloseMessageArgs, cb?: Callback): void;
33
- sendQueryMessage(sql: string, cb?: Callback): void;
72
+ sendExecuteMessage(args: Frontend.ExecuteMessageArgs, cb: CaptureCallback): Promise<any>;
73
+ sendCloseMessage(args: Frontend.CloseMessageArgs, cb: CaptureCallback): Promise<any>;
74
+ /**
75
+ * Sends Parse+Bind+Describe+Execute+Sync as a single write with a single
76
+ * FIFO capture entry, instead of 5 separate sendXMessage() round trips -
77
+ * the one-shot Extended Query fast path (IntlConnection.queryOnce()).
78
+ * Callers pass unnamed statement/portal (omit statement/portal/name) so
79
+ * no Close message is needed at all: PostgreSQL auto-clears an unnamed
80
+ * statement/portal at the next Parse/Bind referencing the unnamed name.
81
+ */
82
+ sendExtendedQueryMessages(args: {
83
+ parse: Frontend.ParseMessageArgs;
84
+ bind: Frontend.BindMessageArgs;
85
+ describe: Frontend.DescribeMessageArgs;
86
+ execute: Frontend.ExecuteMessageArgs;
87
+ }, cb: CaptureCallback): Promise<any>;
88
+ /**
89
+ * Parse + Describe(statement) + Sync as a single round trip - used by
90
+ * PreparedStatement.prepare() instead of a separate awaited Parse then a
91
+ * separate awaited Sync, and fetches the RowDescription/NoData in the
92
+ * same round trip so PreparedStatement can cache it instead of every
93
+ * later execute() re-Describing its own portal.
94
+ */
95
+ sendPrepareMessages(args: {
96
+ parse: Frontend.ParseMessageArgs;
97
+ describe: Frontend.DescribeMessageArgs;
98
+ }, cb: CaptureCallback): Promise<any>;
99
+ /**
100
+ * Bind + Execute + Sync as a single round trip, against an UNNAMED
101
+ * portal (args.bind.portal/args.execute.portal left unset) - used by
102
+ * PreparedStatement._execute() to reuse an already-prepared (named)
103
+ * statement without the per-call Describe/Close that Portal's own
104
+ * bind()/retrieveFields()/execute()/close() sequence needs for a
105
+ * freshly-created named portal. No Close message is needed: PostgreSQL
106
+ * auto-clears an unnamed portal at the next Bind naming it.
107
+ */
108
+ sendBindExecuteMessages(args: {
109
+ bind: Frontend.BindMessageArgs;
110
+ execute: Frontend.ExecuteMessageArgs;
111
+ }, cb: CaptureCallback): Promise<any>;
112
+ /**
113
+ * Bind + Describe(portal) + Flush as a single round trip (NOT Sync - see
114
+ * Portal.bindAndRetrieveFields()'s doc comment for why a Sync here would
115
+ * be unsafe: it would commit an implicit/autocommit transaction and
116
+ * destroy the just-bound named portal before any fetch() could use it).
117
+ * Used by PreparedStatement._execute()'s cursor branch instead of two
118
+ * separately-awaited Portal.bind()/retrieveFields() round trips.
119
+ */
120
+ sendBindDescribeMessages(args: {
121
+ bind: Frontend.BindMessageArgs;
122
+ describe: Frontend.DescribeMessageArgs;
123
+ }, cb: CaptureCallback): Promise<any>;
124
+ /**
125
+ * Close + Sync under ONE capture, rather than a Close capture followed by
126
+ * a separate Sync capture. That split is unsafe after an ErrorResponse:
127
+ * the server then skips every message until Sync, so the only reply is
128
+ * the Sync's ReadyForQuery - which the FIFO hands to the *Close* capture
129
+ * (the older entry), making it fail with "unexpected response message
130
+ * (Z)" and leaving the Sync capture orphaned, to silently swallow the
131
+ * first message of whatever request comes next. With one capture,
132
+ * ReadyForQuery is simply this request's end marker whether or not the
133
+ * Close itself was honoured.
134
+ */
135
+ sendCloseAndSyncMessages(args: Frontend.CloseMessageArgs, cb: CaptureCallback): Promise<any>;
136
+ /**
137
+ * Close(portal) + Close(statement) + Sync as a single round trip - used
138
+ * by PreparedStatement._maybeCloseWithPortal() when a Cursor's close()
139
+ * brings the owning PreparedStatement's refcount to 0, instead of two
140
+ * separately-batched Close+Sync round trips (Portal.close() then
141
+ * PreparedStatement._close()). Sync here is safe/intended: this is final
142
+ * teardown, no portal needs to survive past it.
143
+ */
144
+ sendClosePortalAndStatementMessages(args: {
145
+ portal: Frontend.CloseMessageArgs;
146
+ statement: Frontend.CloseMessageArgs;
147
+ }, cb: CaptureCallback): Promise<any>;
148
+ sendQueryMessage(sql: string, cb: CaptureCallback): Promise<any>;
149
+ /**
150
+ * COPY IN data. Unlike every other send*() here this pushes nothing onto
151
+ * the capture queue: while a COPY is in progress the server answers no
152
+ * individual CopyData message, so the response still belongs to the
153
+ * Query capture that opened the copy.
154
+ *
155
+ * Returns false when the socket's buffer is full, exactly as
156
+ * net.Socket.write() does - the caller is expected to stop writing until
157
+ * `cb` fires, which is what CopyFromStream hands straight to its own
158
+ * Writable callback.
159
+ */
160
+ sendCopyData(data: Buffer, cb?: Callback): boolean;
161
+ /** Ends a COPY IN normally; the server replies CommandComplete. */
162
+ sendCopyDone(cb?: Callback): boolean;
163
+ /**
164
+ * Aborts a COPY IN. The server discards the copy, reports `message` as an
165
+ * ErrorResponse and returns to its normal state - without this a failed
166
+ * import would leave the connection waiting for data forever.
167
+ */
168
+ sendCopyFail(message: string, cb?: Callback): boolean;
169
+ /**
170
+ * Stops reading from the socket, so a COPY OUT consumer that cannot keep
171
+ * up doesn't buffer the whole export in memory. Safe to call repeatedly.
172
+ */
173
+ pause(): void;
174
+ resume(): void;
34
175
  sendFlushMessage(cb?: Callback): void;
35
176
  sendTerminateMessage(cb?: Callback): void;
36
- sendSyncMessage(): void;
37
- capture(callback: CaptureCallback): Promise<any>;
177
+ sendSyncMessage(cb: CaptureCallback): Promise<any>;
178
+ /**
179
+ * The only way anything outside this class can receive a correlated
180
+ * backend response. Sends `data` and pushes `cb` onto the FIFO capture
181
+ * queue in the same call, so a response can never arrive before its
182
+ * capturer is registered, and a capturer can never be registered without
183
+ * a message actually going out for it - there is no separate, decoupled
184
+ * "start listening" step to race or misuse.
185
+ */
186
+ private _sendAndCapture;
38
187
  protected _removeListeners(): void;
39
188
  protected _reset(): void;
40
189
  protected _handleConnect(): void;
190
+ /**
191
+ * Called once the server is ready. With `targetSessionAttrs` set, the
192
+ * session has to be checked before it is handed over: a server that does
193
+ * not match is dropped and the next candidate tried, which is how
194
+ * `read-write` finds whichever node is currently the primary.
195
+ */
196
+ protected _finishConnect(): void;
197
+ /** Accepts this server, or drops it and moves to the next candidate. */
198
+ protected _applySessionAttrs(wanted: string, standby: boolean, readOnly: boolean): void;
41
199
  protected _handleClose(): void;
42
200
  protected _handleError(err: unknown): void;
201
+ /** Rejects and clears every still-open capture, e.g. on close/error. */
202
+ private _failPendingCaptures;
43
203
  protected _handleData(data: Buffer): void;
204
+ /**
205
+ * Routes one backend message to the capture at the front of the FIFO -
206
+ * the oldest still-open request, i.e. the one whose turn it is given the
207
+ * order messages were written to the socket. This *is* the request/
208
+ * response correlation, since the wire protocol has no id of its own.
209
+ * The same entry keeps receiving messages (ParseComplete, BindComplete,
210
+ * RowDescription, DataRow*, CommandComplete, ReadyForQuery, ...) until
211
+ * its callback calls `done()`, at which point it is dequeued and the
212
+ * next entry becomes the one messages are routed to.
213
+ */
214
+ private _dispatch;
44
215
  protected _resolvePassword(cb: (password: string) => void): void;
45
216
  protected _handleAuthenticationMessage(msg?: any): void;
217
+ /**
218
+ * tls-server-end-point: the server certificate hashed with the algorithm
219
+ * its own signature used (RFC 5929), which is why the DER has to be read
220
+ * rather than one of Node's fixed fingerprints taken.
221
+ */
222
+ protected _channelBindingData(socket: tls.TLSSocket): Buffer;
46
223
  protected _handleParameterStatus(msg: Protocol.ParameterStatusMessage): void;
47
224
  protected _handleBackendKeyData(msg: Protocol.BackendKeyDataMessage): void;
48
225
  protected _handleCommandComplete(msg: any): Protocol.CommandCompleteMessage;
49
- protected _send(data: Buffer, cb?: Callback): void;
226
+ /**
227
+ * Queues `data` for the socket instead of writing it straight through:
228
+ * everything handed to _send() during the same synchronous burst (e.g.
229
+ * many concurrent query() calls fired via Promise.all over one
230
+ * connection) is flushed together on the next tick, corked, so Node
231
+ * turns the whole batch into a single underlying writev rather than one
232
+ * socket write per call. Measured against postgres.js, which coalesces
233
+ * the same way at the application level (its connection.js write()/
234
+ * nextWrite() pair, flushing on a 1KB threshold or setImmediate): 50
235
+ * concurrent prepared executes cost it 3 socket writes where this cost
236
+ * us 50, and that write path was the single largest non-idle item in a
237
+ * CPU profile of the scenario.
238
+ *
239
+ * nextTick rather than setImmediate, deliberately: it still batches a
240
+ * whole synchronous burst, but a lone query doesn't have to wait for the
241
+ * event loop's check phase before its bytes go out - which would trade
242
+ * away the sequential/single-query scenarios to win the concurrent ones.
243
+ *
244
+ * Returns whether the socket could accept the data at all; the write
245
+ * itself is reported through `cb` (and a socket that dies before the
246
+ * flush rejects every pending capture via _failPendingCaptures()).
247
+ */
248
+ protected _send(data: Buffer | Buffer[], cb?: Callback): boolean;
249
+ /**
250
+ * Writes COPY bytes straight through instead of queueing them for the
251
+ * next tick like _send() does. Two reasons: during a copy the capture
252
+ * queue holds exactly one entry (the Query that opened it), so _send()'s
253
+ * "batch only when more than one request is in flight" test would never
254
+ * fire and every chunk would be written on its own anyway; and the
255
+ * caller needs write()'s own return value to know when to stop, which a
256
+ * deferred write cannot give it.
257
+ */
258
+ protected _sendCopy(data: Buffer | Buffer[], cb?: Callback): boolean;
259
+ /** Arrow property, not a method: used as a bare process.nextTick callback. */
260
+ private _flushPendingWrites;
50
261
  }