postgrejs 2.23.0 → 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.
- package/CHANGELOG.md +162 -1
- package/LICENSE +26 -17
- package/README.md +195 -175
- package/connection/connection.d.ts +152 -13
- package/connection/connection.js +296 -49
- package/connection/copy-stream.d.ts +88 -0
- package/connection/copy-stream.js +244 -0
- package/connection/cursor.js +20 -13
- package/connection/intl-connection.d.ts +68 -3
- package/connection/intl-connection.js +414 -57
- package/connection/large-object.d.ts +65 -0
- package/connection/large-object.js +151 -0
- package/connection/logical-replication.d.ts +121 -0
- package/connection/logical-replication.js +279 -0
- package/connection/pool.d.ts +61 -4
- package/connection/pool.js +177 -29
- package/connection/portal.d.ts +18 -4
- package/connection/portal.js +53 -59
- package/connection/prepared-statement.d.ts +22 -1
- package/connection/prepared-statement.js +117 -97
- package/data-type-map.d.ts +2 -2
- package/data-type-map.js +4 -2
- package/data-types/bool-type.js +14 -10
- package/data-types/box-type.js +15 -8
- package/data-types/bytea-type.js +9 -5
- package/data-types/char-type.js +11 -5
- package/data-types/circle-type.js +15 -7
- package/data-types/date-type.js +25 -19
- package/data-types/float4-type.js +10 -3
- package/data-types/float8-type.js +12 -3
- package/data-types/int2-type.js +9 -4
- package/data-types/int2-vector-type.js +10 -4
- package/data-types/int4-type.js +11 -5
- package/data-types/int8-type.js +22 -7
- package/data-types/json-type.js +17 -8
- package/data-types/jsonb-type.js +20 -14
- package/data-types/lseg-type.js +16 -8
- package/data-types/numeric-type.d.ts +8 -0
- package/data-types/numeric-type.js +112 -8
- package/data-types/oid-type.d.ts +0 -1
- package/data-types/oid-type.js +9 -10
- package/data-types/oid-vector-type.d.ts +9 -0
- package/data-types/oid-vector-type.js +51 -0
- package/data-types/point-type.js +14 -6
- package/data-types/time-type.js +20 -13
- package/data-types/timestamp-type.js +32 -25
- package/data-types/timestamptz-type.d.ts +1 -1
- package/data-types/timestamptz-type.js +34 -21
- package/data-types/uuid-type.js +21 -11
- package/data-types/varchar-type.js +16 -4
- package/index.d.ts +5 -0
- package/index.js +5 -0
- package/interfaces/data-type.d.ts +5 -3
- package/interfaces/database-connection-params.d.ts +95 -0
- package/interfaces/query-options.d.ts +17 -0
- package/interfaces/script-execute-options.d.ts +17 -0
- package/package.json +3 -4
- package/protocol/backend.d.ts +6 -1
- package/protocol/backend.js +70 -45
- package/protocol/buffer-reader.d.ts +0 -2
- package/protocol/buffer-reader.js +3 -10
- package/protocol/cert-signature.d.ts +12 -0
- package/protocol/cert-signature.js +114 -0
- package/protocol/database-error.js +1 -0
- package/protocol/frontend.d.ts +16 -0
- package/protocol/frontend.js +58 -6
- package/protocol/pg-socket.d.ts +221 -10
- package/protocol/pg-socket.js +539 -102
- package/protocol/pgoutput.d.ts +76 -0
- package/protocol/pgoutput.js +155 -0
- package/protocol/protocol.d.ts +3 -4
- package/protocol/protocol.js +1 -1
- package/protocol/sasl.d.ts +11 -1
- package/protocol/sasl.js +35 -4
- package/protocol/smart-buffer.d.ts +0 -2
- package/protocol/smart-buffer.js +11 -16
- package/types.d.ts +5 -3
- package/util/abort-signal.d.ts +24 -0
- package/util/abort-signal.js +63 -0
- package/util/array-calculatedim.js +3 -1
- package/util/bigint-methods.js +1 -1
- package/util/connection-config.js +77 -3
- package/util/decode-binaryarray.d.ts +1 -1
- package/util/decode-binaryarray.js +15 -6
- package/util/encode-binaryarray.js +7 -3
- package/util/escape-identifier.d.ts +14 -0
- package/util/escape-identifier.js +32 -0
- package/util/escape-literal.js +3 -0
- package/util/fast-parseint.d.ts +1 -0
- package/util/fast-parseint.js +12 -17
- package/util/format-datetime.d.ts +10 -0
- package/util/format-datetime.js +71 -0
- package/util/get-parsers.js +31 -10
- package/util/parse-array.js +2 -2
- package/util/parse-bytea.d.ts +2 -0
- package/util/parse-bytea.js +83 -0
- package/util/parse-datetime.d.ts +3 -1
- package/util/parse-datetime.js +86 -17
- package/util/parse-row.d.ts +3 -1
- package/util/parse-row.js +60 -5
- package/util/parse-time.js +8 -1
- package/util/sql-tag.d.ts +60 -0
- package/util/sql-tag.js +202 -0
- package/util/starts-transaction.d.ts +38 -0
- package/util/starts-transaction.js +148 -0
- package/util/stringify-arrayliteral.js +13 -1
- package/util/wrap-row-description.js +13 -4
- package/util/convert-row-to-object.d.ts +0 -2
- 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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
+
}
|
package/util/escape-literal.js
CHANGED
|
@@ -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;
|
package/util/fast-parseint.d.ts
CHANGED
package/util/fast-parseint.js
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
export function fastParseInt(str) {
|
|
2
|
-
/*
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 (
|
|
14
|
+
if (len > 0 && buf[offset] === 45 /* '-' */) {
|
|
13
15
|
neg = true;
|
|
14
16
|
i++;
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
}
|
package/util/get-parsers.js
CHANGED
|
@@ -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 = (
|
|
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.
|
|
17
|
+
const decode = dataTypeReg.decodeBinary;
|
|
17
18
|
if (decode) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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.
|
|
34
|
+
const parse = dataTypeReg.decodeText;
|
|
25
35
|
if (parse) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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] =
|
|
52
|
+
parsers[i] =
|
|
53
|
+
parsers[i] ||
|
|
54
|
+
(f.format === DataFormat.text
|
|
55
|
+
? DefaultTextColumnParser
|
|
56
|
+
: DefaultColumnParser);
|
|
36
57
|
}
|
|
37
58
|
return parsers;
|
|
38
59
|
}
|
package/util/parse-array.js
CHANGED
|
@@ -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
|
+
}
|
package/util/parse-datetime.d.ts
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export declare function
|
|
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;
|
package/util/parse-datetime.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/util/parse-row.d.ts
CHANGED
|
@@ -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[],
|
|
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,
|
|
2
|
-
const
|
|
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 <
|
|
5
|
-
|
|
6
|
-
|
|
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
|
}
|
package/util/parse-time.js
CHANGED
|
@@ -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
|
-
|
|
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));
|