postgrejs 2.23.1 → 3.0.1

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 (105) hide show
  1. package/CHANGELOG.md +152 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +152 -13
  5. package/connection/connection.js +296 -49
  6. package/connection/copy-stream.d.ts +88 -0
  7. package/connection/copy-stream.js +244 -0
  8. package/connection/cursor.js +20 -13
  9. package/connection/intl-connection.d.ts +68 -3
  10. package/connection/intl-connection.js +414 -57
  11. package/connection/large-object.d.ts +65 -0
  12. package/connection/large-object.js +151 -0
  13. package/connection/logical-replication.d.ts +121 -0
  14. package/connection/logical-replication.js +279 -0
  15. package/connection/pool.d.ts +61 -4
  16. package/connection/pool.js +177 -29
  17. package/connection/portal.d.ts +18 -4
  18. package/connection/portal.js +53 -59
  19. package/connection/prepared-statement.d.ts +22 -1
  20. package/connection/prepared-statement.js +117 -97
  21. package/data-type-map.js +4 -2
  22. package/data-types/bool-type.js +14 -10
  23. package/data-types/box-type.js +15 -8
  24. package/data-types/bytea-type.js +9 -5
  25. package/data-types/char-type.js +11 -5
  26. package/data-types/circle-type.js +15 -7
  27. package/data-types/date-type.js +25 -19
  28. package/data-types/float4-type.js +10 -3
  29. package/data-types/float8-type.js +12 -3
  30. package/data-types/int2-type.js +9 -4
  31. package/data-types/int2-vector-type.js +9 -4
  32. package/data-types/int4-type.js +11 -5
  33. package/data-types/int8-type.js +22 -7
  34. package/data-types/json-type.js +17 -8
  35. package/data-types/jsonb-type.js +20 -14
  36. package/data-types/lseg-type.js +16 -8
  37. package/data-types/numeric-type.d.ts +8 -0
  38. package/data-types/numeric-type.js +112 -8
  39. package/data-types/oid-type.d.ts +0 -1
  40. package/data-types/oid-type.js +9 -10
  41. package/data-types/oid-vector-type.d.ts +9 -0
  42. package/data-types/oid-vector-type.js +51 -0
  43. package/data-types/point-type.js +14 -6
  44. package/data-types/time-type.js +20 -13
  45. package/data-types/timestamp-type.js +32 -25
  46. package/data-types/timestamptz-type.js +33 -21
  47. package/data-types/uuid-type.js +21 -11
  48. package/data-types/varchar-type.js +16 -4
  49. package/index.d.ts +5 -0
  50. package/index.js +5 -0
  51. package/interfaces/data-type.d.ts +5 -3
  52. package/interfaces/database-connection-params.d.ts +95 -0
  53. package/interfaces/query-options.d.ts +17 -0
  54. package/interfaces/script-execute-options.d.ts +17 -0
  55. package/package.json +2 -3
  56. package/protocol/backend.d.ts +6 -1
  57. package/protocol/backend.js +70 -45
  58. package/protocol/buffer-reader.d.ts +0 -2
  59. package/protocol/buffer-reader.js +3 -10
  60. package/protocol/cert-signature.d.ts +12 -0
  61. package/protocol/cert-signature.js +114 -0
  62. package/protocol/frontend.d.ts +16 -0
  63. package/protocol/frontend.js +57 -6
  64. package/protocol/pg-socket.d.ts +220 -9
  65. package/protocol/pg-socket.js +539 -102
  66. package/protocol/pgoutput.d.ts +76 -0
  67. package/protocol/pgoutput.js +155 -0
  68. package/protocol/protocol.d.ts +3 -4
  69. package/protocol/protocol.js +1 -1
  70. package/protocol/sasl.d.ts +11 -1
  71. package/protocol/sasl.js +35 -4
  72. package/protocol/smart-buffer.d.ts +0 -2
  73. package/protocol/smart-buffer.js +11 -16
  74. package/types.d.ts +5 -3
  75. package/util/abort-signal.d.ts +24 -0
  76. package/util/abort-signal.js +63 -0
  77. package/util/array-calculatedim.js +3 -1
  78. package/util/connection-config.js +77 -3
  79. package/util/decode-binaryarray.d.ts +1 -1
  80. package/util/decode-binaryarray.js +15 -6
  81. package/util/encode-binaryarray.js +7 -3
  82. package/util/escape-identifier.d.ts +14 -0
  83. package/util/escape-identifier.js +32 -0
  84. package/util/escape-literal.js +3 -0
  85. package/util/fast-parseint.d.ts +1 -0
  86. package/util/fast-parseint.js +12 -17
  87. package/util/format-datetime.d.ts +10 -0
  88. package/util/format-datetime.js +71 -0
  89. package/util/get-parsers.js +31 -10
  90. package/util/parse-array.js +2 -2
  91. package/util/parse-bytea.d.ts +2 -0
  92. package/util/parse-bytea.js +83 -0
  93. package/util/parse-datetime.d.ts +3 -1
  94. package/util/parse-datetime.js +86 -17
  95. package/util/parse-row.d.ts +3 -1
  96. package/util/parse-row.js +60 -5
  97. package/util/parse-time.js +8 -1
  98. package/util/sql-tag.d.ts +60 -0
  99. package/util/sql-tag.js +202 -0
  100. package/util/starts-transaction.d.ts +38 -0
  101. package/util/starts-transaction.js +148 -0
  102. package/util/stringify-arrayliteral.js +13 -1
  103. package/util/wrap-row-description.js +13 -4
  104. package/util/convert-row-to-object.d.ts +0 -2
  105. package/util/convert-row-to-object.js +0 -9
@@ -1,28 +1,37 @@
1
1
  import { BufferReader } from '../protocol/buffer-reader.js';
2
- export function decodeBinaryArray(buf, decoder, options = {}) {
2
+ export function decodeBinaryArray(buf, offset, decoder, options = {}, fixedBinarySize) {
3
3
  if (!buf.length)
4
4
  return null;
5
5
  const io = new BufferReader(buf);
6
+ io.offset = offset;
6
7
  const ndims = io.readInt32BE();
7
8
  io.readInt32BE(); // hasNulls
8
9
  const elementOID = io.readInt32BE(); // element oid
9
10
  if (ndims === 0)
10
11
  return [];
11
12
  const dims = [];
13
+ const elementOptions = { ...options, elementOID };
14
+ const lastDim = ndims - 1;
12
15
  const readDim = (level) => {
13
16
  const elemCount = dims[level];
14
17
  const target = new Array(elemCount);
15
- for (let i = 0; i < elemCount; i++) {
16
- if (level < dims.length - 1) {
18
+ const isLeafLevel = level >= lastDim;
19
+ let i;
20
+ let len;
21
+ for (i = 0; i < elemCount; i++) {
22
+ if (!isLeafLevel) {
17
23
  target[i] = readDim(level + 1);
18
24
  continue;
19
25
  }
20
- const len = io.readInt32BE();
26
+ len = io.readInt32BE();
21
27
  if (len === -1)
22
28
  target[i] = null;
29
+ else if (fixedBinarySize != null) {
30
+ target[i] = decoder(buf, io.offset, elementOptions);
31
+ io.offset += len;
32
+ }
23
33
  else {
24
- const b = io.readBuffer(len);
25
- target[i] = decoder(b, { ...options, elementOID });
34
+ target[i] = decoder(io.readBuffer(len), 0, elementOptions);
26
35
  }
27
36
  }
28
37
  return target;
@@ -9,16 +9,20 @@ export function encodeBinaryArray(io, value, itemOid, options, encode, encodeCal
9
9
  io.writeInt32BE(ndims) // Number of dimensions
10
10
  .writeInt32BE(0) // reserved for has-null flag
11
11
  .writeInt32BE(itemOid);
12
- for (let d = 0; d < ndims; d++) {
12
+ let d;
13
+ for (d = 0; d < ndims; d++) {
13
14
  io.writeInt32BE(dim[d]); // Number of items in dimension
14
15
  io.writeInt32BE(0); // LBound always 0.
15
16
  }
16
17
  let hasNull = false;
17
18
  let pos;
19
+ const lastDim = ndims - 1;
18
20
  const writeDim = (arr, level) => {
19
21
  const elemCount = dim[level];
20
- for (let i = 0; i < elemCount; i++) {
21
- if (level < dim.length - 1) {
22
+ const isLeafLevel = level >= lastDim;
23
+ let i;
24
+ for (i = 0; i < elemCount; i++) {
25
+ if (!isLeafLevel) {
22
26
  writeDim(arr && arr[i], level + 1);
23
27
  continue;
24
28
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Quotes a table, column or schema name so it can be written into a
3
+ * statement safely.
4
+ *
5
+ * Identifiers cannot be parameters - `$1` is always a value - so a dynamic
6
+ * name has to go into the SQL text itself, which is exactly where an
7
+ * injection would get in. Quoting is what closes that: the name is wrapped
8
+ * in double quotes and any embedded double quote is doubled, so whatever it
9
+ * contains stays a single identifier.
10
+ *
11
+ * Named after PostgreSQL's own `quote_ident()`. Ported from
12
+ * src/interfaces/libpq/fe-exec.c.
13
+ */
14
+ export declare function escapeIdentifier(str: string): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Quotes a table, column or schema name so it can be written into a
3
+ * statement safely.
4
+ *
5
+ * Identifiers cannot be parameters - `$1` is always a value - so a dynamic
6
+ * name has to go into the SQL text itself, which is exactly where an
7
+ * injection would get in. Quoting is what closes that: the name is wrapped
8
+ * in double quotes and any embedded double quote is doubled, so whatever it
9
+ * contains stays a single identifier.
10
+ *
11
+ * Named after PostgreSQL's own `quote_ident()`. Ported from
12
+ * src/interfaces/libpq/fe-exec.c.
13
+ */
14
+ export function escapeIdentifier(str) {
15
+ // A NUL byte would silently truncate the identifier at the protocol level
16
+ // - same reason escapeLiteral() refuses it.
17
+ if (str.indexOf('\0') >= 0) {
18
+ throw new Error('PostgreSQL identifiers cannot contain NUL (\\0) bytes');
19
+ }
20
+ let out = '"';
21
+ const l = str.length;
22
+ let i;
23
+ let c;
24
+ for (i = 0; i < l; i++) {
25
+ c = str[i];
26
+ if (c === '"')
27
+ out += c + c;
28
+ else
29
+ out += c;
30
+ }
31
+ return out + '"';
32
+ }
@@ -1,5 +1,8 @@
1
1
  // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
2
2
  export function escapeLiteral(str) {
3
+ if (str.indexOf('\0') >= 0) {
4
+ throw new Error('PostgreSQL text values cannot contain NUL (\\0) bytes');
5
+ }
3
6
  let backSlash = false;
4
7
  let out = "'";
5
8
  let i;
@@ -1 +1,2 @@
1
1
  export declare function fastParseInt(str: string | number): number;
2
+ export declare function fastParseIntBuffer(buf: Buffer, offset?: number, len?: number): number;
@@ -1,28 +1,23 @@
1
1
  export function fastParseInt(str) {
2
- /* istanbul ignore next */
2
+ /* c8 ignore next */
3
3
  if (typeof str === 'number')
4
4
  return Math.floor(str);
5
5
  // noinspection SuspiciousTypeOfGuard
6
6
  if (typeof str !== 'string')
7
7
  return NaN;
8
- const strLength = str.length;
9
- let res = 0;
10
- let i = 0;
8
+ return parseInt(str, 10);
9
+ }
10
+ export function fastParseIntBuffer(buf, offset = 0, len = buf.length - offset) {
11
+ const end = offset + len;
12
+ let i = offset;
11
13
  let neg = false;
12
- if (str.startsWith('-')) {
14
+ if (len > 0 && buf[offset] === 45 /* '-' */) {
13
15
  neg = true;
14
16
  i++;
15
17
  }
16
- do {
17
- const charCode = str.charCodeAt(i);
18
- /* istanbul ignore next */
19
- if (charCode === 46)
20
- return res;
21
- /* istanbul ignore next */
22
- if (charCode < 48 || charCode > 57)
23
- return NaN;
24
- res *= 10;
25
- res += charCode - 48;
26
- } while (++i < strLength);
27
- return neg ? -res : res;
18
+ let n = 0;
19
+ for (; i < end; i++) {
20
+ n = n * 10 + (buf[i] - 48);
21
+ }
22
+ return neg ? -n : n;
28
23
  }
@@ -0,0 +1,10 @@
1
+ import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
2
+ export declare function formatDate(v: Date | number | string, options?: DataMappingOptions): string;
3
+ export declare function formatTime(v: Date | number | string, options?: DataMappingOptions): string;
4
+ export declare function formatTimestamp(v: Date | number | string, options?: DataMappingOptions): string;
5
+ /**
6
+ * timestamptz is an absolute instant - its binary encoder uses getTime()
7
+ * with no time zone shift - so this writes UTC with an explicit offset and
8
+ * ignores `utcDates`, which only governs the zone-less types.
9
+ */
10
+ export declare function formatTimestamptz(v: Date | number | string): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Writes a Date as PostgreSQL's own text form, mirroring exactly what the
3
+ * matching encodeBinary() would have sent.
4
+ *
5
+ * That mirroring is the whole point: `date`, `time` and `timestamp` have no
6
+ * time zone, and their binary encoders read the JS Date's UTC components
7
+ * when `utcDates` is set and its local ones otherwise. A text encoder that
8
+ * chose differently would make the same statement mean two different
9
+ * instants depending on whether it went through query() or execute().
10
+ */
11
+ function parts(v, options) {
12
+ return options?.utcDates
13
+ ? {
14
+ year: v.getUTCFullYear(),
15
+ month: v.getUTCMonth() + 1,
16
+ day: v.getUTCDate(),
17
+ hour: v.getUTCHours(),
18
+ minute: v.getUTCMinutes(),
19
+ second: v.getUTCSeconds(),
20
+ ms: v.getUTCMilliseconds(),
21
+ }
22
+ : {
23
+ year: v.getFullYear(),
24
+ month: v.getMonth() + 1,
25
+ day: v.getDate(),
26
+ hour: v.getHours(),
27
+ minute: v.getMinutes(),
28
+ second: v.getSeconds(),
29
+ ms: v.getMilliseconds(),
30
+ };
31
+ }
32
+ const pad = (n, len = 2) => String(n).padStart(len, '0');
33
+ function coerce(v) {
34
+ // Infinity and -Infinity are values PostgreSQL accepts verbatim.
35
+ if (v === Infinity)
36
+ return 'infinity';
37
+ if (v === -Infinity)
38
+ return '-infinity';
39
+ return v instanceof Date ? v : new Date(v);
40
+ }
41
+ export function formatDate(v, options) {
42
+ const d = coerce(v);
43
+ if (typeof d === 'string')
44
+ return d;
45
+ const p = parts(d, options);
46
+ return `${pad(p.year, 4)}-${pad(p.month)}-${pad(p.day)}`;
47
+ }
48
+ export function formatTime(v, options) {
49
+ const d = coerce(v);
50
+ if (typeof d === 'string')
51
+ return d;
52
+ const p = parts(d, options);
53
+ return `${pad(p.hour)}:${pad(p.minute)}:${pad(p.second)}.${pad(p.ms, 3)}`;
54
+ }
55
+ export function formatTimestamp(v, options) {
56
+ const d = coerce(v);
57
+ if (typeof d === 'string')
58
+ return d;
59
+ return `${formatDate(d, options)} ${formatTime(d, options)}`;
60
+ }
61
+ /**
62
+ * timestamptz is an absolute instant - its binary encoder uses getTime()
63
+ * with no time zone shift - so this writes UTC with an explicit offset and
64
+ * ignores `utcDates`, which only governs the zone-less types.
65
+ */
66
+ export function formatTimestamptz(v) {
67
+ const d = coerce(v);
68
+ if (typeof d === 'string')
69
+ return d;
70
+ return d.toISOString().replace('T', ' ').replace('Z', '+00');
71
+ }
@@ -1,7 +1,8 @@
1
1
  import { DataFormat } from '../constants.js';
2
2
  import { decodeBinaryArray } from './decode-binaryarray.js';
3
3
  import { parsePostgresArray } from './parse-array.js';
4
- const DefaultColumnParser = (v) => v;
4
+ const DefaultColumnParser = (data, offset, len) => data.subarray(offset, offset + len);
5
+ const DefaultTextColumnParser = (data, offset, len) => data.toString('utf8', offset, offset + len);
5
6
  export function getParsers(typeMap, fields) {
6
7
  const parsers = new Array(fields.length);
7
8
  const l = fields.length;
@@ -13,26 +14,46 @@ export function getParsers(typeMap, fields) {
13
14
  if (dataTypeReg) {
14
15
  const isArray = !!dataTypeReg.elementsOID;
15
16
  if (f.format === DataFormat.binary) {
16
- const decode = dataTypeReg.parseBinary;
17
+ const decode = dataTypeReg.decodeBinary;
17
18
  if (decode) {
18
- parsers[i] = !isArray
19
- ? decode
20
- : (v, options) => decodeBinaryArray(v, decode, options);
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);
27
+ }
28
+ else {
29
+ parsers[i] = (data, offset, len, options) => decode(data.subarray(offset, offset + len), 0, options);
30
+ }
21
31
  }
22
32
  }
23
33
  else if (f.format === DataFormat.text) {
24
- const parse = dataTypeReg.parseText;
34
+ const parse = dataTypeReg.decodeText;
25
35
  if (parse) {
26
- parsers[i] = !isArray
27
- ? parse
28
- : (v, options) => parsePostgresArray(v, {
36
+ const parseBuffer = dataTypeReg.decodeTextBuffer;
37
+ if (!isArray && parseBuffer) {
38
+ parsers[i] = (data, offset, len, options) => parseBuffer(data, offset, len, options);
39
+ }
40
+ else if (!isArray) {
41
+ parsers[i] = (data, offset, len, options) => parse(data.toString('utf8', offset, offset + len), options);
42
+ }
43
+ else {
44
+ parsers[i] = (data, offset, len, options) => parsePostgresArray(data.toString('utf8', offset, offset + len), {
29
45
  transform: x => parse(x, options),
30
46
  separator: dataTypeReg.arraySeparator,
31
47
  });
48
+ }
32
49
  }
33
50
  }
34
51
  }
35
- parsers[i] = parsers[i] || DefaultColumnParser;
52
+ parsers[i] =
53
+ parsers[i] ||
54
+ (f.format === DataFormat.text
55
+ ? DefaultTextColumnParser
56
+ : DefaultColumnParser);
36
57
  }
37
58
  return parsers;
38
59
  }
@@ -39,8 +39,8 @@ export function parsePostgresArray(s, opts) {
39
39
  token += c;
40
40
  continue;
41
41
  }
42
- if (c === '"' || c === "'") {
43
- if (quote && quote === c) {
42
+ if (c === '"') {
43
+ if (quote) {
44
44
  quote = '';
45
45
  }
46
46
  else {
@@ -0,0 +1,2 @@
1
+ export declare function parseBytea(v: string): Buffer;
2
+ export declare function parseByteaBuffer(buf: Buffer, offset: number, len: number): Buffer;
@@ -0,0 +1,83 @@
1
+ // Escape-format decode ported from the `postgres-bytea` npm package
2
+ // (MIT License, Copyright (c) Ben Drucker <bvdrucker@gmail.com>) so the
3
+ // hex fast path below doesn't need an external dependency for the rare
4
+ // legacy fallback. bytea_output has defaulted to "hex" since PostgreSQL
5
+ // 9.0 (2010); "escape" format is legacy and unlikely to appear in
6
+ // practice, but PostgreSQL still supports emitting it.
7
+ function parseByteaEscape(input) {
8
+ let output = '';
9
+ let i = 0;
10
+ const l = input.length;
11
+ while (i < l) {
12
+ if (input[i] !== '\\') {
13
+ output += input[i];
14
+ i++;
15
+ }
16
+ else if (/[0-7]{3}/.test(input.substring(i + 1, i + 4))) {
17
+ output += String.fromCharCode(parseInt(input.substring(i + 1, i + 4), 8));
18
+ i += 4;
19
+ }
20
+ else {
21
+ let backslashes = 1;
22
+ while (i + backslashes < l && input[i + backslashes] === '\\') {
23
+ backslashes++;
24
+ }
25
+ const u = Math.floor(backslashes / 2);
26
+ let k;
27
+ for (k = 0; k < u; k++)
28
+ output += '\\';
29
+ // An odd backslash count (a lone unescaped '\' the loop above
30
+ // couldn't pair up) advances 0 via the halving above - without the
31
+ // `|| 1` fallback, `i` never moves and this loops forever on the
32
+ // same position. Malformed/unexpected input for this legacy format,
33
+ // but must still make forward progress rather than hang.
34
+ i += u * 2 || 1;
35
+ }
36
+ }
37
+ return Buffer.from(output, 'binary');
38
+ }
39
+ export function parseBytea(v) {
40
+ // PostgreSQL's default bytea_output is "hex" (`\x`-prefixed) - check the
41
+ // two marker chars directly rather than a regex test, and hex-decode
42
+ // inline; this is the path almost every real value takes.
43
+ if (v.charCodeAt(0) === 92 /* '\' */ && v.charCodeAt(1) === 120 /* 'x' */)
44
+ return Buffer.from(v.slice(2), 'hex');
45
+ return parseByteaEscape(v);
46
+ }
47
+ // 256-entry lookup: ASCII byte -> hex nibble value (0-15), -1 if not a hex
48
+ // digit. Built once at module load.
49
+ const HEX_NIBBLE = (() => {
50
+ const t = new Int8Array(256).fill(-1);
51
+ for (let i = 0; i <= 9; i++)
52
+ t[48 + i] = i; // '0'-'9'
53
+ for (let i = 0; i <= 5; i++)
54
+ t[97 + i] = 10 + i; // 'a'-'f'
55
+ for (let i = 0; i <= 5; i++)
56
+ t[65 + i] = 10 + i; // 'A'-'F'
57
+ return t;
58
+ })();
59
+ // Parses straight from the raw wire bytes for the `\x`-prefixed hex fast
60
+ // path, skipping both the buffer->string conversion AND the intermediate
61
+ // hex-string Buffer.from(str,'hex') parse that parseBytea's own hex path
62
+ // still needs - bytea values are typically the largest column values in a
63
+ // row, so this is the single biggest per-column allocation avoided of any
64
+ // type in this fast-path set. Falls back to the (rare, legacy) escape
65
+ // format via a single 'latin1' string conversion - its own grammar (\ddd
66
+ // octal triplets, ASCII digits, or a literal backslash) is pure ASCII, same
67
+ // as the hex path, so 'latin1' is safe there too.
68
+ export function parseByteaBuffer(buf, offset, len) {
69
+ if (buf[offset] === 0x5c /* '\' */ && buf[offset + 1] === 0x78 /* 'x' */) {
70
+ // Truncate a trailing odd nibble instead of throwing, matching
71
+ // Buffer.from(hexString, 'hex')'s own silent-truncate behavior today.
72
+ const outLen = (len - 2) >> 1;
73
+ const out = Buffer.allocUnsafe(outLen);
74
+ const start = offset + 2;
75
+ for (let i = 0; i < outLen; i++) {
76
+ out[i] =
77
+ (HEX_NIBBLE[buf[start + i * 2]] << 4) |
78
+ HEX_NIBBLE[buf[start + i * 2 + 1]];
79
+ }
80
+ return out;
81
+ }
82
+ return parseByteaEscape(buf.toString('latin1', offset, offset + len));
83
+ }
@@ -1 +1,3 @@
1
- export declare function parseDateTime(str: string, parseTime?: boolean, parseTimeZone?: boolean, utc?: boolean): Date | number;
1
+ export declare function parseDate(str: string, utc?: boolean): Date | number;
2
+ export declare function parseDateTime(str: string, utc?: boolean): Date | number;
3
+ export declare function parseDateTimeTz(str: string, utc?: boolean): Date | number;
@@ -2,27 +2,96 @@
2
2
  import { fastParseInt } from './fast-parseint.js';
3
3
  // noinspection RegExpUnnecessaryNonCapturingGroup
4
4
  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
- const INFINITY_PATTERN = /^-?infinity$/;
6
- export function parseDateTime(str, parseTime, parseTimeZone, utc) {
7
- let m = str.match(TIMESTAMP_PATTERN);
8
- if (!m) {
9
- m = str.match(INFINITY_PATTERN);
10
- if (m)
11
- return Number(str.replace('i', 'I'));
12
- return new Date('invalid');
13
- }
14
- const args = [
15
- 1970, 0, 1, 0, 0, 0, 0,
16
- ];
17
- const l = parseTime ? 7 : 3;
18
- for (let i = 0; i < l; i++) {
5
+ function noMatch(str) {
6
+ if (str === 'infinity' || str === 'Infinity')
7
+ return Infinity;
8
+ if (str === '-infinity' || str === '-Infinity')
9
+ return -Infinity;
10
+ return new Date('invalid');
11
+ }
12
+ function fillTimeArgs(m, args) {
13
+ for (let i = 3; i < 7; i++) {
19
14
  const s = m[i + 1];
20
- args[i] = fastParseInt(s) || 0;
15
+ if (i === 6 && s) {
16
+ args[i] = fastParseInt((s + '000').slice(0, 3));
17
+ }
18
+ else {
19
+ args[i] = fastParseInt(s) || 0;
20
+ }
21
+ }
22
+ }
23
+ export function parseDate(str, utc) {
24
+ const m = str.match(TIMESTAMP_PATTERN);
25
+ if (!m)
26
+ return noMatch(str);
27
+ const args = [1970, 0, 1, 0, 0, 0, 0];
28
+ args[0] = fastParseInt(m[1]) || 0;
29
+ args[1] = fastParseInt(m[2]) || 0;
30
+ // Regression test: day defaults to 1 when omitted (a bare year or
31
+ // year-month string, both valid per TIMESTAMP_PATTERN) - `|| 0` used to
32
+ // leave it at 0, and Date.UTC()'s day is 1-indexed, so "day 0" silently
33
+ // rolled back to the last day of the *previous* month.
34
+ args[2] = fastParseInt(m[3]) || 1;
35
+ // Months start from 0
36
+ if (args[1] > 0)
37
+ args[1]--;
38
+ if (m[8] || utc)
39
+ return new Date(Date.UTC(...args));
40
+ return new Date(...args);
41
+ }
42
+ export function parseDateTime(str, utc) {
43
+ if (!utc) {
44
+ if (str === 'infinity')
45
+ return Infinity;
46
+ if (str === '-infinity')
47
+ return -Infinity;
48
+ const d = new Date(str);
49
+ if (!isNaN(d.getTime()))
50
+ return d;
21
51
  }
22
- // Months starts from 0
52
+ const m = str.match(TIMESTAMP_PATTERN);
53
+ if (!m)
54
+ return noMatch(str);
55
+ const args = [1970, 0, 1, 0, 0, 0, 0];
56
+ args[0] = fastParseInt(m[1]) || 0;
57
+ args[1] = fastParseInt(m[2]) || 0;
58
+ // Regression test: day defaults to 1 when omitted (a bare year or
59
+ // year-month string, both valid per TIMESTAMP_PATTERN) - `|| 0` used to
60
+ // leave it at 0, and Date.UTC()'s day is 1-indexed, so "day 0" silently
61
+ // rolled back to the last day of the *previous* month.
62
+ args[2] = fastParseInt(m[3]) || 1;
63
+ // Months start from 0
64
+ if (args[1] > 0)
65
+ args[1]--;
66
+ fillTimeArgs(m, args);
67
+ if (m[8] || utc)
68
+ return new Date(Date.UTC(...args));
69
+ return new Date(...args);
70
+ }
71
+ export function parseDateTimeTz(str, utc) {
72
+ if (str === 'infinity')
73
+ return Infinity;
74
+ if (str === '-infinity')
75
+ return -Infinity;
76
+ const native = new Date(str);
77
+ if (!isNaN(native.getTime()))
78
+ return native;
79
+ const m = str.match(TIMESTAMP_PATTERN);
80
+ if (!m)
81
+ return noMatch(str);
82
+ const args = [1970, 0, 1, 0, 0, 0, 0];
83
+ args[0] = fastParseInt(m[1]) || 0;
84
+ args[1] = fastParseInt(m[2]) || 0;
85
+ // Regression test: day defaults to 1 when omitted (a bare year or
86
+ // year-month string, both valid per TIMESTAMP_PATTERN) - `|| 0` used to
87
+ // leave it at 0, and Date.UTC()'s day is 1-indexed, so "day 0" silently
88
+ // rolled back to the last day of the *previous* month.
89
+ args[2] = fastParseInt(m[3]) || 1;
90
+ // Months start from 0
23
91
  if (args[1] > 0)
24
92
  args[1]--;
25
- if (parseTimeZone && parseTime && m[9]) {
93
+ fillTimeArgs(m, args);
94
+ if (m[9]) {
26
95
  const r = m[9] === '-' ? -1 : 1;
27
96
  args[3] -= (fastParseInt(m[10]) || 0) * r;
28
97
  args[4] -= (fastParseInt(m[11]) || 0) * r;
@@ -1,3 +1,5 @@
1
1
  import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
2
+ import type { FieldInfo } from '../interfaces/field-info.js';
2
3
  import type { AnyParseFunction } from '../types.js';
3
- export declare function parseRow(parsers: AnyParseFunction[], row: any[], options: DataMappingOptions): void;
4
+ export declare function parseRow(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions): any[];
5
+ export declare function parseObjectRow(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): object;
package/util/parse-row.js CHANGED
@@ -1,8 +1,63 @@
1
- export function parseRow(parsers, row, options) {
2
- const l = row.length;
1
+ export function parseRow(parsers, data, columnCount, options) {
2
+ const row = new Array(columnCount);
3
+ let offset = 0;
4
+ let len;
3
5
  let i;
4
- for (i = 0; i < l; i++) {
5
- row[i] =
6
- row[i] == null ? null : parsers[i].call(undefined, row[i], options);
6
+ for (i = 0; i < columnCount; i++) {
7
+ // The length of the column value, in bytes (this count does not
8
+ // include itself). Can be zero. As a special case, -1 indicates a
9
+ // NULL column value - no value bytes follow in that case.
10
+ len = data.readInt32BE(offset);
11
+ offset += 4;
12
+ if (len < 0) {
13
+ row[i] = null;
14
+ }
15
+ else {
16
+ // None of the parsers use `this`, so a direct call avoids .call()'s
17
+ // extra indirection (measured ~5.7x slower than invoking directly).
18
+ row[i] = parsers[i](data, offset, len, options);
19
+ offset += len;
20
+ }
7
21
  }
22
+ return row;
23
+ }
24
+ export function parseObjectRow(parsers, data, columnCount, options, fields) {
25
+ const row = {};
26
+ let offset = 0;
27
+ let len;
28
+ let i;
29
+ let value;
30
+ for (i = 0; i < columnCount; i++) {
31
+ // The length of the column value, in bytes (this count does not
32
+ // include itself). Can be zero. As a special case, -1 indicates a
33
+ // NULL column value - no value bytes follow in that case.
34
+ len = data.readInt32BE(offset);
35
+ offset += 4;
36
+ if (len < 0) {
37
+ value = null;
38
+ }
39
+ else {
40
+ // None of the parsers use `this`, so a direct call avoids .call()'s
41
+ // extra indirection (measured ~5.7x slower than invoking directly).
42
+ value = parsers[i](data, offset, len, options);
43
+ offset += len;
44
+ }
45
+ const name = fields[i].fieldName;
46
+ if (name === '__proto__') {
47
+ // `value`, not row[i] - nothing is ever assigned by numeric index
48
+ // here (row is a plain object keyed by field name), so reading row[i]
49
+ // handed defineProperty an undefined and silently dropped the
50
+ // column's actual value.
51
+ Object.defineProperty(row, name, {
52
+ value,
53
+ writable: true,
54
+ enumerable: true,
55
+ configurable: true,
56
+ });
57
+ }
58
+ else {
59
+ row[name] = value;
60
+ }
61
+ }
62
+ return row;
8
63
  }
@@ -14,8 +14,15 @@ export function parseTime(str, parseTimeZone, utc) {
14
14
  const s = m[i];
15
15
  args[i + 2] = fastParseInt(s) || 0;
16
16
  }
17
+ // Fractional seconds (m[4]) were never read here at all - pad/truncate to
18
+ // exactly 3 digits, same reasoning as parse-datetime.ts's equivalent fix.
19
+ if (m[4])
20
+ args[6] = fastParseInt((m[4] + '000').slice(0, 3));
17
21
  if (parseTimeZone && m[6]) {
18
- const r = m[9] === '-' ? -1 : 1;
22
+ // m[6] is the sign group itself ('+'/'-') - there is no m[9] in this
23
+ // pattern (only 8 capture groups), so the old `m[9] === '-'` check was
24
+ // always false, silently treating every offset as positive.
25
+ const r = m[6] === '-' ? -1 : 1;
19
26
  args[3] -= (fastParseInt(m[7]) || 0) * r;
20
27
  args[4] -= (fastParseInt(m[8]) || 0) * r;
21
28
  return new Date(Date.UTC(...args));