postgrejs 3.1.2 → 3.3.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 +48 -1
- package/README.md +25 -9
- package/connection/cursor.d.ts +2 -1
- package/connection/cursor.js +6 -6
- package/connection/intl-connection.d.ts +7 -0
- package/connection/intl-connection.js +18 -14
- package/connection/prepared-statement.js +2 -1
- package/data-types/bool-type.js +0 -1
- package/data-types/box-type.js +0 -1
- package/data-types/bytea-type.js +5 -3
- package/data-types/char-type.js +2 -2
- package/data-types/circle-type.js +0 -1
- package/data-types/date-type.js +1 -2
- package/data-types/float4-type.js +2 -4
- package/data-types/float8-type.js +2 -5
- package/data-types/int2-type.js +0 -1
- package/data-types/int2-vector-type.js +3 -2
- package/data-types/int4-type.js +0 -1
- package/data-types/int8-type.js +0 -1
- package/data-types/json-type.js +2 -2
- package/data-types/jsonb-type.js +6 -5
- package/data-types/lseg-type.js +0 -1
- package/data-types/numeric-type.js +2 -4
- package/data-types/oid-type.js +0 -1
- package/data-types/oid-vector-type.js +3 -2
- package/data-types/point-type.js +0 -1
- package/data-types/time-type.js +1 -2
- package/data-types/timestamp-type.js +13 -3
- package/data-types/timestamptz-type.js +27 -10
- package/data-types/uuid-type.js +1 -2
- package/data-types/varchar-type.js +2 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/command-result.d.ts +3 -2
- package/interfaces/data-type.d.ts +0 -1
- package/interfaces/query-options.d.ts +12 -0
- package/interfaces/script-execute-options.d.ts +12 -0
- package/package.json +14 -2
- package/protocol/backend.js +6 -6
- package/protocol/buffer-reader.d.ts +7 -12
- package/protocol/buffer-reader.js +11 -58
- package/protocol/frontend.js +86 -96
- package/protocol/pg-socket.d.ts +6 -33
- package/protocol/pg-socket.js +19 -74
- package/protocol/pgoutput.js +2 -2
- package/protocol/smart-buffer.d.ts +11 -29
- package/protocol/smart-buffer.js +19 -162
- package/types.d.ts +13 -1
- package/util/bigint-methods.d.ts +0 -1
- package/util/bigint-methods.js +0 -22
- package/util/decode-binaryarray.d.ts +1 -1
- package/util/decode-binaryarray.js +7 -7
- package/util/encode-binaryarray.js +6 -6
- package/util/fast-parsefloat.d.ts +15 -0
- package/util/fast-parsefloat.js +141 -0
- package/util/get-parsers.js +4 -8
- package/util/parse-datetime.js +7 -0
- package/util/parse-pg-timestamp.d.ts +31 -0
- package/util/parse-pg-timestamp.js +199 -0
- package/util/parse-row.js +120 -0
- package/util/row-decoder.d.ts +49 -0
- package/util/row-decoder.js +63 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a fixed 2-digit field at `i`, or -1 when it runs past `end` or
|
|
3
|
+
* either byte isn't a digit. Module level, not a closure inside the
|
|
4
|
+
* scanner: a closure allocated per parsed value would cost more than the
|
|
5
|
+
* parse it speeds up.
|
|
6
|
+
*
|
|
7
|
+
* `end` is load-bearing, not a formality: this reads a row's own slice of
|
|
8
|
+
* a shared buffer, so a read past the end would silently pick up the next
|
|
9
|
+
* column's bytes - and accept a truncated timestamp if they happened to be
|
|
10
|
+
* digits, rather than declining it.
|
|
11
|
+
*/
|
|
12
|
+
function read2(buf, i, end) {
|
|
13
|
+
if (i + 1 >= end)
|
|
14
|
+
return -1;
|
|
15
|
+
const a = buf[i] - 48;
|
|
16
|
+
if (a < 0 || a > 9)
|
|
17
|
+
return -1;
|
|
18
|
+
const b = buf[i + 1] - 48;
|
|
19
|
+
if (b < 0 || b > 9)
|
|
20
|
+
return -1;
|
|
21
|
+
return a * 10 + b;
|
|
22
|
+
}
|
|
23
|
+
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
24
|
+
/**
|
|
25
|
+
* Days between the Unix epoch and a proleptic-Gregorian Y/M/D (Howard
|
|
26
|
+
* Hinnant's days_from_civil). `Date.UTC()` produces the same number but
|
|
27
|
+
* costs roughly twice as much, because it also has to coerce and normalise
|
|
28
|
+
* arbitrarily out-of-range arguments - which every caller here has already
|
|
29
|
+
* ruled out. Only correct for the in-range, already-validated values the
|
|
30
|
+
* scanner feeds it (years 1000-9999, so every intermediate stays positive
|
|
31
|
+
* and `| 0` truncation is the same as flooring).
|
|
32
|
+
*/
|
|
33
|
+
function epochDays(year, month, day) {
|
|
34
|
+
const y = month <= 2 ? year - 1 : year;
|
|
35
|
+
const era = (y / 400) | 0;
|
|
36
|
+
const yoe = y - era * 400;
|
|
37
|
+
const doy = (((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) | 0) + day - 1;
|
|
38
|
+
const doe = yoe * 365 + ((yoe / 4) | 0) - ((yoe / 100) | 0) + doy;
|
|
39
|
+
return era * 146097 + doe - 719468;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Decodes the exact shape PostgreSQL's own ISO output uses -
|
|
43
|
+
* `YYYY-MM-DD HH:MM:SS[.f+][(+|-)HH[:MM]]` - straight out of the wire
|
|
44
|
+
* bytes, instead of building a string and handing that to the engine's
|
|
45
|
+
* general-purpose date parser. `new Date(str)` costs ~150ns per value and
|
|
46
|
+
* dominates decode time on timestamp-heavy result sets; even the string
|
|
47
|
+
* this replaces costs ~43ns to materialise before any parsing starts.
|
|
48
|
+
*
|
|
49
|
+
* Returns undefined for anything that isn't *exactly* that shape, so every
|
|
50
|
+
* other input (compact forms, `T` separators, a `BC` suffix, years outside
|
|
51
|
+
* 1000-9999, LMT-style ±HH:MM:SS offsets) falls through to the original
|
|
52
|
+
* `new Date()`/regex path and keeps its existing result - including the
|
|
53
|
+
* engine's own quirks, which callers may already depend on:
|
|
54
|
+
* `0001-01-01` parsing as year 2001, and `-04:56:02` offsets being
|
|
55
|
+
* rejected outright.
|
|
56
|
+
*
|
|
57
|
+
* A missing offset means local time, matching what `new Date(str)` does
|
|
58
|
+
* with a space-separated, offset-less timestamp.
|
|
59
|
+
*/
|
|
60
|
+
export function parsePgTimestampBuffer(buf, offset, end) {
|
|
61
|
+
if (end - offset < 19)
|
|
62
|
+
return undefined;
|
|
63
|
+
// Year must be a plain 1000-9999: a leading zero would hit the engine's
|
|
64
|
+
// legacy two-digit-year mapping (`0001` -> 2001) that the fallback path
|
|
65
|
+
// still produces.
|
|
66
|
+
const y0 = buf[offset] - 48;
|
|
67
|
+
if (y0 < 1 || y0 > 9)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (buf[offset + 4] !== 45 || // -
|
|
70
|
+
buf[offset + 7] !== 45 || // -
|
|
71
|
+
buf[offset + 10] !== 32 || // space
|
|
72
|
+
buf[offset + 13] !== 58 || // :
|
|
73
|
+
buf[offset + 16] !== 58 // :
|
|
74
|
+
)
|
|
75
|
+
return undefined;
|
|
76
|
+
let n;
|
|
77
|
+
let year = y0;
|
|
78
|
+
for (let i = offset + 1; i < offset + 4; i++) {
|
|
79
|
+
n = buf[i] - 48;
|
|
80
|
+
if (n < 0 || n > 9)
|
|
81
|
+
return undefined;
|
|
82
|
+
year = year * 10 + n;
|
|
83
|
+
}
|
|
84
|
+
// month, day, hour, minute, second - each a 2-digit field at a fixed
|
|
85
|
+
// offset. Every field is range-checked rather than just parsed: the
|
|
86
|
+
// fallback path handles out-of-range values inconsistently (month 0,
|
|
87
|
+
// day 0, minute 60 and second 60 are rejected outright, while a day past
|
|
88
|
+
// the end of its month rolls over into the next one), so rather than
|
|
89
|
+
// reproduce that split, only unambiguously valid values are taken here
|
|
90
|
+
// and everything else is left to it, exactly as before.
|
|
91
|
+
const month = read2(buf, offset + 5, end);
|
|
92
|
+
if (month < 1 || month > 12)
|
|
93
|
+
return undefined;
|
|
94
|
+
const day = read2(buf, offset + 8, end);
|
|
95
|
+
if (day < 1 || day > 31)
|
|
96
|
+
return undefined;
|
|
97
|
+
const hour = read2(buf, offset + 11, end);
|
|
98
|
+
if (hour < 0 || hour > 23)
|
|
99
|
+
return undefined;
|
|
100
|
+
const minute = read2(buf, offset + 14, end);
|
|
101
|
+
if (minute < 0 || minute > 59)
|
|
102
|
+
return undefined;
|
|
103
|
+
const second = read2(buf, offset + 17, end);
|
|
104
|
+
if (second < 0 || second > 59)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (day > DAYS_IN_MONTH[month - 1]) {
|
|
107
|
+
// February 29th is the one day this table can't answer on its own.
|
|
108
|
+
if (month !== 2 || day !== 29)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (year % 4 !== 0 || (year % 100 === 0 && year % 400 !== 0))
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
let pos = offset + 19;
|
|
114
|
+
let ms = 0;
|
|
115
|
+
if (pos < end && buf[pos] === 46) {
|
|
116
|
+
// Fractional seconds: milliseconds are the first 3 digits, zero-padded
|
|
117
|
+
// when fewer are present (".5" is 500ms) and truncated when more are
|
|
118
|
+
// (".123456" is 123ms) - both matching the existing paths exactly.
|
|
119
|
+
pos++;
|
|
120
|
+
let digits = 0;
|
|
121
|
+
while (pos < end) {
|
|
122
|
+
n = buf[pos] - 48;
|
|
123
|
+
if (n < 0 || n > 9)
|
|
124
|
+
break;
|
|
125
|
+
if (digits < 3)
|
|
126
|
+
ms = ms * 10 + n;
|
|
127
|
+
digits++;
|
|
128
|
+
pos++;
|
|
129
|
+
}
|
|
130
|
+
if (!digits)
|
|
131
|
+
return undefined;
|
|
132
|
+
if (digits === 1)
|
|
133
|
+
ms *= 100;
|
|
134
|
+
else if (digits === 2)
|
|
135
|
+
ms *= 10;
|
|
136
|
+
}
|
|
137
|
+
if (pos === end)
|
|
138
|
+
return new Date(year, month - 1, day, hour, minute, second, ms);
|
|
139
|
+
const sign = buf[pos];
|
|
140
|
+
if (sign !== 43 && sign !== 45)
|
|
141
|
+
return undefined; // + or -
|
|
142
|
+
pos++;
|
|
143
|
+
const offHour = read2(buf, pos, end);
|
|
144
|
+
if (offHour < 0 || offHour > 23)
|
|
145
|
+
return undefined;
|
|
146
|
+
pos += 2;
|
|
147
|
+
let offMinute = 0;
|
|
148
|
+
if (pos < end) {
|
|
149
|
+
if (buf[pos] !== 58)
|
|
150
|
+
return undefined; // :
|
|
151
|
+
offMinute = read2(buf, pos + 1, end);
|
|
152
|
+
if (offMinute < 0 || offMinute > 59)
|
|
153
|
+
return undefined;
|
|
154
|
+
pos += 3;
|
|
155
|
+
// A trailing ":SS" (PostgreSQL emits these for pre-standard-time
|
|
156
|
+
// dates) is deliberately not handled: `new Date()` rejects those
|
|
157
|
+
// outright today, so they must keep falling through to do the same.
|
|
158
|
+
if (pos !== end)
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
const offsetMinutes = (offHour * 60 + offMinute) * (sign === 45 ? -1 : 1);
|
|
162
|
+
return new Date((epochDays(year, month, day) * 86400 +
|
|
163
|
+
hour * 3600 +
|
|
164
|
+
(minute - offsetMinutes) * 60 +
|
|
165
|
+
second) *
|
|
166
|
+
1000 +
|
|
167
|
+
ms);
|
|
168
|
+
}
|
|
169
|
+
// Big enough for any timestamp PostgreSQL prints (the longest is
|
|
170
|
+
// "YYYY-MM-DD HH:MM:SS.ffffff+HH:MM", 32 bytes); anything longer is left to
|
|
171
|
+
// the fallback, which produces the same value it always did.
|
|
172
|
+
const scratch = Buffer.allocUnsafe(64);
|
|
173
|
+
/**
|
|
174
|
+
* The same scan for a value that is already a string - the array-element
|
|
175
|
+
* and string-parameter paths, which never see the wire bytes.
|
|
176
|
+
*
|
|
177
|
+
* Copying into a scratch buffer first still beats scanning the string
|
|
178
|
+
* directly through a second, parallel implementation: the copy costs about
|
|
179
|
+
* half of what materialising the string cost in the first place, and there
|
|
180
|
+
* is only one scanner to keep correct. Safe to share one buffer because
|
|
181
|
+
* the copy and the scan that reads it are a single synchronous step.
|
|
182
|
+
*/
|
|
183
|
+
export function parsePgTimestampString(str) {
|
|
184
|
+
const len = str.length;
|
|
185
|
+
if (len < 19 || len > scratch.length)
|
|
186
|
+
return undefined;
|
|
187
|
+
let c;
|
|
188
|
+
for (let i = 0; i < len; i++) {
|
|
189
|
+
c = str.charCodeAt(i);
|
|
190
|
+
// A code point above 255 would be truncated on the way into the buffer
|
|
191
|
+
// and could land on a digit, turning a string the scanner should
|
|
192
|
+
// reject into one it accepts. Nothing in the shape it scans is
|
|
193
|
+
// non-ASCII, so anything that isn't goes to the fallback.
|
|
194
|
+
if (c > 255)
|
|
195
|
+
return undefined;
|
|
196
|
+
scratch[i] = c;
|
|
197
|
+
}
|
|
198
|
+
return parsePgTimestampBuffer(scratch, 0, len);
|
|
199
|
+
}
|
package/util/parse-row.js
CHANGED
|
@@ -21,7 +21,127 @@ export function parseRow(parsers, data, columnCount, options) {
|
|
|
21
21
|
}
|
|
22
22
|
return row;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Rows decoded against one `FieldInfo[]` before compiling a factory for it.
|
|
26
|
+
*
|
|
27
|
+
* Compiling costs ~0.8µs when the engine can reuse its own compilation
|
|
28
|
+
* cache for identical source and several µs when it can't, against ~0.1µs
|
|
29
|
+
* saved per row - so a query returning a handful of rows would pay more
|
|
30
|
+
* than it ever got back. Since wrapRowDescription() builds a fresh fields
|
|
31
|
+
* array per query, a single-row query executed in a loop would otherwise
|
|
32
|
+
* recompile on every call. Waiting a few rows keeps that case free while
|
|
33
|
+
* giving up well under 1% of the win on a result worth compiling for.
|
|
34
|
+
*/
|
|
35
|
+
const OBJECT_ROW_FACTORY_THRESHOLD = 8;
|
|
36
|
+
/**
|
|
37
|
+
* Holds only factories that were actually compiled - never the row counts
|
|
38
|
+
* leading up to one. Writing a fresh key here costs ~67ns plus the garbage
|
|
39
|
+
* it leaves behind, which a single-row query (a new fields array every
|
|
40
|
+
* time, never looked up again) would pay on every call for nothing.
|
|
41
|
+
*
|
|
42
|
+
* Keyed weakly, so a finished query's factory goes away with its fields;
|
|
43
|
+
* an entry earns its keep only when the same fields array is decoded
|
|
44
|
+
* against again after something else interrupted it.
|
|
45
|
+
*/
|
|
46
|
+
const objectRowFactories = new WeakMap();
|
|
47
|
+
// Rows of one result arrive in a run, so a single entry in front of the
|
|
48
|
+
// WeakMap answers nearly every lookup with one identity check - and is
|
|
49
|
+
// also where the pre-threshold row count lives, keeping it out of the
|
|
50
|
+
// WeakMap entirely.
|
|
51
|
+
let memoFields;
|
|
52
|
+
let memoFactory = null;
|
|
53
|
+
let memoRows = 0;
|
|
54
|
+
// Set when this shape can never be compiled, so the attempt isn't
|
|
55
|
+
// repeated for every remaining row of the result.
|
|
56
|
+
let memoDeclined = false;
|
|
57
|
+
/**
|
|
58
|
+
* Compiles a function that builds this query's row object in one shot,
|
|
59
|
+
* from a single object literal with every column name in it.
|
|
60
|
+
*
|
|
61
|
+
* Assigning the columns one at a time instead (`row[name] = value`) walks
|
|
62
|
+
* the object through one hidden-class transition per column, per row -
|
|
63
|
+
* measured at ~6.8x the cost of handing the engine a literal whose shape
|
|
64
|
+
* it can lay out once and reuse for every row of the result.
|
|
65
|
+
*
|
|
66
|
+
* Returns null when that isn't possible, leaving the caller on the
|
|
67
|
+
* assignment path: a column named `__proto__` would set the prototype
|
|
68
|
+
* rather than become an own property if it appeared as a literal key, and
|
|
69
|
+
* Function construction itself can be unavailable (a Content-Security-
|
|
70
|
+
* Policy without 'unsafe-eval').
|
|
71
|
+
*/
|
|
72
|
+
function buildObjectRowFactory(fields, columnCount) {
|
|
73
|
+
let src = 'return{';
|
|
74
|
+
let name;
|
|
75
|
+
let i;
|
|
76
|
+
for (i = 0; i < columnCount; i++) {
|
|
77
|
+
name = fields[i].fieldName;
|
|
78
|
+
if (name === '__proto__')
|
|
79
|
+
return null;
|
|
80
|
+
if (i)
|
|
81
|
+
src += ',';
|
|
82
|
+
// JSON.stringify() quotes and escapes the name into a string literal,
|
|
83
|
+
// so any column name is embedded safely, whatever it contains.
|
|
84
|
+
src += JSON.stringify(name) + ':v[' + i + ']';
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return new Function('v', src + '}');
|
|
88
|
+
/* c8 ignore next 3 - needs a runtime with Function construction disabled */
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function getObjectRowFactory(fields, columnCount) {
|
|
95
|
+
if (fields !== memoFields) {
|
|
96
|
+
// A different result than the last row belonged to. A hit here means
|
|
97
|
+
// an earlier, long-enough run already paid to compile this exact
|
|
98
|
+
// fields array; otherwise start counting it from scratch.
|
|
99
|
+
memoFields = fields;
|
|
100
|
+
memoRows = 1;
|
|
101
|
+
memoDeclined = false;
|
|
102
|
+
memoFactory = objectRowFactories.get(fields) ?? null;
|
|
103
|
+
return memoFactory;
|
|
104
|
+
}
|
|
105
|
+
if (memoFactory || memoDeclined)
|
|
106
|
+
return memoFactory;
|
|
107
|
+
if (++memoRows < OBJECT_ROW_FACTORY_THRESHOLD)
|
|
108
|
+
return null;
|
|
109
|
+
const factory = buildObjectRowFactory(fields, columnCount);
|
|
110
|
+
if (!factory) {
|
|
111
|
+
memoDeclined = true;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
memoFactory = factory;
|
|
115
|
+
objectRowFactories.set(fields, factory);
|
|
116
|
+
return factory;
|
|
117
|
+
}
|
|
24
118
|
export function parseObjectRow(parsers, data, columnCount, options, fields) {
|
|
119
|
+
// A compiled factory is built against `fields`, so it can only be used
|
|
120
|
+
// when the row really does have one value per field.
|
|
121
|
+
const factory = fields.length === columnCount
|
|
122
|
+
? getObjectRowFactory(fields, columnCount)
|
|
123
|
+
: null;
|
|
124
|
+
if (factory) {
|
|
125
|
+
const values = new Array(columnCount);
|
|
126
|
+
let offset = 0;
|
|
127
|
+
let len;
|
|
128
|
+
let i;
|
|
129
|
+
for (i = 0; i < columnCount; i++) {
|
|
130
|
+
len = data.readInt32BE(offset);
|
|
131
|
+
offset += 4;
|
|
132
|
+
if (len < 0) {
|
|
133
|
+
values[i] = null;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
values[i] = parsers[i](data, offset, len, options);
|
|
137
|
+
offset += len;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return factory(values);
|
|
141
|
+
}
|
|
142
|
+
return parseObjectRowByAssignment(parsers, data, columnCount, options, fields);
|
|
143
|
+
}
|
|
144
|
+
function parseObjectRowByAssignment(parsers, data, columnCount, options, fields) {
|
|
25
145
|
const row = {};
|
|
26
146
|
let offset = 0;
|
|
27
147
|
let len;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
|
|
2
|
+
import type { FieldInfo } from '../interfaces/field-info.js';
|
|
3
|
+
import type { AnyParseFunction } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extension point for turning one row's raw wire data into whatever shape a
|
|
6
|
+
* caller wants. `data` is the row's own zero-copy buffer view and
|
|
7
|
+
* `columnCount`/`parsers` are exactly what `parseRow`/`parseObjectRow` take -
|
|
8
|
+
* a subclass is free to defer decoding individual columns (e.g. lazily, on
|
|
9
|
+
* first access) instead of eagerly decoding the whole row up front.
|
|
10
|
+
*
|
|
11
|
+
* Contract: `data` is never written to or reused by PostgreJS once `decode()`
|
|
12
|
+
* is called with it - it's safe to retain a reference to it (or a
|
|
13
|
+
* `Buffer.subarray()` of it) for as long as you want, e.g. to defer decoding
|
|
14
|
+
* a column until it's actually read. The only cost of doing so is memory:
|
|
15
|
+
* `data` is itself a zero-copy view into the socket's own read buffer (up to
|
|
16
|
+
* ~64KB), so holding onto even one row's `data` keeps that whole chunk (and
|
|
17
|
+
* every other row's bytes in it) alive until released.
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class RowDecoder {
|
|
20
|
+
abstract decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): any;
|
|
21
|
+
}
|
|
22
|
+
export declare class ArrayRowDecoder extends RowDecoder {
|
|
23
|
+
decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions): any[];
|
|
24
|
+
}
|
|
25
|
+
export declare class ObjectRowDecoder extends RowDecoder {
|
|
26
|
+
decode(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): object;
|
|
27
|
+
}
|
|
28
|
+
export declare const DEFAULT_ARRAY_ROW_DECODER: ArrayRowDecoder;
|
|
29
|
+
export declare const DEFAULT_OBJECT_ROW_DECODER: ObjectRowDecoder;
|
|
30
|
+
interface RowDecoderOptions {
|
|
31
|
+
rowDecoder?: 'array' | 'object' | RowDecoder;
|
|
32
|
+
objectRows?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Collapses the `rowDecoder`/`objectRows` query options into one RowDecoder
|
|
36
|
+
* instance - the single place this union gets resolved, so every call site
|
|
37
|
+
* (queryOnce/executeReused/the simple-query path/Cursor) applies the same
|
|
38
|
+
* precedence: an explicit `rowDecoder` wins over the deprecated `objectRows`
|
|
39
|
+
* boolean.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveRowDecoder(options: RowDecoderOptions): RowDecoder;
|
|
42
|
+
/**
|
|
43
|
+
* The `rowType` string surfaced on `QueryResult`/`Cursor.rowType`. `'custom'`
|
|
44
|
+
* when the caller supplied their own RowDecoder subclass - there's no way to
|
|
45
|
+
* know what shape it returns - otherwise the same `'array' | 'object'` this
|
|
46
|
+
* option has always reported.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveRowType(options: RowDecoderOptions): 'array' | 'object' | 'custom';
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { parseObjectRow, parseRow } from './parse-row.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extension point for turning one row's raw wire data into whatever shape a
|
|
4
|
+
* caller wants. `data` is the row's own zero-copy buffer view and
|
|
5
|
+
* `columnCount`/`parsers` are exactly what `parseRow`/`parseObjectRow` take -
|
|
6
|
+
* a subclass is free to defer decoding individual columns (e.g. lazily, on
|
|
7
|
+
* first access) instead of eagerly decoding the whole row up front.
|
|
8
|
+
*
|
|
9
|
+
* Contract: `data` is never written to or reused by PostgreJS once `decode()`
|
|
10
|
+
* is called with it - it's safe to retain a reference to it (or a
|
|
11
|
+
* `Buffer.subarray()` of it) for as long as you want, e.g. to defer decoding
|
|
12
|
+
* a column until it's actually read. The only cost of doing so is memory:
|
|
13
|
+
* `data` is itself a zero-copy view into the socket's own read buffer (up to
|
|
14
|
+
* ~64KB), so holding onto even one row's `data` keeps that whole chunk (and
|
|
15
|
+
* every other row's bytes in it) alive until released.
|
|
16
|
+
*/
|
|
17
|
+
export class RowDecoder {
|
|
18
|
+
}
|
|
19
|
+
export class ArrayRowDecoder extends RowDecoder {
|
|
20
|
+
decode(parsers, data, columnCount, options) {
|
|
21
|
+
return parseRow(parsers, data, columnCount, options);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class ObjectRowDecoder extends RowDecoder {
|
|
25
|
+
decode(parsers, data, columnCount, options, fields) {
|
|
26
|
+
return parseObjectRow(parsers, data, columnCount, options, fields);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export const DEFAULT_ARRAY_ROW_DECODER = new ArrayRowDecoder();
|
|
30
|
+
export const DEFAULT_OBJECT_ROW_DECODER = new ObjectRowDecoder();
|
|
31
|
+
/**
|
|
32
|
+
* Collapses the `rowDecoder`/`objectRows` query options into one RowDecoder
|
|
33
|
+
* instance - the single place this union gets resolved, so every call site
|
|
34
|
+
* (queryOnce/executeReused/the simple-query path/Cursor) applies the same
|
|
35
|
+
* precedence: an explicit `rowDecoder` wins over the deprecated `objectRows`
|
|
36
|
+
* boolean.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveRowDecoder(options) {
|
|
39
|
+
const { rowDecoder } = options;
|
|
40
|
+
if (rowDecoder instanceof RowDecoder)
|
|
41
|
+
return rowDecoder;
|
|
42
|
+
if (rowDecoder === 'object')
|
|
43
|
+
return DEFAULT_OBJECT_ROW_DECODER;
|
|
44
|
+
if (rowDecoder === 'array')
|
|
45
|
+
return DEFAULT_ARRAY_ROW_DECODER;
|
|
46
|
+
return options.objectRows
|
|
47
|
+
? DEFAULT_OBJECT_ROW_DECODER
|
|
48
|
+
: DEFAULT_ARRAY_ROW_DECODER;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The `rowType` string surfaced on `QueryResult`/`Cursor.rowType`. `'custom'`
|
|
52
|
+
* when the caller supplied their own RowDecoder subclass - there's no way to
|
|
53
|
+
* know what shape it returns - otherwise the same `'array' | 'object'` this
|
|
54
|
+
* option has always reported.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveRowType(options) {
|
|
57
|
+
const decoder = resolveRowDecoder(options);
|
|
58
|
+
if (decoder instanceof ObjectRowDecoder)
|
|
59
|
+
return 'object';
|
|
60
|
+
if (decoder instanceof ArrayRowDecoder)
|
|
61
|
+
return 'array';
|
|
62
|
+
return 'custom';
|
|
63
|
+
}
|