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
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { formatTimestamp } from '../util/format-datetime.js';
2
3
  import { parseDateTime } from '../util/parse-datetime.js';
3
4
  const timeShift = 946684800000;
4
5
  const timeMul = 4294967296;
@@ -6,25 +7,13 @@ export const TimestampType = {
6
7
  name: 'timestamp',
7
8
  oid: DataTypeOIDs.timestamp,
8
9
  jsType: 'Date',
9
- parseBinary(v, options) {
10
- const fetchAsString = options.fetchAsString &&
11
- options.fetchAsString.includes(DataTypeOIDs.timestamp);
12
- const hi = v.readInt32BE();
13
- const lo = v.readUInt32BE(4);
14
- if (lo === 0xffffffff && hi === 0x7fffffff)
15
- return fetchAsString ? 'infinity' : Infinity;
16
- if (lo === 0x00000000 && hi === -0x80000000)
17
- return fetchAsString ? '-infinity' : -Infinity;
18
- // Shift from 2000 to 1970
19
- let d = new Date((lo + hi * timeMul) / 1000 + timeShift);
20
- if (fetchAsString || !options.utcDates) {
21
- d = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());
22
- }
23
- return fetchAsString ? dateToTimestampString(d) : d;
10
+ fixedBinarySize: 8,
11
+ encodeText(v, options) {
12
+ return formatTimestamp(v, options);
24
13
  },
25
14
  encodeBinary(buf, v, options) {
26
15
  if (typeof v === 'string')
27
- v = parseDateTime(v, true, false, options.utcDates);
16
+ v = parseDateTime(v, options.utcDates);
28
17
  if (v === Infinity) {
29
18
  buf.writeInt32BE(0x7fffffff); // hi
30
19
  buf.writeUInt32BE(0xffffffff); // lo
@@ -47,19 +36,37 @@ export const TimestampType = {
47
36
  buf.writeInt32BE(hi);
48
37
  buf.writeUInt32BE(lo);
49
38
  },
50
- parseText(v, options) {
51
- if (options.fetchAsString &&
52
- options.fetchAsString.includes(DataTypeOIDs.timestamp))
39
+ decodeBinary(v, offset = 0, options) {
40
+ const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.timestamp);
41
+ const hi = v.readInt32BE(offset);
42
+ const lo = v.readUInt32BE(offset + 4);
43
+ if (lo === 0xffffffff && hi === 0x7fffffff)
44
+ return fetchAsString ? 'infinity' : Infinity;
45
+ if (lo === 0x00000000 && hi === -0x80000000)
46
+ return fetchAsString ? '-infinity' : -Infinity;
47
+ // Shift from 2000 to 1970
48
+ let d = new Date((lo + hi * timeMul) / 1000 + timeShift);
49
+ if (fetchAsString || !options.utcDates) {
50
+ d = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());
51
+ }
52
+ return fetchAsString ? dateToTimestampString(d) : d;
53
+ },
54
+ decodeText(v, options) {
55
+ if (options.fetchAsString?.includes(DataTypeOIDs.timestamp))
53
56
  return v;
54
- return parseDateTime(v, true, false, options.utcDates);
57
+ return parseDateTime(v, options.utcDates);
58
+ },
59
+ // See date-type.ts's decodeTextBuffer comment - same rationale.
60
+ decodeTextBuffer(buf, offset, len, options) {
61
+ return TimestampType.decodeText(buf.toString('latin1', offset, offset + len), options);
55
62
  },
56
63
  isType(v) {
64
+ // A midnight-exact Date is ambiguous between "date-only" and "a real
65
+ // timestamp that happens to fall on midnight" - default to timestamp,
66
+ // since a timestamp/timestamptz column represents midnight just fine,
67
+ // whereas a date column can never represent a non-midnight time.
57
68
  return (v instanceof Date &&
58
- !(v.getFullYear() === 1970 && v.getMonth() === 0 && v.getDate() === 1) &&
59
- !(v.getHours() === 0 &&
60
- v.getMinutes() === 0 &&
61
- v.getSeconds() === 0 &&
62
- v.getMilliseconds() === 0));
69
+ !(v.getFullYear() === 1970 && v.getMonth() === 0 && v.getDate() === 1));
63
70
  },
64
71
  };
65
72
  function padZero(v) {
@@ -1,31 +1,20 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
2
  import { SmartBuffer } from '../protocol/smart-buffer.js';
3
- import { parseDateTime } from '../util/parse-datetime.js';
3
+ import { formatTimestamptz } from '../util/format-datetime.js';
4
+ import { parseDateTimeTz } from '../util/parse-datetime.js';
4
5
  const timeShift = 946684800000;
5
6
  const timeMul = 4294967296;
6
7
  export const TimestamptzType = {
7
8
  name: 'timestamptz',
8
9
  oid: DataTypeOIDs.timestamptz,
9
10
  jsType: 'Date',
10
- parseBinary(v, options) {
11
- const fetchAsString = options.fetchAsString &&
12
- options.fetchAsString.includes(DataTypeOIDs.timestamptz);
13
- const hi = v.readInt32BE();
14
- const lo = v.readUInt32BE(4);
15
- if (lo === 0xffffffff && hi === 0x7fffffff)
16
- return fetchAsString ? 'infinity' : Infinity;
17
- if (lo === 0x00000000 && hi === -0x80000000)
18
- return fetchAsString ? '-infinity' : -Infinity;
19
- // Shift from 2000 to 1970
20
- let d = new Date((lo + hi * timeMul) / 1000 + timeShift);
21
- if (fetchAsString || !options.utcDates) {
22
- d = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
23
- }
24
- return fetchAsString ? dateToTimestamptzString(d) : d;
11
+ fixedBinarySize: 8,
12
+ encodeText(v) {
13
+ return formatTimestamptz(v);
25
14
  },
26
15
  encodeBinary(buf, v, options) {
27
16
  if (typeof v === 'string')
28
- v = parseDateTime(v, true, true, options.utcDates);
17
+ v = parseDateTimeTz(v, options.utcDates);
29
18
  if (v === Infinity) {
30
19
  buf.writeInt32BE(0x7fffffff); // hi
31
20
  buf.writeUInt32BE(0xffffffff); // lo
@@ -45,20 +34,43 @@ export const TimestamptzType = {
45
34
  buf.writeInt32BE(hi);
46
35
  buf.writeUInt32BE(lo);
47
36
  },
48
- parseText(v, options) {
49
- const d = parseDateTime(v, true, true, options.utcDates);
50
- if (options.fetchAsString &&
51
- options.fetchAsString.includes(DataTypeOIDs.timestamptz)) {
37
+ decodeBinary(v, offset = 0, options) {
38
+ const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.timestamptz);
39
+ const hi = v.readInt32BE(offset);
40
+ const lo = v.readUInt32BE(offset + 4);
41
+ if (lo === 0xffffffff && hi === 0x7fffffff)
42
+ return fetchAsString ? 'infinity' : Infinity;
43
+ if (lo === 0x00000000 && hi === -0x80000000)
44
+ return fetchAsString ? '-infinity' : -Infinity;
45
+ // Shift from 2000 to 1970
46
+ let d = new Date((lo + hi * timeMul) / 1000 + timeShift);
47
+ if (fetchAsString || !options.utcDates) {
48
+ d = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
49
+ }
50
+ return fetchAsString ? dateToTimestamptzString(d) : d;
51
+ },
52
+ decodeText(v, options) {
53
+ const d = parseDateTimeTz(v, options.utcDates);
54
+ if (options.fetchAsString?.includes(DataTypeOIDs.timestamptz)) {
52
55
  if (d instanceof Date)
53
56
  return dateToTimestamptzString(d);
54
57
  if (d === Infinity)
55
58
  return 'infinity';
56
59
  if (d === -Infinity)
57
60
  return '-infinity';
61
+ // parseDateTimeTz() only ever returns a Date, Infinity or -Infinity,
62
+ // never anything else, so this is unreachable.
58
63
  return '';
59
64
  }
60
65
  return d;
61
66
  },
67
+ // See date-type.ts's decodeTextBuffer comment - same rationale. Note this
68
+ // type's decodeText always parses first and only branches on
69
+ // fetchAsString afterward (unlike date/time/timestamp, which early-return
70
+ // the raw string) - delegating by reference preserves that as-is.
71
+ decodeTextBuffer(buf, offset, len, options) {
72
+ return TimestamptzType.decodeText(buf.toString('latin1', offset, offset + len), options);
73
+ },
62
74
  isType(v) {
63
75
  return v instanceof Date;
64
76
  },
@@ -4,16 +4,9 @@ export const UuidType = {
4
4
  name: 'uuid',
5
5
  oid: DataTypeOIDs.uuid,
6
6
  jsType: 'String',
7
- parseBinary(v) {
8
- return (v.toString('hex', 0, 4) +
9
- '-' +
10
- v.toString('hex', 4, 6) +
11
- '-' +
12
- v.toString('hex', 6, 8) +
13
- '-' +
14
- v.toString('hex', 8, 10) +
15
- '-' +
16
- v.toString('hex', 10, 16));
7
+ fixedBinarySize: 16,
8
+ encodeText(v) {
9
+ return '' + v;
17
10
  },
18
11
  encodeBinary(buf, v) {
19
12
  if (!GUID_PATTERN.test(v))
@@ -21,9 +14,26 @@ export const UuidType = {
21
14
  const b = Buffer.from(v.replace(/-/g, ''), 'hex');
22
15
  buf.writeBuffer(b);
23
16
  },
24
- parseText(v) {
17
+ decodeBinary(v, offset = 0) {
18
+ return (v.toString('hex', offset, offset + 4) +
19
+ '-' +
20
+ v.toString('hex', offset + 4, offset + 6) +
21
+ '-' +
22
+ v.toString('hex', offset + 6, offset + 8) +
23
+ '-' +
24
+ v.toString('hex', offset + 8, offset + 10) +
25
+ '-' +
26
+ v.toString('hex', offset + 10, offset + 16));
27
+ },
28
+ decodeText(v) {
25
29
  return v;
26
30
  },
31
+ // Canonical UUID text is fixed-format hex+dashes, pure ASCII - 'latin1'
32
+ // decodes identically to 'utf8' here but skips V8's multi-byte-sequence
33
+ // detection.
34
+ decodeTextBuffer(buf, offset, len) {
35
+ return buf.toString('latin1', offset, offset + len);
36
+ },
27
37
  isType(v) {
28
38
  return typeof v === 'string' && GUID_PATTERN.test(v);
29
39
  },
@@ -3,14 +3,26 @@ export const VarcharType = {
3
3
  name: 'varchar',
4
4
  oid: DataTypeOIDs.varchar,
5
5
  jsType: 'string',
6
- parseBinary(v) {
7
- return v.toString('utf8');
6
+ encodeText(v) {
7
+ return '' + v;
8
8
  },
9
9
  encodeBinary(buf, v) {
10
10
  buf.writeString('' + v, 'utf8');
11
11
  },
12
- parseText(v) {
13
- return '' + v;
12
+ decodeBinary(v, offset = 0) {
13
+ return v.toString('utf8', offset);
14
+ },
15
+ decodeText(v) {
16
+ // The wire decoder always hands this a string (see intl-connection.ts's
17
+ // DataRow handling) - no coercion needed.
18
+ return v;
19
+ },
20
+ // varchar can hold arbitrary Unicode text, so this must stay 'utf8' -
21
+ // same allocation as the default (v.toString('utf8')) path, this only
22
+ // skips the extra decodeText wrapper-closure call get-parsers.ts would
23
+ // otherwise add.
24
+ decodeTextBuffer(buf, offset, len) {
25
+ return buf.toString('utf8', offset, offset + len);
14
26
  },
15
27
  isType(v) {
16
28
  return typeof v === 'string';
package/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from './connection/bind-param.js';
2
2
  export * from './connection/connection.js';
3
+ export * from './connection/copy-stream.js';
3
4
  export * from './connection/cursor.js';
5
+ export * from './connection/large-object.js';
6
+ export * from './connection/logical-replication.js';
4
7
  export * from './connection/pool.js';
5
8
  export * from './connection/prepared-statement.js';
6
9
  export * from './constants.js';
@@ -19,7 +22,9 @@ export * from './protocol/database-error.js';
19
22
  export * from './protocol/smart-buffer.js';
20
23
  export * from './types.js';
21
24
  export * from './util/connection-config.js';
25
+ export * from './util/escape-identifier.js';
22
26
  export * from './util/escape-literal.js';
23
27
  export * from './util/parse-datetime.js';
28
+ export * from './util/sql-tag.js';
24
29
  export * from './util/stringify-arrayliteral.js';
25
30
  export * from './util/stringify-for-sql.js';
package/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from './connection/bind-param.js';
2
2
  export * from './connection/connection.js';
3
+ export * from './connection/copy-stream.js';
3
4
  export * from './connection/cursor.js';
5
+ export * from './connection/large-object.js';
6
+ export * from './connection/logical-replication.js';
4
7
  export * from './connection/pool.js';
5
8
  export * from './connection/prepared-statement.js';
6
9
  export * from './constants.js';
@@ -19,7 +22,9 @@ export * from './protocol/database-error.js';
19
22
  export * from './protocol/smart-buffer.js';
20
23
  export * from './types.js';
21
24
  export * from './util/connection-config.js';
25
+ export * from './util/escape-identifier.js';
22
26
  export * from './util/escape-literal.js';
23
27
  export * from './util/parse-datetime.js';
28
+ export * from './util/sql-tag.js';
24
29
  export * from './util/stringify-arrayliteral.js';
25
30
  export * from './util/stringify-for-sql.js';
@@ -1,4 +1,4 @@
1
- import type { DecodeBinaryFunction, EncodeAsNullFunction, EncodeBinaryFunction, EncodeCalculateDimFunction, EncodeTextFunction, OID, ParseTextFunction } from '../types.js';
1
+ import type { DecodeBinaryFunction, DecodeTextBufferFunction, DecodeTextFunction, EncodeAsNullFunction, EncodeBinaryFunction, EncodeCalculateDimFunction, EncodeTextFunction, OID } from '../types.js';
2
2
  export interface DataType {
3
3
  oid: OID;
4
4
  name: string;
@@ -7,8 +7,10 @@ export interface DataType {
7
7
  jsType: string;
8
8
  arraySeparator?: string;
9
9
  isType: (v: any) => boolean;
10
- parseBinary: DecodeBinaryFunction;
11
- parseText: ParseTextFunction;
10
+ decodeBinary: DecodeBinaryFunction;
11
+ decodeText: DecodeTextFunction;
12
+ decodeTextBuffer?: DecodeTextBufferFunction;
13
+ fixedBinarySize?: number;
12
14
  encodeAsNull?: EncodeAsNullFunction;
13
15
  encodeBinary?: EncodeBinaryFunction;
14
16
  encodeText?: EncodeTextFunction;
@@ -9,6 +9,58 @@ export interface DatabaseConnectionParams {
9
9
  password?: string | (() => string | Promise<string>);
10
10
  database?: string;
11
11
  applicationName?: string;
12
+ /**
13
+ * Opens the connection in replication mode, which is what lets it run
14
+ * START_REPLICATION. Set by LogicalReplication; a connection in this mode
15
+ * cannot serve ordinary queries once streaming has begun.
16
+ */
17
+ replication?: 'database' | 'true';
18
+ /**
19
+ * Additional servers to try, in order, when the first one cannot be used.
20
+ *
21
+ * Also accepted as a comma-separated `host`, or in a connection string:
22
+ * `postgres://a:5432,b:5433/db`. Each entry falls back to the top-level
23
+ * `port` when it does not carry one of its own.
24
+ *
25
+ * Selection happens when a connection is opened, so a cluster that has
26
+ * failed over to another node is found on the next connect - a query
27
+ * already in flight when a server goes down still fails.
28
+ */
29
+ hosts?: {
30
+ host: string;
31
+ port?: number;
32
+ }[];
33
+ /**
34
+ * Which server in `hosts` is acceptable, mirroring libpq's option of the
35
+ * same name: `read-write`, `read-only`, `primary`, `standby` or
36
+ * `prefer-standby`. A server that does not match is dropped and the next
37
+ * one tried, which is how `read-write` finds the current primary.
38
+ */
39
+ targetSessionAttrs?: TargetSessionAttrs;
40
+ /**
41
+ * How TLS is started: `postgres` (the default) asks first with an
42
+ * SSLRequest and waits for the server's yes or no; `direct` begins the
43
+ * TLS handshake straight away, announcing the `postgresql` protocol over
44
+ * ALPN so the server can tell what it is talking to.
45
+ *
46
+ * Direct saves a round trip and leaves no plaintext preamble for a
47
+ * middlebox to read or strip, but it needs PostgreSQL 17 or later and
48
+ * `ssl` set - an older server just closes the connection.
49
+ */
50
+ sslNegotiation?: 'postgres' | 'direct';
51
+ /**
52
+ * Whether SCRAM authentication binds itself to the TLS channel, mirroring
53
+ * libpq's option of the same name: `prefer` (the default there and here)
54
+ * uses it when the server offers it, `require` refuses to connect
55
+ * otherwise, `disable` never asks for it.
56
+ *
57
+ * Binding mixes a hash of the server's certificate into the SCRAM proof,
58
+ * so an attacker who terminates TLS in the middle - necessarily with a
59
+ * different certificate - cannot relay the exchange. It is what protects
60
+ * the login when the certificate itself is not verified, which is the
61
+ * usual case with `sslmode=require` or a self-signed certificate.
62
+ */
63
+ channelBinding?: 'prefer' | 'require' | 'disable';
12
64
  requireSSL?: boolean;
13
65
  ssl?: TlsConnectionOptions;
14
66
  timezone?: string;
@@ -26,8 +78,22 @@ export interface DatabaseConnectionParams {
26
78
  * @default true
27
79
  */
28
80
  rollbackOnError?: boolean;
81
+ /**
82
+ * Whether a thrown error's stack trace points at the application code
83
+ * that called query()/execute() (etc.) across the `await`, instead of an
84
+ * internal async frame inside this library.
85
+ *
86
+ * Getting this right costs a real, measurable amount of CPU when many
87
+ * calls are in flight at once (e.g. a burst of pipelined queries on one
88
+ * connection) - set to `false` to skip it, which is also what makes an
89
+ * apples-to-apples benchmark against a client that does not offer this
90
+ * fair.
91
+ * @default true
92
+ */
93
+ asyncErrorHandling?: boolean;
29
94
  debugLogger?: DebugLogger;
30
95
  }
96
+ export type TargetSessionAttrs = 'read-write' | 'read-only' | 'primary' | 'standby' | 'prefer-standby';
31
97
  export interface SocketOptions {
32
98
  keepAlive?: boolean;
33
99
  }
@@ -35,4 +101,33 @@ export interface ConnectionConfiguration extends DatabaseConnectionParams, Socke
35
101
  buffer?: SmartBufferConfig;
36
102
  }
37
103
  export interface PoolConfiguration extends ConnectionConfiguration, LPoolConfiguration {
104
+ /**
105
+ * How many of Pool.query()'s one-shot queries may share a single pooled
106
+ * connection at the same time (default 100).
107
+ *
108
+ * PostgreSQL correlates responses to requests by order, so several
109
+ * queries can be in flight on one connection at once, each with its own
110
+ * Sync and therefore its own error boundary. Dispatching that way means
111
+ * `max` stops being a ceiling on concurrent queries and becomes only a
112
+ * ceiling on connections - with the default pool of 10, a burst of 1000
113
+ * queries no longer has to run as 100 sequential rounds of 10.
114
+ *
115
+ * Set to 1 for the older behaviour, where Pool.query() holds a
116
+ * connection exclusively for the duration of each query. Connections
117
+ * handed out by acquire() are never shared, whatever this is set to, so
118
+ * transactions, cursors and prepared statements are unaffected.
119
+ */
120
+ pipelineMaxQueries?: number;
121
+ /**
122
+ * How many pooled connections the pipelined path may borrow at once
123
+ * (default: the pool's own `max`).
124
+ *
125
+ * The server runs one connection's statements serially, so a burst
126
+ * spread over ten connections finishes in a fraction of the time it
127
+ * takes on one. Borrowed connections are handed straight back the
128
+ * moment they go idle, so this is a ceiling on how wide a single burst
129
+ * may fan out, not a reservation - lower it to keep connections free
130
+ * for acquire() while a burst is running.
131
+ */
132
+ pipelineMaxConnections?: number;
38
133
  }
@@ -43,4 +43,21 @@ export interface QueryOptions extends DataMappingOptions {
43
43
  * @default true
44
44
  */
45
45
  rollbackOnError?: boolean;
46
+ /**
47
+ * Aborts the call when the signal fires: the running statement is
48
+ * cancelled on the server and the promise rejects with the signal's own
49
+ * reason, with the database error attached as `cause`.
50
+ *
51
+ * Cancelling is a request rather than a guarantee - it travels on its own
52
+ * connection and the statement may finish first - so the call settles when
53
+ * the server has actually finished with it, not the moment abort() is
54
+ * called. `AbortSignal.timeout(ms)` gives a per-call timeout.
55
+ */
56
+ signal?: AbortSignal;
57
+ /**
58
+ * Overrides the connection's own `asyncErrorHandling` for this call only -
59
+ * see `DatabaseConnectionParams.asyncErrorHandling`.
60
+ * @default the connection's own setting
61
+ */
62
+ asyncErrorHandling?: boolean;
46
63
  }
@@ -23,4 +23,21 @@ export interface ScriptExecuteOptions extends DataMappingOptions {
23
23
  * @default true
24
24
  */
25
25
  rollbackOnError?: boolean;
26
+ /**
27
+ * Aborts the call when the signal fires: the running statement is
28
+ * cancelled on the server and the promise rejects with the signal's own
29
+ * reason, with the database error attached as `cause`.
30
+ *
31
+ * Cancelling is a request rather than a guarantee - it travels on its own
32
+ * connection and the statement may finish first - so the call settles when
33
+ * the server has actually finished with it, not the moment abort() is
34
+ * called. `AbortSignal.timeout(ms)` gives a per-call timeout.
35
+ */
36
+ signal?: AbortSignal;
37
+ /**
38
+ * Overrides the connection's own `asyncErrorHandling` for this call only -
39
+ * see `DatabaseConnectionParams.asyncErrorHandling`.
40
+ * @default the connection's own setting
41
+ */
42
+ asyncErrorHandling?: boolean;
26
43
  }
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "postgrejs",
3
3
  "description": "Professional PostgreSQL client NodeJS",
4
- "version": "2.23.1",
4
+ "version": "3.0.1",
5
5
  "author": "Panates",
6
- "license": "MIT",
6
+ "license": "BSD-3-Clause",
7
7
  "dependencies": {
8
8
  "@jsopen/objects": "^2.2.3",
9
9
  "doublylinked": "^2.5.6",
10
10
  "lightning-pool": "^4.12.0",
11
- "postgres-bytea": "^3.0.0",
12
11
  "power-tasks": "^1.14.2",
13
12
  "putil-promisify": "^1.10.1",
14
13
  "putil-varhelpers": "^1.7.0",
@@ -1,7 +1,12 @@
1
1
  import { Protocol } from './protocol.js';
2
2
  declare type ParseCallback = (code: Protocol.BackendMessageCode, data?: any) => void;
3
3
  export declare class Backend {
4
- private _buf?;
4
+ private readonly _headerBuf;
5
+ private _headerFilled;
6
+ private _code?;
7
+ private _len;
8
+ private _bodyBuf?;
9
+ private _bodyFilled;
5
10
  reset(): void;
6
11
  parse(data: Buffer, callback: ParseCallback): void;
7
12
  }
@@ -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
  }