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.
Files changed (62) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/README.md +25 -9
  3. package/connection/cursor.d.ts +2 -1
  4. package/connection/cursor.js +6 -6
  5. package/connection/intl-connection.d.ts +7 -0
  6. package/connection/intl-connection.js +18 -14
  7. package/connection/prepared-statement.js +2 -1
  8. package/data-types/bool-type.js +0 -1
  9. package/data-types/box-type.js +0 -1
  10. package/data-types/bytea-type.js +5 -3
  11. package/data-types/char-type.js +2 -2
  12. package/data-types/circle-type.js +0 -1
  13. package/data-types/date-type.js +1 -2
  14. package/data-types/float4-type.js +2 -4
  15. package/data-types/float8-type.js +2 -5
  16. package/data-types/int2-type.js +0 -1
  17. package/data-types/int2-vector-type.js +3 -2
  18. package/data-types/int4-type.js +0 -1
  19. package/data-types/int8-type.js +0 -1
  20. package/data-types/json-type.js +2 -2
  21. package/data-types/jsonb-type.js +6 -5
  22. package/data-types/lseg-type.js +0 -1
  23. package/data-types/numeric-type.js +2 -4
  24. package/data-types/oid-type.js +0 -1
  25. package/data-types/oid-vector-type.js +3 -2
  26. package/data-types/point-type.js +0 -1
  27. package/data-types/time-type.js +1 -2
  28. package/data-types/timestamp-type.js +13 -3
  29. package/data-types/timestamptz-type.js +27 -10
  30. package/data-types/uuid-type.js +1 -2
  31. package/data-types/varchar-type.js +2 -2
  32. package/index.d.ts +1 -0
  33. package/index.js +1 -0
  34. package/interfaces/command-result.d.ts +3 -2
  35. package/interfaces/data-type.d.ts +0 -1
  36. package/interfaces/query-options.d.ts +12 -0
  37. package/interfaces/script-execute-options.d.ts +12 -0
  38. package/package.json +14 -2
  39. package/protocol/backend.js +6 -6
  40. package/protocol/buffer-reader.d.ts +7 -12
  41. package/protocol/buffer-reader.js +11 -58
  42. package/protocol/frontend.js +86 -96
  43. package/protocol/pg-socket.d.ts +6 -33
  44. package/protocol/pg-socket.js +19 -74
  45. package/protocol/pgoutput.js +2 -2
  46. package/protocol/smart-buffer.d.ts +11 -29
  47. package/protocol/smart-buffer.js +19 -162
  48. package/types.d.ts +13 -1
  49. package/util/bigint-methods.d.ts +0 -1
  50. package/util/bigint-methods.js +0 -22
  51. package/util/decode-binaryarray.d.ts +1 -1
  52. package/util/decode-binaryarray.js +7 -7
  53. package/util/encode-binaryarray.js +6 -6
  54. package/util/fast-parsefloat.d.ts +15 -0
  55. package/util/fast-parsefloat.js +141 -0
  56. package/util/get-parsers.js +4 -8
  57. package/util/parse-datetime.js +7 -0
  58. package/util/parse-pg-timestamp.d.ts +31 -0
  59. package/util/parse-pg-timestamp.js +199 -0
  60. package/util/parse-row.js +120 -0
  61. package/util/row-decoder.d.ts +49 -0
  62. 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
+ }