postgrejs 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +50 -0
  3. package/connection/connection.d.ts +14 -2
  4. package/connection/connection.js +70 -10
  5. package/connection/intl-connection.d.ts +146 -5
  6. package/connection/intl-connection.js +485 -35
  7. package/connection/pool.d.ts +30 -6
  8. package/connection/pool.js +28 -3
  9. package/connection/prepared-statement.d.ts +11 -0
  10. package/connection/prepared-statement.js +39 -5
  11. package/data-type-map.d.ts +16 -0
  12. package/data-type-map.js +56 -12
  13. package/data-types/classes/numeric.d.ts +8 -4
  14. package/data-types/classes/numeric.js +8 -4
  15. package/data-types/date-type.js +2 -2
  16. package/data-types/int2-vector-type.js +4 -1
  17. package/data-types/money-type.d.ts +38 -0
  18. package/data-types/money-type.js +203 -0
  19. package/data-types/numeric-type.d.ts +2 -0
  20. package/data-types/numeric-type.js +1 -1
  21. package/data-types/oid-vector-type.js +4 -1
  22. package/data-types/timestamp-type.js +2 -2
  23. package/data-types/timestamptz-type.js +11 -2
  24. package/index.d.ts +1 -0
  25. package/index.js +1 -0
  26. package/interfaces/data-mapping-options.d.ts +27 -0
  27. package/interfaces/database-connection-params.d.ts +32 -5
  28. package/interfaces/query-options.d.ts +32 -0
  29. package/interfaces/script-execute-options.d.ts +32 -0
  30. package/interfaces/transaction-options.d.ts +27 -0
  31. package/interfaces/transaction-options.js +1 -0
  32. package/package.json +1 -1
  33. package/protocol/database-error.d.ts +15 -0
  34. package/protocol/database-error.js +19 -0
  35. package/protocol/frontend.js +17 -0
  36. package/protocol/pg-socket.js +7 -10
  37. package/util/array-leaf.d.ts +10 -0
  38. package/util/array-leaf.js +22 -0
  39. package/util/date-style.d.ts +47 -0
  40. package/util/date-style.js +123 -0
  41. package/util/decode-binaryarray.js +8 -1
  42. package/util/encode-binaryarray.d.ts +12 -1
  43. package/util/encode-binaryarray.js +13 -2
  44. package/util/format-datetime.d.ts +18 -0
  45. package/util/format-datetime.js +33 -0
  46. package/util/parse-datetime.d.ts +4 -3
  47. package/util/parse-datetime.js +34 -9
  48. package/util/resolve-column-formats.js +4 -0
  49. package/util/row-decoder.d.ts +24 -4
  50. package/util/row-decoder.js +25 -10
  51. package/util/sql-tag.js +16 -0
  52. package/util/stringify-for-sql.js +7 -0
  53. package/util/transaction-command.d.ts +7 -0
  54. package/util/transaction-command.js +32 -0
  55. package/util/unspecified-param.d.ts +27 -0
  56. package/util/unspecified-param.js +31 -0
@@ -0,0 +1,203 @@
1
+ import { DataTypeOIDs } from '../constants.js';
2
+ import { readBigInt64BE } from '../util/bigint-methods.js';
3
+ import { Numeric } from './classes/numeric.js';
4
+ import { trimTrailingZeros } from './numeric-type.js';
5
+ /**
6
+ * What the server's own `lc_monetary` does to a money value, when
7
+ * nothing has asked it. Two fraction digits and a dot is what the great
8
+ * majority of locales use, and it is only ever reached when the probe
9
+ * itself failed - see IntlConnection.ensureMoneyFormat().
10
+ */
11
+ const DEFAULT_SCALE = 2;
12
+ const DEFAULT_SEPARATOR = '.';
13
+ /**
14
+ * Reads the server's own rendering of `1::money` - see
15
+ * `IntlConnection.ensureMoneyFormat()` for why it is that value: with no
16
+ * thousands separator in it, any separator present can only be the
17
+ * decimal one, and the digits after it can only be the fraction.
18
+ */
19
+ export function parseMoneyFormat(text) {
20
+ let lastSep = -1;
21
+ let i;
22
+ const l = text.length;
23
+ let c;
24
+ for (i = 0; i < l; i++) {
25
+ c = text.charCodeAt(i);
26
+ if (c >= 48 && c <= 57)
27
+ continue;
28
+ if (c === 46 /* . */ || c === 44 /* , */)
29
+ lastSep = i;
30
+ }
31
+ if (lastSep < 0)
32
+ return { scale: 0, decimalSeparator: DEFAULT_SEPARATOR };
33
+ let scale = 0;
34
+ for (i = lastSep + 1; i < l; i++) {
35
+ c = text.charCodeAt(i);
36
+ if (c >= 48 && c <= 57)
37
+ scale++;
38
+ }
39
+ return { scale, decimalSeparator: text[lastSep] };
40
+ }
41
+ const FALLBACK_FORMAT = {
42
+ scale: DEFAULT_SCALE,
43
+ decimalSeparator: DEFAULT_SEPARATOR,
44
+ };
45
+ function formatOf(options) {
46
+ return options?.moneyFormat || FALLBACK_FORMAT;
47
+ }
48
+ /** The exact decimal `minorUnits` stands for at `scale` fraction digits. */
49
+ export function moneyToString(minorUnits, scale) {
50
+ if (!scale)
51
+ return minorUnits.toString();
52
+ const neg = minorUnits < 0n;
53
+ let digits = (neg ? -minorUnits : minorUnits).toString();
54
+ if (digits.length <= scale)
55
+ digits = digits.padStart(scale + 1, '0');
56
+ return ((neg ? '-' : '') +
57
+ digits.slice(0, digits.length - scale) +
58
+ '.' +
59
+ digits.slice(digits.length - scale));
60
+ }
61
+ /**
62
+ * A number while a double carries the value exactly, a Numeric after
63
+ * that - the same rule `numeric` decodes by, and for the same reason.
64
+ * money is an int64 of minor units, so it reaches past 2^53 long before
65
+ * it runs out of range.
66
+ */
67
+ function toNumberOrNumeric(s) {
68
+ const n = parseFloat(s);
69
+ // The fraction is always written out to the full scale - `1` at scale
70
+ // 2 is `1.00` - and those zeroes are padding, not a difference.
71
+ return String(n) === trimTrailingZeros(s) ? n : new Numeric(s);
72
+ }
73
+ /**
74
+ * Every digit in `s`, read as minor units at `scale` - which is how a
75
+ * rendered money value is read back without knowing the locale that
76
+ * rendered it. `-$1,234.50` is `-123450` at scale 2, whatever the symbol
77
+ * and the separators were.
78
+ */
79
+ function parseMoneyText(s) {
80
+ let digits = '';
81
+ let neg = false;
82
+ let i;
83
+ const l = s.length;
84
+ let c;
85
+ for (i = 0; i < l; i++) {
86
+ c = s.charCodeAt(i);
87
+ if (c >= 48 && c <= 57)
88
+ digits += s[i];
89
+ else if (c === 45 /* - */ || c === 40 /* ( */)
90
+ neg = true;
91
+ }
92
+ if (!digits)
93
+ digits = '0';
94
+ const v = BigInt(digits);
95
+ return neg ? -v : v;
96
+ }
97
+ /**
98
+ * PostgreSQL's fixed-point currency type.
99
+ *
100
+ * On the wire it is an int64 of the smallest currency unit - `$12.34`
101
+ * arrives as `1234` - and how many of those make a unit is `lc_monetary`,
102
+ * which the server does not report in its startup parameters. So the
103
+ * connection asks it once, on its way up, and every money value is read
104
+ * against that answer. See `IntlConnection.ensureMoneyFormat()`.
105
+ *
106
+ * The value decodes to a plain number, or a `Numeric` when a double
107
+ * cannot carry it. The currency symbol is *not* part of it: it is the
108
+ * server's rendering of the value, not the value, and
109
+ * `fetchAsString: [DataTypeOIDs.money]` asks for that rendering when it
110
+ * is what the caller wants - which is also what `pg` always returns.
111
+ *
112
+ * The question is asked the first time a money column is on its way
113
+ * back, while the rows are still raw bytes - so no value is ever read
114
+ * against a scale nobody confirmed, and a caller who never touches
115
+ * money never asks. `execute()` and `pipeline()` decode inside the
116
+ * message loop, where there is nowhere left to ask from, so those two
117
+ * set the rows aside and read them once the loop is over.
118
+ *
119
+ * `moneyFormat` in the options answers it in advance, for a caller who
120
+ * knows the server or is reading a value some other server rendered.
121
+ */
122
+ export const MoneyType = {
123
+ name: 'money',
124
+ oid: DataTypeOIDs.money,
125
+ jsType: 'number',
126
+ /**
127
+ * Written in the server's own decimal separator, so that the value the
128
+ * server parses is the value that was meant - money input is read in
129
+ * `lc_monetary` too, and `12.34` is not `12,34` to a locale that
130
+ * separates with a comma.
131
+ */
132
+ encodeText(v, options) {
133
+ const { scale, decimalSeparator } = formatOf(options);
134
+ const s = moneyToString(toExactMinor(v, scale), scale);
135
+ return decimalSeparator === '.' ? s : s.replace('.', decimalSeparator);
136
+ },
137
+ encodeBinary(buf, v, options) {
138
+ const { scale } = formatOf(options);
139
+ buf.writeBigInt64BE(toExactMinor(v, scale));
140
+ },
141
+ decodeBinary(buf, offset = 0, _len, options) {
142
+ const v = typeof buf.readBigInt64BE === 'function'
143
+ ? buf.readBigInt64BE(offset)
144
+ : readBigInt64BE(buf, offset);
145
+ return toNumberOrNumeric(moneyToString(v, formatOf(options).scale));
146
+ },
147
+ decodeText(s, options) {
148
+ const { scale } = formatOf(options);
149
+ return toNumberOrNumeric(moneyToString(parseMoneyText(s), scale));
150
+ },
151
+ /**
152
+ * Never inferred: a JavaScript number is `int4`, `float8` or `numeric`
153
+ * long before anyone means money by it, and nothing about the value
154
+ * says which. `new BindParam(DataTypeOIDs.money, 12.34)` asks for it.
155
+ */
156
+ inferrable: false,
157
+ isType(v) {
158
+ return typeof v === 'number' || v instanceof Numeric;
159
+ },
160
+ };
161
+ /** The int64 of minor units `v` stands for, without going through a double. */
162
+ function toExactMinor(v, scale) {
163
+ if (typeof v === 'bigint')
164
+ return v;
165
+ const s = v instanceof Numeric ? v.value : String(v ?? 0);
166
+ const neg = s.trimStart().startsWith('-');
167
+ let digits = '';
168
+ let frac = -1;
169
+ let i;
170
+ const l = s.length;
171
+ let c;
172
+ for (i = 0; i < l; i++) {
173
+ c = s.charCodeAt(i);
174
+ if (c >= 48 && c <= 57) {
175
+ digits += s[i];
176
+ if (frac >= 0)
177
+ frac++;
178
+ }
179
+ else if (c === 46 /* . */ || c === 44 /* , */)
180
+ frac = 0;
181
+ }
182
+ if (!digits)
183
+ return 0n;
184
+ if (frac < 0)
185
+ frac = 0;
186
+ // Pad or round to exactly `scale` fraction digits.
187
+ if (frac < scale)
188
+ digits = digits.padEnd(digits.length + scale - frac, '0');
189
+ else if (frac > scale) {
190
+ const cut = frac - scale;
191
+ const keep = digits.slice(0, digits.length - cut);
192
+ const next = digits.charCodeAt(digits.length - cut) - 48;
193
+ digits = (BigInt(keep || '0') + (next >= 5 ? 1n : 0n)).toString();
194
+ }
195
+ const out = BigInt(digits || '0');
196
+ return neg ? -out : out;
197
+ }
198
+ export const ArrayMoneyType = {
199
+ ...MoneyType,
200
+ name: '_money',
201
+ oid: DataTypeOIDs._money,
202
+ elementsOID: DataTypeOIDs.money,
203
+ };
@@ -1,4 +1,6 @@
1
1
  import type { DataType } from '../interfaces/data-type.js';
2
+ /** The decimal without the zeroes a declared scale pads it out to. */
3
+ export declare function trimTrailingZeros(s: string): string;
2
4
  export declare const NumericType: DataType;
3
5
  export declare const ArrayNumericType: DataType;
4
6
  /**
@@ -16,7 +16,7 @@ const PAIRS = new Array(100);
16
16
  for (let i = 0; i < 100; i++)
17
17
  PAIRS[i] = i < 10 ? '0' + i : '' + i;
18
18
  /** The decimal without the zeroes a declared scale pads it out to. */
19
- function trimTrailingZeros(s) {
19
+ export function trimTrailingZeros(s) {
20
20
  const dot = s.indexOf('.');
21
21
  if (dot < 0)
22
22
  return s;
@@ -16,7 +16,10 @@ export const OidVectorType = {
16
16
  encodeBinary(buf, v) {
17
17
  encodeBinaryArray(buf, v, DataTypeOIDs.oid, {}, (io, x) => {
18
18
  io.writeUInt32BE(x);
19
- });
19
+ },
20
+ // A vector is genuinely 0-based, unlike every ordinary array -
21
+ // `array_lower(pg_index.indkey, 1)` is 0 on the server.
22
+ undefined, 0);
20
23
  },
21
24
  encodeCalculateDim(v) {
22
25
  return [v.length];
@@ -13,7 +13,7 @@ export const TimestampType = {
13
13
  },
14
14
  encodeBinary(buf, v, options) {
15
15
  if (typeof v === 'string')
16
- v = parseDateTime(v, options.utcDates);
16
+ v = parseDateTime(v, options.utcDates, options.dateStyle);
17
17
  if (v === Infinity) {
18
18
  buf.writeInt32BE(0x7fffffff); // hi
19
19
  buf.writeUInt32BE(0xffffffff); // lo
@@ -51,7 +51,7 @@ export const TimestampType = {
51
51
  return d;
52
52
  },
53
53
  decodeText(v, options) {
54
- return parseDateTime(v, options.utcDates);
54
+ return parseDateTime(v, options.utcDates, options.dateStyle);
55
55
  },
56
56
  // Reads PostgreSQL's own timestamp shape straight from the wire bytes -
57
57
  // see timestamptz-type.ts's decodeTextBuffer for why. The guard is what
@@ -14,7 +14,7 @@ export const TimestamptzType = {
14
14
  },
15
15
  encodeBinary(buf, v, options) {
16
16
  if (typeof v === 'string')
17
- v = parseDateTimeTz(v, options.utcDates);
17
+ v = parseDateTimeTz(v, options.utcDates, options.dateStyle);
18
18
  if (v === Infinity) {
19
19
  buf.writeInt32BE(0x7fffffff); // hi
20
20
  buf.writeUInt32BE(0xffffffff); // lo
@@ -53,7 +53,7 @@ export const TimestamptzType = {
53
53
  return new Date((lo + hi * timeMul) / 1000 + timeShift);
54
54
  },
55
55
  decodeText(v, options) {
56
- return parseDateTimeTz(v, options.utcDates);
56
+ return parseDateTimeTz(v, options.utcDates, options.dateStyle);
57
57
  },
58
58
  // Reads PostgreSQL's own timestamp shape straight from the wire bytes.
59
59
  // Building the string first and handing it to decodeText() costs about
@@ -67,6 +67,15 @@ export const TimestamptzType = {
67
67
  return d;
68
68
  return TimestamptzType.decodeText(buf.toString('latin1', offset, offset + len), options);
69
69
  },
70
+ /**
71
+ * Unreachable through `determine()`: TimestampType, TimeType and
72
+ * DateType are all consulted before this one and every Date is claimed
73
+ * by one of them. Nothing depends on it - a Date parameter goes out
74
+ * unspecified and the column decides what it means (see
75
+ * format-datetime.ts's formatDateParam) - so this stays as the
76
+ * truthful answer to a direct call rather than being narrowed to
77
+ * describe a walk it never wins.
78
+ */
70
79
  isType(v) {
71
80
  return v instanceof Date;
72
81
  },
package/index.d.ts CHANGED
@@ -27,6 +27,7 @@ export * from './interfaces/query-result.js';
27
27
  export * from './interfaces/script-execute-options.js';
28
28
  export * from './interfaces/script-result.js';
29
29
  export * from './interfaces/statement-prepare-options.js';
30
+ export * from './interfaces/transaction-options.js';
30
31
  export * from './protocol/connection-lost-error.js';
31
32
  export * from './protocol/database-error.js';
32
33
  export * from './protocol/smart-buffer.js';
package/index.js CHANGED
@@ -27,6 +27,7 @@ export * from './interfaces/query-result.js';
27
27
  export * from './interfaces/script-execute-options.js';
28
28
  export * from './interfaces/script-result.js';
29
29
  export * from './interfaces/statement-prepare-options.js';
30
+ export * from './interfaces/transaction-options.js';
30
31
  export * from './protocol/connection-lost-error.js';
31
32
  export * from './protocol/database-error.js';
32
33
  export * from './protocol/smart-buffer.js';
@@ -1,5 +1,32 @@
1
1
  import type { OID } from '../types.js';
2
+ import type { PgDateStyle } from '../util/date-style.js';
3
+ /** How a server renders `money` - see `DataMappingOptions.moneyFormat`. */
4
+ export interface MoneyFormat {
5
+ /** Fraction digits: 2 for `$1.00`, 0 for `¥1`, 3 for a dinar. */
6
+ scale: number;
7
+ /** The decimal separator the server prints, `.` or `,`. */
8
+ decimalSeparator: string;
9
+ }
2
10
  export interface DataMappingOptions {
11
+ /**
12
+ * How the server renders dates, when it is not rendering them in ISO.
13
+ *
14
+ * Only the text wire format is affected - binary carries no formatting
15
+ * at all - and the connection fills this in from the `DateStyle` the
16
+ * server reports, so a caller normally never sets it. See
17
+ * `util/date-style.ts` for what each setting prints.
18
+ */
19
+ dateStyle?: PgDateStyle;
20
+ /**
21
+ * How the server renders money, which is what `money`'s wire format
22
+ * cannot say on its own: it is an int64 of the smallest currency unit,
23
+ * and how many of those make one is `lc_monetary` - a setting the
24
+ * server does not report. A connection asks it once on its way up (see
25
+ * `IntlConnection.ensureMoneyFormat()`) and fills this in; set it here
26
+ * to skip that question, or to read a value rendered by a different
27
+ * server than the one at hand.
28
+ */
29
+ moneyFormat?: MoneyFormat;
3
30
  /**
4
31
  * If true UTC time will be used for date decoding, else system time offset will be used
5
32
  * @default false
@@ -2,6 +2,8 @@ import type { PoolConfiguration as LPoolConfiguration } from 'lightning-pool';
2
2
  import type { ConnectionOptions as TlsConnectionOptions } from 'tls';
3
3
  import type { SmartBufferConfig } from '../protocol/smart-buffer.js';
4
4
  import type { DebugLogger } from '../types.js';
5
+ import type { DataMappingOptions } from './data-mapping-options.js';
6
+ import type { QueryOptions } from './query-options.js';
5
7
  export interface DatabaseConnectionParams {
6
8
  host?: string;
7
9
  port?: number;
@@ -147,8 +149,32 @@ export type TargetSessionAttrs = 'read-write' | 'read-only' | 'primary' | 'stand
147
149
  export interface SocketOptions {
148
150
  keepAlive?: boolean;
149
151
  }
150
- export interface ConnectionConfiguration extends DatabaseConnectionParams, SocketOptions {
152
+ /**
153
+ * The data-mapping options a connection can answer for every statement
154
+ * on it, so a caller that always wants the same shape says so once.
155
+ *
156
+ * Each is the default for `QueryOptions`'s field of the same name and
157
+ * is described there; a value on the call always wins. `fetchAsString`
158
+ * and `unknownTypesAsString` are the two with a cost worth knowing
159
+ * about - see `fetchAsString` in QueryOptions.
160
+ */
161
+ export type ConnectionMappingDefaults = DataMappingOptions & Pick<QueryOptions, 'objectRows' | 'rowDecoder' | 'typeMap' | 'columnFormat'>;
162
+ export interface ConnectionConfiguration extends DatabaseConnectionParams, SocketOptions, ConnectionMappingDefaults {
151
163
  buffer?: SmartBufferConfig;
164
+ /**
165
+ * Whether statements may share a connection with statements already in
166
+ * flight - the default for every call here, which
167
+ * `QueryOptions.pipeline` overrides per statement and describes in
168
+ * full.
169
+ *
170
+ * Defaults to **on for a `Connection`** (what one has always done) and
171
+ * **off for a `Pool`**, where sharing also means running on a
172
+ * connection other callers are using. `true` on a pool turns it on for
173
+ * every query that can take it; `false` on a connection gives the
174
+ * behaviour of a client that waits for each reply before sending the
175
+ * next statement.
176
+ */
177
+ pipeline?: boolean;
152
178
  }
153
179
  export interface PoolConfiguration extends ConnectionConfiguration, LPoolConfiguration {
154
180
  /**
@@ -162,10 +188,11 @@ export interface PoolConfiguration extends ConnectionConfiguration, LPoolConfigu
162
188
  * ceiling on connections - with the default pool of 10, a burst of 1000
163
189
  * queries no longer has to run as 100 sequential rounds of 10.
164
190
  *
165
- * Set to 1 for the older behaviour, where Pool.query() holds a
166
- * connection exclusively for the duration of each query. Connections
167
- * handed out by acquire() are never shared, whatever this is set to, so
168
- * transactions, cursors and prepared statements are unaffected.
191
+ * Only queries that asked to be pipelined are counted, since sharing a
192
+ * pooled connection is opt-in - see `pipeline`. Setting this to 1 is
193
+ * the other way to turn it off. Connections handed out by acquire()
194
+ * are never shared whatever either is set to, so transactions, cursors
195
+ * and prepared statements are unaffected.
169
196
  */
170
197
  pipelineMaxQueries?: number;
171
198
  /**
@@ -9,6 +9,38 @@ export interface QueryOptions extends DataMappingOptions {
9
9
  * @default true
10
10
  */
11
11
  autoCommit?: boolean;
12
+ /**
13
+ * Whether this statement may share a connection with statements that
14
+ * are already in flight.
15
+ *
16
+ * PostgreSQL correlates responses to requests by order, so several
17
+ * statements can be on the wire at once, each under its own Sync and
18
+ * therefore with its own error boundary.
19
+ *
20
+ * **The default differs by where the statement runs, because the two
21
+ * places are not the same question:**
22
+ *
23
+ * - On a `Connection`, **on**. The connection is the caller's own, and
24
+ * concurrent `query()` calls on one have always overlapped this way.
25
+ * - On a `Pool`, **off**. Here it also decides *which* connection the
26
+ * statement runs on: sharing puts it on a connection other callers
27
+ * are using, where session state - `SET`, an advisory lock, a
28
+ * temporary table - is no longer the caller's alone. That is opt-in,
29
+ * per call or with `pipeline: true` on the pool.
30
+ *
31
+ * `false` asks for the wire to itself: the statement waits until
32
+ * nothing else is running on the connection, and nothing else starts
33
+ * until it finishes, whichever mode those asked for. Nothing orders
34
+ * two pipelined statements against each other, so this is how a
35
+ * statement that depends on what another one left behind says so.
36
+ *
37
+ * Some statements never share a *pooled* connection however this is
38
+ * set, because they need one to themselves for longer than the call:
39
+ * anything carrying an `AbortSignal`, anything with
40
+ * `autoCommit: false`, a cursor, and SQL that opens a transaction or a
41
+ * COPY. On a `Connection` the setting is only about the wire.
42
+ */
43
+ pipeline?: boolean;
12
44
  /**
13
45
  * Specifies if rows will be fetched as <FieldName, Value> pair objects or array of values
14
46
  * @deprecated Use `rowDecoder: 'object'` instead - `objectRows: true` still works exactly
@@ -7,6 +7,38 @@ export interface ScriptExecuteOptions extends DataMappingOptions {
7
7
  * @default true
8
8
  */
9
9
  autoCommit?: boolean;
10
+ /**
11
+ * Whether this statement may share a connection with statements that
12
+ * are already in flight.
13
+ *
14
+ * PostgreSQL correlates responses to requests by order, so several
15
+ * statements can be on the wire at once, each under its own Sync and
16
+ * therefore with its own error boundary.
17
+ *
18
+ * **The default differs by where the statement runs, because the two
19
+ * places are not the same question:**
20
+ *
21
+ * - On a `Connection`, **on**. The connection is the caller's own, and
22
+ * concurrent `query()` calls on one have always overlapped this way.
23
+ * - On a `Pool`, **off**. Here it also decides *which* connection the
24
+ * statement runs on: sharing puts it on a connection other callers
25
+ * are using, where session state - `SET`, an advisory lock, a
26
+ * temporary table - is no longer the caller's alone. That is opt-in,
27
+ * per call or with `pipeline: true` on the pool.
28
+ *
29
+ * `false` asks for the wire to itself: the statement waits until
30
+ * nothing else is running on the connection, and nothing else starts
31
+ * until it finishes, whichever mode those asked for. Nothing orders
32
+ * two pipelined statements against each other, so this is how a
33
+ * statement that depends on what another one left behind says so.
34
+ *
35
+ * Some statements never share a *pooled* connection however this is
36
+ * set, because they need one to themselves for longer than the call:
37
+ * anything carrying an `AbortSignal`, anything with
38
+ * `autoCommit: false`, a cursor, and SQL that opens a transaction or a
39
+ * COPY. On a `Connection` the setting is only about the wire.
40
+ */
41
+ pipeline?: boolean;
10
42
  /**
11
43
  * Specifies if rows will be fetched as <FieldName, Value> pair objects or array of values
12
44
  * @deprecated Use `rowDecoder: 'object'` instead - `objectRows: true` still works exactly
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The modes PostgreSQL takes on the `BEGIN` itself.
3
+ *
4
+ * Setting them this way is one round trip where `BEGIN` followed by
5
+ * `SET TRANSACTION` is two - measured at 0.500ms against 0.740ms for
6
+ * opening and closing one isolated transaction, which is most of what a
7
+ * short transaction costs.
8
+ *
9
+ * `SET TRANSACTION SNAPSHOT` is deliberately absent: PostgreSQL does not
10
+ * accept it on `BEGIN`, so it still needs a statement of its own.
11
+ */
12
+ export interface TransactionOptions {
13
+ /**
14
+ * What this transaction may see of the ones running beside it.
15
+ * Defaults to the server's `default_transaction_isolation`, which is
16
+ * `read committed` unless the server says otherwise.
17
+ */
18
+ isolationLevel?: 'serializable' | 'repeatable read' | 'read committed' | 'read uncommitted';
19
+ /** Refuses anything that would write, at the server. */
20
+ readOnly?: boolean;
21
+ /**
22
+ * Lets a `serializable` `readOnly` transaction wait until it can run
23
+ * without the serialization failures that level otherwise risks -
24
+ * ignored by the server for any other combination.
25
+ */
26
+ deferrable?: boolean;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "postgrejs",
3
3
  "description": "Professional PostgreSQL client NodeJS",
4
- "version": "3.7.0",
4
+ "version": "3.9.0",
5
5
  "author": "Panates",
6
6
  "license": "BSD-3-Clause",
7
7
  "dependencies": {
@@ -16,6 +16,21 @@ export declare class DatabaseError extends Error {
16
16
  * explicit transaction was already open they are rolled back with it.
17
17
  */
18
18
  batchResults?: BatchCommandResult[];
19
+ /**
20
+ * The message exactly as PostgreSQL sent it.
21
+ *
22
+ * `message` is decorated where the server reported a position - the
23
+ * line and column, the offending source line and a caret under it -
24
+ * which is what makes an error readable in a terminal with no extra
25
+ * work, and would otherwise leave nothing holding the original. A
26
+ * consumer that parses the text needs the original: PostgreSQL does
27
+ * not always put the interesting part in a field, so an anchored
28
+ * pattern like `/^column (.+) does not exist$/` is how the column name
29
+ * is read, and a suffix defeats it.
30
+ *
31
+ * Always set, and equal to `message` when nothing was decorated.
32
+ */
33
+ readonly serverMessage: string;
19
34
  severity?: string;
20
35
  code?: string;
21
36
  detail?: string;
@@ -15,6 +15,21 @@ export class DatabaseError extends Error {
15
15
  * explicit transaction was already open they are rolled back with it.
16
16
  */
17
17
  batchResults;
18
+ /**
19
+ * The message exactly as PostgreSQL sent it.
20
+ *
21
+ * `message` is decorated where the server reported a position - the
22
+ * line and column, the offending source line and a caret under it -
23
+ * which is what makes an error readable in a terminal with no extra
24
+ * work, and would otherwise leave nothing holding the original. A
25
+ * consumer that parses the text needs the original: PostgreSQL does
26
+ * not always put the interesting part in a field, so an anchored
27
+ * pattern like `/^column (.+) does not exist$/` is how the column name
28
+ * is read, and a suffix defeats it.
29
+ *
30
+ * Always set, and equal to `message` when nothing was decorated.
31
+ */
32
+ serverMessage;
18
33
  severity;
19
34
  code;
20
35
  detail;
@@ -41,5 +56,9 @@ export class DatabaseError extends Error {
41
56
  });
42
57
  if (msg.position)
43
58
  this.position = parseInt(msg.position, 10) || undefined;
59
+ // Read off `message` rather than off `msg`, and after the assign
60
+ // above, so the two cannot drift apart - whatever this error was
61
+ // built to say, before anything decorates it.
62
+ this.serverMessage = this.message;
44
63
  }
45
64
  }
@@ -1,6 +1,8 @@
1
1
  import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
2
2
  import { DataTypeMap } from '../data-type-map.js';
3
+ import { arrayLeaf } from '../util/array-leaf.js';
3
4
  import { encodeBinaryArray } from '../util/encode-binaryarray.js';
5
+ import { formatDateParam } from '../util/format-datetime.js';
4
6
  import { stringifyArrayLiteral } from '../util/stringify-arrayliteral.js';
5
7
  import { Protocol } from './protocol.js';
6
8
  import { SmartBuffer } from './smart-buffer.js';
@@ -186,6 +188,21 @@ export class Frontend {
186
188
  io.writeLString(v, 'utf8');
187
189
  }
188
190
  }
191
+ else if (v instanceof Date) {
192
+ // No declared type, so the server resolves it from the column
193
+ // - which is the whole point, and why the text has to carry
194
+ // the zone. `String(v)` is a JavaScript date string the server
195
+ // cannot parse at all, which is what a prepared statement with
196
+ // no paramTypes used to send.
197
+ io.writeLString(formatDateParam(v, queryOptions), 'utf8');
198
+ }
199
+ else if (Array.isArray(v)) {
200
+ // Also undeclared, so the server reads an array literal and
201
+ // takes the element type from the column. `'' + v` would hand
202
+ // it `a,b`, which is not one - the elements have to be quoted
203
+ // and the braces written, and a nested array needs its own.
204
+ io.writeLString(stringifyArrayLiteral(v, queryOptions, arrayLeaf(v) instanceof Date ? formatDateParam : undefined), 'utf8');
205
+ }
189
206
  else if (Buffer.isBuffer(v)) {
190
207
  // Set param format to binary
191
208
  io.buffer.writeInt16BE(Protocol.DataFormat.binary, formatOffset + i * 2);
@@ -899,16 +899,13 @@ export class PgSocket extends SafeEventEmitter {
899
899
  socket.write(data, cb);
900
900
  return true;
901
901
  }
902
- socket.cork();
903
- try {
904
- const l = data.length;
905
- let i;
906
- for (i = 0; i < l; i++)
907
- socket.write(data[i], i === l - 1 ? cb : undefined);
908
- }
909
- finally {
910
- socket.uncork();
911
- }
902
+ // Joined rather than written one by one under a cork: a statement is
903
+ // three messages (Bind, Execute, Sync) of a few dozen bytes, and a
904
+ // write apiece costs three trips through the stream machinery - a
905
+ // WriteReq, a buffer entry, a cork/uncork pair - to save a copy of
906
+ // 43 bytes. Measured at 50 concurrent queries, 150 writes against
907
+ // 50.
908
+ socket.write(data.length === 1 ? data[0] : Buffer.concat(data), cb);
912
909
  return true;
913
910
  }
914
911
  /**
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The first value inside `v` that is neither an array nor null - what an
3
+ * array's element type is read from. A scalar answers itself, and an
4
+ * array with nothing but nulls (or nothing at all) answers `undefined`.
5
+ *
6
+ * Reading `v[0]` instead is wrong twice over: `[[1, 2], [3, 4]]` asks
7
+ * what type `[1, 2]` is, and `[null, 2, 3]` asks what type `null` is.
8
+ * Neither has an answer, and both are ordinary values to send.
9
+ */
10
+ export declare function arrayLeaf(v: any): any;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The first value inside `v` that is neither an array nor null - what an
3
+ * array's element type is read from. A scalar answers itself, and an
4
+ * array with nothing but nulls (or nothing at all) answers `undefined`.
5
+ *
6
+ * Reading `v[0]` instead is wrong twice over: `[[1, 2], [3, 4]]` asks
7
+ * what type `[1, 2]` is, and `[null, 2, 3]` asks what type `null` is.
8
+ * Neither has an answer, and both are ordinary values to send.
9
+ */
10
+ export function arrayLeaf(v) {
11
+ if (!Array.isArray(v))
12
+ return v;
13
+ const l = v.length;
14
+ let i;
15
+ let x;
16
+ for (i = 0; i < l; i++) {
17
+ x = arrayLeaf(v[i]);
18
+ if (x != null)
19
+ return x;
20
+ }
21
+ return undefined;
22
+ }