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.
- package/CHANGELOG.md +53 -0
- package/README.md +50 -0
- package/connection/connection.d.ts +14 -2
- package/connection/connection.js +70 -10
- package/connection/intl-connection.d.ts +146 -5
- package/connection/intl-connection.js +485 -35
- package/connection/pool.d.ts +30 -6
- package/connection/pool.js +28 -3
- package/connection/prepared-statement.d.ts +11 -0
- package/connection/prepared-statement.js +39 -5
- package/data-type-map.d.ts +16 -0
- package/data-type-map.js +56 -12
- package/data-types/classes/numeric.d.ts +8 -4
- package/data-types/classes/numeric.js +8 -4
- package/data-types/date-type.js +2 -2
- package/data-types/int2-vector-type.js +4 -1
- package/data-types/money-type.d.ts +38 -0
- package/data-types/money-type.js +203 -0
- package/data-types/numeric-type.d.ts +2 -0
- package/data-types/numeric-type.js +1 -1
- package/data-types/oid-vector-type.js +4 -1
- package/data-types/timestamp-type.js +2 -2
- package/data-types/timestamptz-type.js +11 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/data-mapping-options.d.ts +27 -0
- package/interfaces/database-connection-params.d.ts +32 -5
- package/interfaces/query-options.d.ts +32 -0
- package/interfaces/script-execute-options.d.ts +32 -0
- package/interfaces/transaction-options.d.ts +27 -0
- package/interfaces/transaction-options.js +1 -0
- package/package.json +1 -1
- package/protocol/database-error.d.ts +15 -0
- package/protocol/database-error.js +19 -0
- package/protocol/frontend.js +17 -0
- package/protocol/pg-socket.js +7 -10
- package/util/array-leaf.d.ts +10 -0
- package/util/array-leaf.js +22 -0
- package/util/date-style.d.ts +47 -0
- package/util/date-style.js +123 -0
- package/util/decode-binaryarray.js +8 -1
- package/util/encode-binaryarray.d.ts +12 -1
- package/util/encode-binaryarray.js +13 -2
- package/util/format-datetime.d.ts +18 -0
- package/util/format-datetime.js +33 -0
- package/util/parse-datetime.d.ts +4 -3
- package/util/parse-datetime.js +34 -9
- package/util/resolve-column-formats.js +4 -0
- package/util/row-decoder.d.ts +24 -4
- package/util/row-decoder.js +25 -10
- package/util/sql-tag.js +16 -0
- package/util/stringify-for-sql.js +7 -0
- package/util/transaction-command.d.ts +7 -0
- package/util/transaction-command.js +32 -0
- package/util/unspecified-param.d.ts +27 -0
- 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
|
-
|
|
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
|
-
*
|
|
166
|
-
* connection
|
|
167
|
-
*
|
|
168
|
-
*
|
|
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
|
@@ -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
|
}
|
package/protocol/frontend.js
CHANGED
|
@@ -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);
|
package/protocol/pg-socket.js
CHANGED
|
@@ -899,16 +899,13 @@ export class PgSocket extends SafeEventEmitter {
|
|
|
899
899
|
socket.write(data, cb);
|
|
900
900
|
return true;
|
|
901
901
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
+
}
|