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
@@ -1,172 +1,29 @@
1
+ import { FlexyBuffer } from 'flexy-buffer';
1
2
  import * as os from 'os';
2
- import { writeBigUInt64BE } from '../util/bigint-methods.js';
3
- import { BufferReader } from './buffer-reader.js';
4
- export class SmartBuffer extends BufferReader {
3
+ /**
4
+ * Adds the PostgreSQL wire format's two string encodings on top of
5
+ * flexy-buffer's `FlexyBuffer`: a NUL-terminated C string, and a
6
+ * length-prefixed string where a `null`/`undefined` value is written as a
7
+ * length of -1 with no bytes following.
8
+ */
9
+ export class SmartBuffer extends FlexyBuffer {
5
10
  static DEFAULT_PAGE_SIZE = 512;
6
11
  static DEFAULT_MAX_SIZE = Math.min(Math.floor(os.totalmem() / 2), 1024 * 1024 * 1024 * 2);
7
- _houseKeepInterval;
8
- _houseKeepTimer;
9
- _stMaxPages = 1;
10
- _length = 0;
11
- pageSize;
12
- maxSize;
13
12
  constructor(cfg) {
14
- super(Buffer.allocUnsafe(cfg?.pageSize || SmartBuffer.DEFAULT_PAGE_SIZE));
15
- this._houseKeepInterval = cfg?.houseKeepInterval || 5000;
16
- this.pageSize = this.buffer.length;
17
- this.maxSize = cfg?.maxLength || SmartBuffer.DEFAULT_MAX_SIZE;
18
- this._length = 0;
19
- }
20
- get capacity() {
21
- return this.buffer.length;
22
- }
23
- get length() {
24
- return this._length;
25
- }
26
- start() {
27
- this.offset = 0;
28
- this._length = 0;
29
- if (this._houseKeepTimer) {
30
- clearTimeout(this._houseKeepTimer);
31
- this._houseKeepTimer = undefined;
32
- }
33
- return this;
34
- }
35
- flush() {
36
- if (this._houseKeepTimer)
37
- clearTimeout(this._houseKeepTimer);
38
- const length = this.length;
39
- this._length = 0;
40
- const out = Buffer.from(this.buffer.subarray(0, length));
41
- const pages = length ? Math.ceil(length / this.pageSize) : 1;
42
- this._stMaxPages = Math.max(this._stMaxPages, pages);
43
- this._houseKeep();
44
- if (this.buffer.length > this.pageSize) {
45
- this._houseKeepTimer = setTimeout(() => {
46
- this._houseKeepTimer = undefined;
47
- this._houseKeep();
48
- }, this._houseKeepInterval).unref();
49
- }
50
- return out;
51
- }
52
- growSize(len) {
53
- const endOffset = this.offset + len;
54
- if (this.capacity < endOffset) {
55
- if (endOffset > this.maxSize)
56
- throw new Error('Buffer limit exceeded.');
57
- const byPage = Math.ceil(endOffset / this.pageSize) * this.pageSize;
58
- const newSize = Math.min(Math.max(byPage, this.capacity * 2), this.maxSize);
59
- const newBuffer = Buffer.allocUnsafe(newSize);
60
- this.buffer.copy(newBuffer);
61
- this.buffer = newBuffer;
62
- }
63
- this._length = Math.max(this.length, endOffset);
64
- return this;
13
+ super({
14
+ pageSize: cfg?.pageSize || SmartBuffer.DEFAULT_PAGE_SIZE,
15
+ maxLength: cfg?.maxLength || SmartBuffer.DEFAULT_MAX_SIZE,
16
+ houseKeepMs: cfg?.houseKeepMs,
17
+ });
65
18
  }
66
19
  writeCString(str, encoding) {
67
- const len = str ? Buffer.byteLength(str, encoding) : 0;
68
- this.ensureSize(len + 1);
69
- if (str) {
70
- this.buffer.write(str, this.offset, encoding);
71
- this.offset += len;
72
- }
73
- this.writeUInt8(0);
74
- return this;
20
+ const written = this.writeString(str, encoding);
21
+ return written + this.writeUInt8(0);
75
22
  }
76
23
  writeLString(str, encoding) {
77
- const len = str ? Buffer.byteLength(str, encoding) : 0;
78
- this.ensureSize(len + 4);
79
- this.writeInt32BE(str == null ? -1 : len);
80
- if (str) {
81
- if (encoding)
82
- this.offset += this.buffer.write(str, this.offset, encoding);
83
- else
84
- this.offset += this.buffer.write(str, this.offset);
85
- }
86
- return this;
87
- }
88
- writeString(str, encoding) {
89
- if (str) {
90
- const len = Buffer.byteLength(str, encoding);
91
- this.ensureSize(len);
92
- this.offset += this.buffer.write(str, this.offset, encoding);
93
- }
94
- return this;
95
- }
96
- writeInt8(n) {
97
- this.ensureSize(1);
98
- this.buffer.writeInt8(n, this.offset);
99
- this.offset++;
100
- return this;
101
- }
102
- writeUInt8(n) {
103
- this.ensureSize(1);
104
- this.buffer.writeUInt8(n, this.offset);
105
- this.offset++;
106
- return this;
107
- }
108
- writeUInt16BE(n) {
109
- this.ensureSize(2);
110
- this.buffer.writeUInt16BE(n, this.offset);
111
- this.offset += 2;
112
- return this;
113
- }
114
- writeUInt32BE(n) {
115
- this.ensureSize(4);
116
- this.buffer.writeUInt32BE(n, this.offset);
117
- this.offset += 4;
118
- return this;
119
- }
120
- writeInt16BE(n) {
121
- this.ensureSize(2);
122
- this.buffer.writeInt16BE(n, this.offset);
123
- this.offset += 2;
124
- return this;
125
- }
126
- writeInt32BE(n) {
127
- this.ensureSize(4);
128
- this.buffer.writeInt32BE(n, this.offset);
129
- this.offset += 4;
130
- return this;
131
- }
132
- writeBigInt64BE(n) {
133
- n = typeof n === 'bigint' ? n : BigInt(n);
134
- this.ensureSize(8);
135
- if (typeof this.buffer.writeBigInt64BE === 'function')
136
- this.buffer.writeBigInt64BE(n, this.offset);
137
- else
138
- writeBigUInt64BE(this.buffer, n, this.offset);
139
- this.offset += 8;
140
- return this;
141
- }
142
- writeFloatBE(n) {
143
- this.ensureSize(4);
144
- this.buffer.writeFloatBE(n, this.offset);
145
- this.offset += 4;
146
- return this;
147
- }
148
- writeDoubleBE(n) {
149
- this.ensureSize(8);
150
- this.buffer.writeDoubleBE(n, this.offset);
151
- this.offset += 8;
152
- return this;
153
- }
154
- writeBuffer(buffer) {
155
- this.ensureSize(buffer.length);
156
- buffer.copy(this.buffer, this.offset, 0, buffer.length);
157
- this.offset += buffer.length;
158
- return this;
159
- }
160
- ensureSize(len) {
161
- const n = this.offset + len - this.length;
162
- if (n > 0)
163
- this.growSize(n);
164
- return this;
165
- }
166
- _houseKeep() {
167
- const needSize = this._stMaxPages * this.pageSize;
168
- if (this.buffer.length > needSize)
169
- this.buffer = Buffer.allocUnsafe(needSize);
170
- this._stMaxPages = this.length ? Math.ceil(this.length / this.pageSize) : 1;
24
+ if (str == null)
25
+ return this.writeInt32BE(-1);
26
+ const len = Buffer.byteLength(str, encoding);
27
+ return this.writeInt32BE(len) + this.writeString(str, encoding);
171
28
  }
172
29
  }
package/types.d.ts CHANGED
@@ -5,7 +5,19 @@ export type Maybe<T> = T | undefined;
5
5
  export type Nullable<T> = T | null;
6
6
  export type Row = any;
7
7
  export type Callback = (err?: Error | null) => void;
8
- export type DecodeBinaryFunction = (buf: Buffer, offset: number, options: DataMappingOptions & Record<string, any>) => any;
8
+ /**
9
+ * What a DataType's own `decodeBinary` implements. `buf` is a shared
10
+ * buffer - the whole row, or the whole array - and this value occupies
11
+ * exactly `len` bytes starting at `offset`.
12
+ *
13
+ * Honouring `len` is the implementation's responsibility, and reading past
14
+ * it is not a bounds error: it silently returns the next column's or
15
+ * element's bytes. A type whose binary form says how long it is - a fixed
16
+ * width like int4's four bytes, or a count it carries itself like
17
+ * numeric's - can ignore `len`; anything that would otherwise read "to the
18
+ * end of the buffer" must not.
19
+ */
20
+ export type DecodeBinaryFunction = (buf: Buffer, offset: number, len: number, options: DataMappingOptions & Record<string, any>) => any;
9
21
  export type DecodeTextBufferFunction = (buf: Buffer, offset: number, len: number, options: DataMappingOptions & Record<string, any>) => any;
10
22
  export type EncodeBinaryFunction = (buf: SmartBuffer, v: any, options: DataMappingOptions) => void;
11
23
  export type EncodeCalculateDimFunction = (v: any[]) => number[];
@@ -1,2 +1 @@
1
1
  export declare function readBigInt64BE(buf: Buffer, offset?: number): bigint;
2
- export declare function writeBigUInt64BE(buf: Buffer, value: BigInt, offset?: number): number;
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable */
2
2
  const big0 = BigInt(0);
3
- const beAnd = BigInt('0xffffffff');
4
3
  const big32 = BigInt(32);
5
4
  // https://github.com/nodejs/node/blob/v13.9.0/lib/internal/buffer.js
6
5
  export function readBigInt64BE(buf, offset = 0) {
@@ -18,24 +17,3 @@ export function readBigInt64BE(buf, offset = 0) {
18
17
  buf[++offset] * 2 ** 8 +
19
18
  last));
20
19
  }
21
- export function writeBigUInt64BE(buf, value, offset = 0) {
22
- // @ts-ignore
23
- let lo = Number(value & beAnd);
24
- buf[offset + 7] = lo;
25
- lo = lo >> 8;
26
- buf[offset + 6] = lo;
27
- lo = lo >> 8;
28
- buf[offset + 5] = lo;
29
- lo = lo >> 8;
30
- buf[offset + 4] = lo;
31
- // @ts-ignore
32
- let hi = Number((value >> big32) & beAnd);
33
- buf[offset + 3] = hi;
34
- hi = hi >> 8;
35
- buf[offset + 2] = hi;
36
- hi = hi >> 8;
37
- buf[offset + 1] = hi;
38
- hi = hi >> 8;
39
- buf[offset] = hi;
40
- return offset + 8;
41
- }
@@ -1,3 +1,3 @@
1
1
  import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
2
2
  import type { DecodeBinaryFunction, Nullable } from '../types.js';
3
- export declare function decodeBinaryArray<T = any>(buf: Buffer, offset: number, decoder: DecodeBinaryFunction, options?: DataMappingOptions, fixedBinarySize?: number): Nullable<T[]>;
3
+ export declare function decodeBinaryArray<T = any>(buf: Buffer, offset: number, decoder: DecodeBinaryFunction, options?: DataMappingOptions): Nullable<T[]>;
@@ -1,9 +1,9 @@
1
1
  import { BufferReader } from '../protocol/buffer-reader.js';
2
- export function decodeBinaryArray(buf, offset, decoder, options = {}, fixedBinarySize) {
2
+ export function decodeBinaryArray(buf, offset, decoder, options = {}) {
3
3
  if (!buf.length)
4
4
  return null;
5
5
  const io = new BufferReader(buf);
6
- io.offset = offset;
6
+ io.position = offset;
7
7
  const ndims = io.readInt32BE();
8
8
  io.readInt32BE(); // hasNulls
9
9
  const elementOID = io.readInt32BE(); // element oid
@@ -26,12 +26,12 @@ export function decodeBinaryArray(buf, offset, decoder, options = {}, fixedBinar
26
26
  len = io.readInt32BE();
27
27
  if (len === -1)
28
28
  target[i] = null;
29
- else if (fixedBinarySize != null) {
30
- target[i] = decoder(buf, io.offset, elementOptions);
31
- io.offset += len;
32
- }
33
29
  else {
34
- target[i] = decoder(io.readBuffer(len), 0, elementOptions);
30
+ // Every element carries its own length on the wire, so the decoder
31
+ // can be pointed straight at it - no bounded slice per element,
32
+ // whether or not the element type has a fixed binary size.
33
+ target[i] = decoder(buf, io.position, len, elementOptions);
34
+ io.position += len;
35
35
  }
36
36
  }
37
37
  return target;
@@ -5,10 +5,10 @@ export function encodeBinaryArray(io, value, itemOid, options, encode, encodeCal
5
5
  itemOid = itemOid || DataTypeOIDs.varchar;
6
6
  const dim = encodeCalculateDimFn(value);
7
7
  const ndims = dim.length;
8
- const zeroOffset = io.offset;
9
- io.writeInt32BE(ndims) // Number of dimensions
10
- .writeInt32BE(0) // reserved for has-null flag
11
- .writeInt32BE(itemOid);
8
+ const zeroOffset = io.position;
9
+ io.writeInt32BE(ndims); // Number of dimensions
10
+ io.writeInt32BE(0); // reserved for has-null flag
11
+ io.writeInt32BE(itemOid);
12
12
  let d;
13
13
  for (d = 0; d < ndims; d++) {
14
14
  io.writeInt32BE(dim[d]); // Number of items in dimension
@@ -33,10 +33,10 @@ export function encodeBinaryArray(io, value, itemOid, options, encode, encodeCal
33
33
  continue;
34
34
  }
35
35
  io.writeInt32BE(0); // reserved for data len
36
- pos = io.offset;
36
+ pos = io.position;
37
37
  encode(io, arr[i], options);
38
38
  // Update item data size
39
- io.buffer.writeInt32BE(io.length - pos, pos - 4);
39
+ io.buffer.writeInt32BE(io.size - pos, pos - 4);
40
40
  }
41
41
  };
42
42
  writeDim(value, 0);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Decodes a decimal number straight out of `buf`, without materialising the
3
+ * intermediate string `parseFloat(buf.toString(...))` needs - measured at
4
+ * over half the cost of decoding a float column.
5
+ *
6
+ * Only takes that shortcut when the result is provably identical to what
7
+ * parseFloat() would return: when the mantissa's digits fit exactly in a
8
+ * double and the decimal exponent is small enough that its power of ten
9
+ * does too, a single multiply (or divide) of two exact values is by
10
+ * definition correctly rounded. Everything else - more digits than that,
11
+ * a large exponent, `NaN`/`Infinity`, a leading `+`, whitespace, anything
12
+ * malformed - falls through to parseFloat() itself, so those values are
13
+ * decoded exactly as before.
14
+ */
15
+ export declare function fastParseFloatBuffer(buf: Buffer, offset?: number, len?: number): number;
@@ -0,0 +1,141 @@
1
+ // 10^0 .. 10^22 - every one of these is exactly representable as a double,
2
+ // which is what makes the single multiply/divide below correctly rounded.
3
+ // 10^23 is the first that isn't, so the table stops here deliberately.
4
+ const POW10 = [
5
+ 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,
6
+ 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
7
+ ];
8
+ const MAX_EXACT_EXPONENT = 22;
9
+ // 10^15 - 1 still fits well inside 2^53, so a mantissa of at most this many
10
+ // digits is carried exactly.
11
+ const MAX_EXACT_DIGITS = 15;
12
+ /**
13
+ * Decodes a decimal number straight out of `buf`, without materialising the
14
+ * intermediate string `parseFloat(buf.toString(...))` needs - measured at
15
+ * over half the cost of decoding a float column.
16
+ *
17
+ * Only takes that shortcut when the result is provably identical to what
18
+ * parseFloat() would return: when the mantissa's digits fit exactly in a
19
+ * double and the decimal exponent is small enough that its power of ten
20
+ * does too, a single multiply (or divide) of two exact values is by
21
+ * definition correctly rounded. Everything else - more digits than that,
22
+ * a large exponent, `NaN`/`Infinity`, a leading `+`, whitespace, anything
23
+ * malformed - falls through to parseFloat() itself, so those values are
24
+ * decoded exactly as before.
25
+ */
26
+ export function fastParseFloatBuffer(buf, offset = 0, len = buf.length - offset) {
27
+ const end = offset + len;
28
+ // The longest value the exact path can possibly take is a sign, 15
29
+ // mantissa digits, a decimal point and a two-digit exponent with its own
30
+ // sign. Anything longer is going to parseFloat() regardless, so check
31
+ // that up front rather than after scanning digits that get discarded.
32
+ if (len > 21)
33
+ return parseFloat(buf.toString('latin1', offset, end));
34
+ let i = offset;
35
+ let negative = false;
36
+ if (i < end && buf[i] === 45 /* - */) {
37
+ negative = true;
38
+ i++;
39
+ }
40
+ let mantissa = 0;
41
+ let digits = 0;
42
+ let fractionDigits = 0;
43
+ let seenDot = false;
44
+ let c;
45
+ while (i < end) {
46
+ c = buf[i];
47
+ if (c >= 48 && c <= 57) {
48
+ // Bailing out the moment the mantissa outgrows what a double holds
49
+ // exactly, rather than after the loop: a long numeric's digits would
50
+ // otherwise all be scanned before being thrown away.
51
+ if (++digits > MAX_EXACT_DIGITS)
52
+ return parseFloat(buf.toString('latin1', offset, end));
53
+ mantissa = mantissa * 10 + (c - 48);
54
+ if (seenDot)
55
+ fractionDigits++;
56
+ }
57
+ else if (c === 46 /* . */ && !seenDot) {
58
+ seenDot = true;
59
+ }
60
+ else
61
+ break;
62
+ i++;
63
+ }
64
+ if (!digits)
65
+ return parseFloat(buf.toString('latin1', offset, end));
66
+ let exponent = 0;
67
+ if (i < end && ((c = buf[i]) === 101 /* e */ || c === 69) /* E */) {
68
+ i++;
69
+ let expNegative = false;
70
+ if (i < end) {
71
+ c = buf[i];
72
+ if (c === 45 /* - */) {
73
+ expNegative = true;
74
+ i++;
75
+ }
76
+ else if (c === 43 /* + */)
77
+ i++;
78
+ }
79
+ let expDigits = 0;
80
+ let value = 0;
81
+ while (i < end) {
82
+ c = buf[i];
83
+ if (c < 48 || c > 57)
84
+ break;
85
+ value = value * 10 + (c - 48);
86
+ // Pinned at a value far outside the exact range instead of being
87
+ // allowed to grow without bound. It must never be *truncated* toward
88
+ // a plausible one: an exponent quietly cut from 234 to 23 would be
89
+ // taken as exact and decode to a wrong number rather than falling
90
+ // back. Since the fractional digits that offset it number at most
91
+ // MAX_EXACT_DIGITS, nothing this large can come back into range.
92
+ if (value > 1000)
93
+ value = 1000;
94
+ expDigits++;
95
+ i++;
96
+ }
97
+ if (!expDigits)
98
+ return parseFloat(buf.toString('latin1', offset, end));
99
+ exponent = expNegative ? -value : value;
100
+ }
101
+ // The `| 0` is not redundant: without it this is -0 whenever there is
102
+ // nothing to subtract (any value with no fractional digits, e.g. "42").
103
+ // A -0 index can't be a small integer, so POW10[exponent] below misses
104
+ // V8's fast element access and takes a generic property lookup instead -
105
+ // and the site stays generic afterwards, slowing down every later value
106
+ // too, not just the one that poisoned it.
107
+ exponent = (exponent - fractionDigits) | 0;
108
+ // Trailing characters mean this wasn't a plain decimal number after all.
109
+ if (i !== end)
110
+ return parseFloat(buf.toString('latin1', offset, end));
111
+ let n;
112
+ if (exponent >= 0) {
113
+ if (exponent > MAX_EXACT_EXPONENT) {
114
+ // Too large a power of ten to be exact on its own, but the surplus
115
+ // can often be folded into the mantissa instead: if the mantissa is
116
+ // small enough that scaling it up stays a whole number a double
117
+ // holds exactly, both operands of the multiply below are still
118
+ // exact, so its single rounding is still the correct one. This is
119
+ // what keeps values like 1.2345678e+30 (a mantissa of only eight
120
+ // digits) on the exact path.
121
+ const shift = exponent - MAX_EXACT_EXPONENT;
122
+ if (shift > MAX_EXACT_EXPONENT)
123
+ return parseFloat(buf.toString('latin1', offset, end));
124
+ const scaled = mantissa * POW10[shift];
125
+ if (scaled > Number.MAX_SAFE_INTEGER)
126
+ return parseFloat(buf.toString('latin1', offset, end));
127
+ mantissa = scaled;
128
+ exponent = MAX_EXACT_EXPONENT;
129
+ }
130
+ n = mantissa * POW10[exponent];
131
+ }
132
+ else {
133
+ if (exponent < -MAX_EXACT_EXPONENT)
134
+ return parseFloat(buf.toString('latin1', offset, end));
135
+ n = mantissa / POW10[-exponent];
136
+ }
137
+ // `0 * -1` is -0, which is the value PostgreSQL's own "-0" has to decode
138
+ // to - so the sign is applied by multiplication rather than negation of
139
+ // a separately built positive result.
140
+ return negative ? n * -1 : n;
141
+ }
@@ -17,16 +17,12 @@ export function getParsers(typeMap, fields) {
17
17
  const decode = dataTypeReg.decodeBinary;
18
18
  if (decode) {
19
19
  if (isArray) {
20
- parsers[i] = (data, offset, len, options) => decodeBinaryArray(data, offset, decode, options, dataTypeReg.fixedBinarySize);
21
- }
22
- else if (dataTypeReg.fixedBinarySize != null) {
23
- const fixedSize = dataTypeReg.fixedBinarySize;
24
- parsers[i] = (data, offset, len, options) => len === fixedSize
25
- ? decode(data, offset, options)
26
- : decode(data.subarray(offset, offset + len), 0, options);
20
+ parsers[i] = (data, offset, len, options) => decodeBinaryArray(data, offset, decode, options);
27
21
  }
28
22
  else {
29
- parsers[i] = (data, offset, len, options) => decode(data.subarray(offset, offset + len), 0, options);
23
+ // Straight through: decodeBinary is told where its value starts
24
+ // and how long it is, so there is nothing to slice first.
25
+ parsers[i] = decode;
30
26
  }
31
27
  }
32
28
  }
@@ -1,5 +1,6 @@
1
1
  // noinspection RegExpUnnecessaryNonCapturingGroup
2
2
  import { fastParseInt } from './fast-parseint.js';
3
+ import { parsePgTimestampString } from './parse-pg-timestamp.js';
3
4
  // noinspection RegExpUnnecessaryNonCapturingGroup
4
5
  const TIMESTAMP_PATTERN = /^(\d{4})-?(0[1-9]|1[012])?-?([123]0|[012][1-9]|31)?(?:[T ]?([01][0-9]|2[0-3]):?([0-5][0-9]):?([0-5][0-9])?(?:\.(\d+))?(?:(Z)|(?:([+-])([01]?[0-9]|2[0-3]):?([0-5][0-9])?))?)?$/;
5
6
  function noMatch(str) {
@@ -45,6 +46,9 @@ export function parseDateTime(str, utc) {
45
46
  return Infinity;
46
47
  if (str === '-infinity')
47
48
  return -Infinity;
49
+ const fast = parsePgTimestampString(str);
50
+ if (fast)
51
+ return fast;
48
52
  const d = new Date(str);
49
53
  if (!isNaN(d.getTime()))
50
54
  return d;
@@ -73,6 +77,9 @@ export function parseDateTimeTz(str, utc) {
73
77
  return Infinity;
74
78
  if (str === '-infinity')
75
79
  return -Infinity;
80
+ const fast = parsePgTimestampString(str);
81
+ if (fast)
82
+ return fast;
76
83
  const native = new Date(str);
77
84
  if (!isNaN(native.getTime()))
78
85
  return native;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Decodes the exact shape PostgreSQL's own ISO output uses -
3
+ * `YYYY-MM-DD HH:MM:SS[.f+][(+|-)HH[:MM]]` - straight out of the wire
4
+ * bytes, instead of building a string and handing that to the engine's
5
+ * general-purpose date parser. `new Date(str)` costs ~150ns per value and
6
+ * dominates decode time on timestamp-heavy result sets; even the string
7
+ * this replaces costs ~43ns to materialise before any parsing starts.
8
+ *
9
+ * Returns undefined for anything that isn't *exactly* that shape, so every
10
+ * other input (compact forms, `T` separators, a `BC` suffix, years outside
11
+ * 1000-9999, LMT-style ±HH:MM:SS offsets) falls through to the original
12
+ * `new Date()`/regex path and keeps its existing result - including the
13
+ * engine's own quirks, which callers may already depend on:
14
+ * `0001-01-01` parsing as year 2001, and `-04:56:02` offsets being
15
+ * rejected outright.
16
+ *
17
+ * A missing offset means local time, matching what `new Date(str)` does
18
+ * with a space-separated, offset-less timestamp.
19
+ */
20
+ export declare function parsePgTimestampBuffer(buf: Buffer, offset: number, end: number): Date | undefined;
21
+ /**
22
+ * The same scan for a value that is already a string - the array-element
23
+ * and string-parameter paths, which never see the wire bytes.
24
+ *
25
+ * Copying into a scratch buffer first still beats scanning the string
26
+ * directly through a second, parallel implementation: the copy costs about
27
+ * half of what materialising the string cost in the first place, and there
28
+ * is only one scanner to keep correct. Safe to share one buffer because
29
+ * the copy and the scan that reads it are a single synchronous step.
30
+ */
31
+ export declare function parsePgTimestampString(str: string): Date | undefined;