postgrejs 2.23.1 → 3.0.2

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 (107) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +169 -16
  5. package/connection/connection.js +318 -56
  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 +113 -5
  10. package/connection/intl-connection.js +500 -70
  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 +129 -109
  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 +118 -9
  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/command-result.d.ts +2 -1
  52. package/interfaces/data-type.d.ts +5 -3
  53. package/interfaces/database-connection-params.d.ts +108 -0
  54. package/interfaces/query-options.d.ts +23 -0
  55. package/interfaces/script-execute-options.d.ts +23 -0
  56. package/interfaces/script-result.d.ts +3 -2
  57. package/package.json +2 -3
  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/frontend.d.ts +16 -0
  65. package/protocol/frontend.js +70 -11
  66. package/protocol/pg-socket.d.ts +220 -9
  67. package/protocol/pg-socket.js +539 -102
  68. package/protocol/pgoutput.d.ts +76 -0
  69. package/protocol/pgoutput.js +155 -0
  70. package/protocol/protocol.d.ts +3 -4
  71. package/protocol/protocol.js +1 -1
  72. package/protocol/sasl.d.ts +11 -1
  73. package/protocol/sasl.js +36 -5
  74. package/protocol/smart-buffer.d.ts +0 -2
  75. package/protocol/smart-buffer.js +11 -16
  76. package/types.d.ts +5 -3
  77. package/util/abort-signal.d.ts +34 -0
  78. package/util/abort-signal.js +76 -0
  79. package/util/array-calculatedim.js +3 -1
  80. package/util/connection-config.js +77 -3
  81. package/util/decode-binaryarray.d.ts +1 -1
  82. package/util/decode-binaryarray.js +15 -6
  83. package/util/encode-binaryarray.js +7 -3
  84. package/util/escape-identifier.d.ts +14 -0
  85. package/util/escape-identifier.js +32 -0
  86. package/util/escape-literal.js +3 -0
  87. package/util/fast-parseint.d.ts +1 -0
  88. package/util/fast-parseint.js +12 -17
  89. package/util/format-datetime.d.ts +10 -0
  90. package/util/format-datetime.js +71 -0
  91. package/util/get-parsers.js +31 -10
  92. package/util/parse-array.js +2 -2
  93. package/util/parse-bytea.d.ts +2 -0
  94. package/util/parse-bytea.js +83 -0
  95. package/util/parse-datetime.d.ts +3 -1
  96. package/util/parse-datetime.js +86 -17
  97. package/util/parse-row.d.ts +3 -1
  98. package/util/parse-row.js +60 -5
  99. package/util/parse-time.js +8 -1
  100. package/util/sql-tag.d.ts +60 -0
  101. package/util/sql-tag.js +202 -0
  102. package/util/starts-transaction.d.ts +38 -0
  103. package/util/starts-transaction.js +148 -0
  104. package/util/stringify-arrayliteral.js +13 -1
  105. package/util/wrap-row-description.js +13 -4
  106. package/util/convert-row-to-object.d.ts +0 -2
  107. package/util/convert-row-to-object.js +0 -9
@@ -23,35 +23,70 @@ const ErrorFieldTypes = {
23
23
  R: 'routine',
24
24
  };
25
25
  export class Backend {
26
- _buf;
26
+ _headerBuf = Buffer.allocUnsafe(HEADER_LENGTH);
27
+ _headerFilled = 0;
28
+ _code;
29
+ _len = 0;
30
+ _bodyBuf;
31
+ _bodyFilled = 0;
27
32
  reset() {
28
- this._buf = undefined;
33
+ this._headerFilled = 0;
34
+ this._code = undefined;
35
+ this._bodyBuf = undefined;
36
+ this._bodyFilled = 0;
29
37
  }
30
38
  parse(data, callback) {
31
- if (this._buf) {
32
- data = Buffer.concat([this._buf, data]);
33
- this._buf = undefined;
34
- }
35
- const io = new BufferReader(data);
36
- let offsetBookmark;
37
- while (io.length - io.offset >= HEADER_LENGTH) {
38
- offsetBookmark = io.offset;
39
- const code = io.readUInt8();
40
- const len = io.readUInt32BE();
41
- // Check if frame data not received yet
42
- if (io.length - io.offset < len - 4) {
43
- io.offset = offsetBookmark;
44
- this._buf = io.readBuffer();
45
- return;
39
+ const dataLen = data.length;
40
+ let pos = 0;
41
+ while (pos < dataLen) {
42
+ if (this._headerFilled === 0 &&
43
+ !this._bodyBuf &&
44
+ dataLen - pos >= HEADER_LENGTH) {
45
+ const code = data.readUInt8(pos);
46
+ const len = data.readUInt32BE(pos + 1);
47
+ const bodyStart = pos + HEADER_LENGTH;
48
+ const bodyEnd = bodyStart + (len - 4);
49
+ if (bodyEnd <= dataLen) {
50
+ const io = new BufferReader(data.subarray(bodyStart, bodyEnd));
51
+ const parser = MessageParsers[code];
52
+ const v = parser && parser(io, code, len);
53
+ callback(code, v);
54
+ pos = bodyEnd;
55
+ continue;
56
+ }
57
+ }
58
+ if (!this._bodyBuf) {
59
+ const need = HEADER_LENGTH - this._headerFilled;
60
+ const take = Math.min(need, dataLen - pos);
61
+ data.copy(this._headerBuf, this._headerFilled, pos, pos + take);
62
+ this._headerFilled += take;
63
+ pos += take;
64
+ if (this._headerFilled < HEADER_LENGTH)
65
+ return; // header not complete yet
66
+ this._code = this._headerBuf.readUInt8(0);
67
+ this._len = this._headerBuf.readUInt32BE(1);
68
+ this._bodyBuf = Buffer.allocUnsafe(this._len - 4);
69
+ this._bodyFilled = 0;
70
+ }
71
+ const need = this._bodyBuf.length - this._bodyFilled;
72
+ const take = Math.min(need, dataLen - pos);
73
+ if (take > 0) {
74
+ data.copy(this._bodyBuf, this._bodyFilled, pos, pos + take);
75
+ this._bodyFilled += take;
76
+ pos += take;
46
77
  }
47
- const parser = MessageParsers[code];
48
- const v = parser && parser(io, code, len);
49
- callback(code, v);
50
- // Set offset to next message
51
- io.offset = offsetBookmark + len + 1;
78
+ if (this._bodyFilled < this._bodyBuf.length)
79
+ return; // body not complete yet
80
+ const io = new BufferReader(this._bodyBuf);
81
+ const parser = MessageParsers[this._code];
82
+ const v = parser && parser(io, this._code, this._len);
83
+ callback(this._code, v);
84
+ // Reset reassembly state for the next message.
85
+ this._headerFilled = 0;
86
+ this._code = undefined;
87
+ this._bodyBuf = undefined;
88
+ this._bodyFilled = 0;
52
89
  }
53
- if (io.offset < io.length)
54
- this._buf = io.readBuffer(io.length - io.offset);
55
90
  }
56
91
  }
57
92
  const MessageParsers = {
@@ -158,7 +193,9 @@ function parseCopyResponse(io) {
158
193
  };
159
194
  if (out.columnCount) {
160
195
  out.columnFormats = [];
161
- for (let i = 0; i < out.columnCount; i++) {
196
+ const l = out.columnCount;
197
+ let i;
198
+ for (i = 0; i < l; i++) {
162
199
  out.columnFormats.push(io.readUInt16BE() === 0
163
200
  ? Protocol.DataFormat.text
164
201
  : Protocol.DataFormat.binary);
@@ -166,24 +203,10 @@ function parseCopyResponse(io) {
166
203
  }
167
204
  return out;
168
205
  }
169
- function parseDataRow(io) {
170
- const out = {
171
- columnCount: io.readUInt16BE(),
172
- };
173
- if (out.columnCount) {
174
- out.columns = [];
175
- for (let i = 0; i < out.columnCount; i++) {
176
- // The length of the column value, in bytes (this count does not include itself).
177
- // Can be zero. As a special case, -1 indicates a NULL column value.
178
- // No value bytes follow in the NULL case.
179
- const l = io.readInt32BE();
180
- if (l < 0)
181
- out.columns.push(null);
182
- else
183
- out.columns.push(io.readBuffer(l));
184
- }
185
- }
186
- return out;
206
+ function parseDataRow(io, code, len) {
207
+ const columnCount = io.readUInt16BE();
208
+ const data = io.readBuffer(len - 6);
209
+ return { columnCount, data };
187
210
  }
188
211
  function parseErrorResponse(io) {
189
212
  const out = {};
@@ -217,10 +240,12 @@ function parseNegotiateProtocolVersion(io) {
217
240
  }
218
241
  function parseParameterDescription(io) {
219
242
  const out = {
220
- parameterCount: io.readUInt32BE(),
243
+ parameterCount: io.readUInt16BE(),
221
244
  parameterIds: [],
222
245
  };
223
- for (let i = 0; i < out.parameterCount; i++) {
246
+ const l = out.parameterCount;
247
+ let i;
248
+ for (i = 0; i < l; i++) {
224
249
  out.parameterIds.push(io.readUInt32BE());
225
250
  }
226
251
  return out;
@@ -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
+ }
@@ -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;
@@ -26,6 +26,13 @@ const StaticSyncBuffer = Buffer.from([
26
26
  0x00,
27
27
  0x04,
28
28
  ]);
29
+ const StaticCopyDoneBuffer = Buffer.from([
30
+ Protocol.FrontendMessageCode.CopyDone,
31
+ 0x00,
32
+ 0x00,
33
+ 0x00,
34
+ 0x04,
35
+ ]);
29
36
  export class Frontend {
30
37
  _io;
31
38
  constructor(options) {
@@ -45,7 +52,12 @@ export class Frontend {
45
52
  .writeInt32BE(0) // Preserve length
46
53
  .writeInt16BE(Protocol.VERSION_MAJOR)
47
54
  .writeInt16BE(Protocol.VERSION_MINOR);
48
- for (const [k, v] of Object.entries(args)) {
55
+ const entries = Object.entries(args);
56
+ const l = entries.length;
57
+ let k;
58
+ let v;
59
+ for (let i = 0; i < l; i++) {
60
+ [k, v] = entries[i];
49
61
  if (k !== 'client_encoding')
50
62
  io.writeCString(k, 'utf8').writeCString(v, 'utf8');
51
63
  }
@@ -54,6 +66,22 @@ export class Frontend {
54
66
  .writeUInt8(0);
55
67
  return setLengthAndFlush(io, 0);
56
68
  }
69
+ /**
70
+ * Asks the server to cancel whatever the given session is currently
71
+ * running. Sent on its own fresh connection - a backend busy with a query
72
+ * is not reading its own socket, which is the whole reason this cannot go
73
+ * down the normal one.
74
+ */
75
+ getCancelRequestMessage(processID, secretKey) {
76
+ return this._io
77
+ .start()
78
+ .writeUInt32BE(16) // Length of message contents in bytes, including self.
79
+ .writeUInt16BE(1234)
80
+ .writeUInt16BE(5678)
81
+ .writeUInt32BE(processID)
82
+ .writeUInt32BE(secretKey)
83
+ .flush();
84
+ }
57
85
  getPasswordMessage(password) {
58
86
  const io = this._io
59
87
  .start()
@@ -82,16 +110,19 @@ export class Frontend {
82
110
  getParseMessage(args) {
83
111
  if (args.statement && args.statement.length > 63)
84
112
  throw new Error('Query name length must be lower than 63');
113
+ const { paramTypes } = args;
85
114
  const io = this._io
86
115
  .start()
87
116
  .writeInt8(Protocol.FrontendMessageCode.Parse)
88
117
  .writeInt32BE(0) // Preserve header
89
118
  .writeCString(args.statement || '', 'utf8')
90
119
  .writeCString(args.sql, 'utf8')
91
- .writeUInt16BE(args.paramTypes ? args.paramTypes.length : 0);
92
- if (args.paramTypes) {
93
- for (const t of args.paramTypes) {
94
- io.writeUInt32BE(t || 0);
120
+ .writeUInt16BE(paramTypes?.length || 0);
121
+ if (paramTypes) {
122
+ let i;
123
+ const l = paramTypes.length;
124
+ for (i = 0; i < l; i++) {
125
+ io.writeUInt32BE(paramTypes[i] || 0);
95
126
  }
96
127
  }
97
128
  return setLengthAndFlush(io, 1);
@@ -114,13 +145,16 @@ export class Frontend {
114
145
  if (params && params.length) {
115
146
  io.writeInt16BE(params.length);
116
147
  const formatOffset = io.offset;
117
- for (let i = 0; i < params.length; i++) {
148
+ const l = params.length;
149
+ let i;
150
+ for (i = 0; i < l; i++) {
118
151
  io.writeInt16BE(0); // Preserve
119
152
  }
120
153
  // Write parameter values
121
- io.writeUInt16BE(params.length);
122
- for (let i = 0; i < params?.length; i++) {
123
- let v = params[i];
154
+ io.writeUInt16BE(l);
155
+ let v;
156
+ for (i = 0; i < l; i++) {
157
+ v = params[i];
124
158
  if (v === null || v === undefined) {
125
159
  io.writeInt32BE(-1);
126
160
  continue;
@@ -175,8 +209,10 @@ export class Frontend {
175
209
  io.writeUInt16BE(0);
176
210
  }
177
211
  if (Array.isArray(columnFormat)) {
178
- io.writeUInt16BE(columnFormat.length);
179
- for (let i = 0; i < columnFormat.length; i++) {
212
+ const l = columnFormat.length;
213
+ io.writeUInt16BE(l);
214
+ let i;
215
+ for (i = 0; i < l; i++) {
180
216
  io.writeUInt16BE(columnFormat[i]);
181
217
  }
182
218
  }
@@ -237,6 +273,29 @@ export class Frontend {
237
273
  .writeCString(sql || '', 'utf8');
238
274
  return setLengthAndFlush(io, 1);
239
275
  }
276
+ /**
277
+ * Returns the CopyData header and the caller's payload as two buffers
278
+ * rather than one, so a bulk import never copies the caller's bytes.
279
+ * PgSocket writes an array of buffers as a single corked write anyway,
280
+ * so this costs nothing on the wire.
281
+ */
282
+ getCopyDataMessage(data) {
283
+ const header = Buffer.allocUnsafe(5);
284
+ header[0] = Protocol.FrontendMessageCode.CopyData;
285
+ header.writeUInt32BE(data.length + 4, 1);
286
+ return [header, data];
287
+ }
288
+ getCopyDoneMessage() {
289
+ return StaticCopyDoneBuffer;
290
+ }
291
+ getCopyFailMessage(message) {
292
+ const io = this._io
293
+ .start()
294
+ .writeInt8(Protocol.FrontendMessageCode.CopyFail)
295
+ .writeInt32BE(0) // Preserve header
296
+ .writeCString(message || '', 'utf8');
297
+ return setLengthAndFlush(io, 1);
298
+ }
240
299
  getFlushMessage() {
241
300
  return StaticFlushBuffer;
242
301
  }