@minnowdb/core 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -32
- package/dist/block-format/block.d.ts +32 -13
- package/dist/block-format/block.js +179 -55
- package/dist/block-format/checksum.d.ts +2 -1
- package/dist/block-format/checksum.js +5 -2
- package/dist/block-format/codecs.d.ts +7 -2
- package/dist/block-format/codecs.js +53 -12
- package/dist/block-format/column.d.ts +3 -2
- package/dist/block-format/column.js +96 -30
- package/dist/block-format/index.d.ts +2 -2
- package/dist/block-format/index.js +2 -2
- package/dist/block-format/physical.d.ts +8 -2
- package/dist/block-format/physical.js +14 -6
- package/dist/block-format/types.d.ts +7 -3
- package/dist/block-format/types.js +0 -1
- package/dist/block-format/unicode.d.ts +11 -0
- package/dist/block-format/unicode.js +46 -0
- package/dist/date-value.d.ts +15 -0
- package/dist/date-value.js +64 -0
- package/dist/engine/artifact-cache.d.ts +2 -1
- package/dist/engine/artifact-cache.js +5 -1
- package/dist/engine/batch.d.ts +11 -3
- package/dist/engine/batch.js +17 -7
- package/dist/engine/buffered-writer.d.ts +2 -1
- package/dist/engine/buffered-writer.js +34 -9
- package/dist/engine/cache-limits.d.ts +25 -0
- package/dist/engine/cache-limits.js +25 -0
- package/dist/engine/catalog.d.ts +24 -5
- package/dist/engine/catalog.js +34 -7
- package/dist/engine/client.d.ts +29 -7
- package/dist/engine/client.js +216 -40
- package/dist/engine/database.d.ts +224 -39
- package/dist/engine/database.js +7475 -1539
- package/dist/engine/defaults.d.ts +4 -8
- package/dist/engine/defaults.js +46 -22
- package/dist/engine/errors.d.ts +19 -1
- package/dist/engine/errors.js +32 -2
- package/dist/engine/fts.d.ts +0 -6
- package/dist/engine/fts.js +41 -14
- package/dist/engine/group-index.d.ts +0 -1
- package/dist/engine/group-index.js +6 -11
- package/dist/engine/index.d.ts +16 -10
- package/dist/engine/index.js +12 -9
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/join-index.js +2 -2
- package/dist/engine/keyed-live.d.ts +57 -0
- package/dist/engine/keyed-live.js +226 -0
- package/dist/engine/live-api.d.ts +4 -0
- package/dist/engine/live-api.js +4 -0
- package/dist/engine/live.d.ts +43 -26
- package/dist/engine/live.js +504 -126
- package/dist/engine/memory.d.ts +2 -1
- package/dist/engine/memory.js +3 -2
- package/dist/engine/optimizer.d.ts +0 -1
- package/dist/engine/optimizer.js +262 -27
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +1 -2
- package/dist/engine/query-cache.js +7 -5
- package/dist/engine/query.d.ts +134 -29
- package/dist/engine/query.js +1668 -372
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +6 -3
- package/dist/engine/schema-wire.js +40 -34
- package/dist/engine/schema.d.ts +156 -80
- package/dist/engine/schema.js +570 -108
- package/dist/engine/sort-keys.d.ts +4 -4
- package/dist/engine/sort-keys.js +44 -29
- package/dist/engine/sql-domains.d.ts +31 -0
- package/dist/engine/sql-domains.js +585 -0
- package/dist/engine/sql-driver.d.ts +19 -0
- package/dist/engine/sql-driver.js +1 -0
- package/dist/engine/sql-json.d.ts +7 -9
- package/dist/engine/sql-json.js +75 -15
- package/dist/engine/sql-semantics.d.ts +8 -3
- package/dist/engine/sql-semantics.js +458 -30
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +12 -2
- package/dist/engine/vector.js +635 -119
- package/dist/engine/worker-host.d.ts +9 -2
- package/dist/engine/worker-host.js +620 -152
- package/dist/engine/worker.d.ts +0 -1
- package/dist/engine/worker.js +3 -3
- package/dist/engine/write-block-planner.d.ts +18 -0
- package/dist/engine/write-block-planner.js +112 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/plan/index.d.ts +3 -4
- package/dist/plan/index.js +3 -4
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +99 -51
- package/dist/storage/indexeddb.js +13448 -2660
- package/dist/storage/memory.d.ts +64 -57
- package/dist/storage/memory.js +1042 -128
- package/dist/storage/opfs/files.d.ts +18 -7
- package/dist/storage/opfs/files.js +115 -28
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +1 -1
- package/dist/storage/opfs/leader.d.ts +409 -75
- package/dist/storage/opfs/leader.js +4621 -650
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +224 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
- package/dist/storage/opfs/snapshot-ledger.js +281 -0
- package/dist/storage/opfs/store.d.ts +33 -99
- package/dist/storage/opfs/store.js +453 -364
- package/dist/storage/persistence.d.ts +37 -0
- package/dist/storage/persistence.js +78 -0
- package/dist/storage/snapshot-stream.d.ts +24 -0
- package/dist/storage/snapshot-stream.js +897 -0
- package/dist/storage/snapshot.d.ts +9 -85
- package/dist/storage/snapshot.js +33 -265
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +4 -5
- package/dist/storage/toolkit/index.js +28 -4
- package/dist/storage/toolkit/record-core.d.ts +182 -55
- package/dist/storage/toolkit/record-core.js +6158 -1331
- package/dist/storage/toolkit/sync-file.d.ts +10 -1
- package/dist/storage/toolkit/sync-file.js +50 -2
- package/dist/storage/toolkit/wal.d.ts +23 -5
- package/dist/storage/toolkit/wal.js +89 -27
- package/dist/storage/toolkit/wire.d.ts +15 -2
- package/dist/storage/toolkit/wire.js +199 -10
- package/dist/storage/types.d.ts +1439 -204
- package/dist/storage/types.js +1745 -141
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +1009 -119
- package/dist/testing/index.d.ts +46 -26
- package/dist/testing/index.js +112 -57
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +97 -0
- package/dist/testing/simulator.js +591 -0
- package/dist/testing/sqllogictest.d.ts +89 -0
- package/dist/testing/sqllogictest.js +434 -0
- package/dist/transactions/index.d.ts +92 -16
- package/dist/transactions/index.js +1074 -236
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +53 -3
- package/postgres-feature-profile.json +223 -0
- package/sql-feature-matrix.json +172 -252
- package/dist/block-format/block.d.ts.map +0 -1
- package/dist/block-format/block.js.map +0 -1
- package/dist/block-format/checksum.d.ts.map +0 -1
- package/dist/block-format/checksum.js.map +0 -1
- package/dist/block-format/codecs.d.ts.map +0 -1
- package/dist/block-format/codecs.js.map +0 -1
- package/dist/block-format/column.d.ts.map +0 -1
- package/dist/block-format/column.js.map +0 -1
- package/dist/block-format/index.d.ts.map +0 -1
- package/dist/block-format/index.js.map +0 -1
- package/dist/block-format/physical.d.ts.map +0 -1
- package/dist/block-format/physical.js.map +0 -1
- package/dist/block-format/types.d.ts.map +0 -1
- package/dist/block-format/types.js.map +0 -1
- package/dist/engine/artifact-cache.d.ts.map +0 -1
- package/dist/engine/artifact-cache.js.map +0 -1
- package/dist/engine/batch.d.ts.map +0 -1
- package/dist/engine/batch.js.map +0 -1
- package/dist/engine/buffered-writer.d.ts.map +0 -1
- package/dist/engine/buffered-writer.js.map +0 -1
- package/dist/engine/catalog.d.ts.map +0 -1
- package/dist/engine/catalog.js.map +0 -1
- package/dist/engine/client.d.ts.map +0 -1
- package/dist/engine/client.js.map +0 -1
- package/dist/engine/coordinator.d.ts +0 -17
- package/dist/engine/coordinator.d.ts.map +0 -1
- package/dist/engine/coordinator.js +0 -60
- package/dist/engine/coordinator.js.map +0 -1
- package/dist/engine/database.d.ts.map +0 -1
- package/dist/engine/database.js.map +0 -1
- package/dist/engine/defaults.d.ts.map +0 -1
- package/dist/engine/defaults.js.map +0 -1
- package/dist/engine/errors.d.ts.map +0 -1
- package/dist/engine/errors.js.map +0 -1
- package/dist/engine/fts.d.ts.map +0 -1
- package/dist/engine/fts.js.map +0 -1
- package/dist/engine/group-index.d.ts.map +0 -1
- package/dist/engine/group-index.js.map +0 -1
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/join-index.d.ts.map +0 -1
- package/dist/engine/join-index.js.map +0 -1
- package/dist/engine/live.d.ts.map +0 -1
- package/dist/engine/live.js.map +0 -1
- package/dist/engine/memory.d.ts.map +0 -1
- package/dist/engine/memory.js.map +0 -1
- package/dist/engine/optimizer.d.ts.map +0 -1
- package/dist/engine/optimizer.js.map +0 -1
- package/dist/engine/query-cache.d.ts.map +0 -1
- package/dist/engine/query-cache.js.map +0 -1
- package/dist/engine/query.d.ts.map +0 -1
- package/dist/engine/query.js.map +0 -1
- package/dist/engine/result-wire.d.ts.map +0 -1
- package/dist/engine/result-wire.js.map +0 -1
- package/dist/engine/schema-wire.d.ts.map +0 -1
- package/dist/engine/schema-wire.js.map +0 -1
- package/dist/engine/schema.d.ts.map +0 -1
- package/dist/engine/schema.js.map +0 -1
- package/dist/engine/sort-keys.d.ts.map +0 -1
- package/dist/engine/sort-keys.js.map +0 -1
- package/dist/engine/sql-json.d.ts.map +0 -1
- package/dist/engine/sql-json.js.map +0 -1
- package/dist/engine/sql-semantics.d.ts.map +0 -1
- package/dist/engine/sql-semantics.js.map +0 -1
- package/dist/engine/vector.d.ts.map +0 -1
- package/dist/engine/vector.js.map +0 -1
- package/dist/engine/worker-host.d.ts.map +0 -1
- package/dist/engine/worker-host.js.map +0 -1
- package/dist/engine/worker.d.ts.map +0 -1
- package/dist/engine/worker.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plan/index.d.ts.map +0 -1
- package/dist/plan/index.js.map +0 -1
- package/dist/storage/fixture-shape.d.ts +0 -42
- package/dist/storage/fixture-shape.d.ts.map +0 -1
- package/dist/storage/fixture-shape.js +0 -146
- package/dist/storage/fixture-shape.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/indexeddb.d.ts.map +0 -1
- package/dist/storage/indexeddb.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/opfs/files.d.ts.map +0 -1
- package/dist/storage/opfs/files.js.map +0 -1
- package/dist/storage/opfs/index.d.ts.map +0 -1
- package/dist/storage/opfs/index.js.map +0 -1
- package/dist/storage/opfs/leader.d.ts.map +0 -1
- package/dist/storage/opfs/leader.js.map +0 -1
- package/dist/storage/opfs/rpc.d.ts.map +0 -1
- package/dist/storage/opfs/rpc.js.map +0 -1
- package/dist/storage/opfs/store.d.ts.map +0 -1
- package/dist/storage/opfs/store.js.map +0 -1
- package/dist/storage/snapshot.d.ts.map +0 -1
- package/dist/storage/snapshot.js.map +0 -1
- package/dist/storage/toolkit/extents.d.ts.map +0 -1
- package/dist/storage/toolkit/extents.js.map +0 -1
- package/dist/storage/toolkit/index.d.ts.map +0 -1
- package/dist/storage/toolkit/index.js.map +0 -1
- package/dist/storage/toolkit/record-core.d.ts.map +0 -1
- package/dist/storage/toolkit/record-core.js.map +0 -1
- package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
- package/dist/storage/toolkit/sync-file.js.map +0 -1
- package/dist/storage/toolkit/wal.d.ts.map +0 -1
- package/dist/storage/toolkit/wal.js.map +0 -1
- package/dist/storage/toolkit/wire.d.ts.map +0 -1
- package/dist/storage/toolkit/wire.js.map +0 -1
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js.map +0 -1
- package/dist/testing/block-store-conformance.d.ts.map +0 -1
- package/dist/testing/block-store-conformance.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/opfs-shim.d.ts.map +0 -1
- package/dist/testing/opfs-shim.js.map +0 -1
- package/dist/testing/seeds.d.ts +0 -11
- package/dist/testing/seeds.d.ts.map +0 -1
- package/dist/testing/seeds.js +0 -50
- package/dist/testing/seeds.js.map +0 -1
- package/dist/transactions/index.d.ts.map +0 -1
- package/dist/transactions/index.js.map +0 -1
- package/dist/worker-protocol/index.d.ts.map +0 -1
- package/dist/worker-protocol/index.js.map +0 -1
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import { assertWellFormedString } from "../block-format/unicode.js";
|
|
2
|
+
import { dateIsoString } from "../date-value.js";
|
|
3
|
+
import { MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_NUMERIC_DIGITS, MAX_SQL_STRUCTURED_VALUE_DEPTH, MAX_SQL_STRUCTURED_VALUE_ITEMS, } from "./cache-limits.js";
|
|
4
|
+
/** Internal string encodings for PostgreSQL value domains carried by string vectors. */
|
|
5
|
+
const PREFIX = "\u0000minnow-domain:";
|
|
6
|
+
const TEXT_VALUE = `${PREFIX}text:`;
|
|
7
|
+
const NUMERIC = `${PREFIX}numeric:`;
|
|
8
|
+
const JSON_VALUE = `${PREFIX}json:`;
|
|
9
|
+
const JSONB_VALUE = `${PREFIX}jsonb:`;
|
|
10
|
+
const UUID_VALUE = `${PREFIX}uuid:`;
|
|
11
|
+
const TIME_VALUE = `${PREFIX}time:`;
|
|
12
|
+
const INTERVAL_VALUE = `${PREFIX}interval:`;
|
|
13
|
+
const ARRAY_VALUE = `${PREFIX}array:`;
|
|
14
|
+
const COLLATION_VALUE = `${PREFIX}collation:`;
|
|
15
|
+
const ENUM_VALUE = `${PREFIX}enum:`;
|
|
16
|
+
/**
|
|
17
|
+
* Protects an ordinary SQL string that happens to use the internal domain namespace. Physical
|
|
18
|
+
* TEXT remains unchanged on disk; this wrapper is applied at execution boundaries so a user
|
|
19
|
+
* value can never be mistaken for NUMERIC, INTERVAL, enum, or another tagged logical value.
|
|
20
|
+
*/
|
|
21
|
+
export function protectedSqlTextValue(value) {
|
|
22
|
+
return value.startsWith(PREFIX) ? TEXT_VALUE + value : value;
|
|
23
|
+
}
|
|
24
|
+
/** Removes only the ordinary-TEXT wrapper, leaving real domain values tagged. */
|
|
25
|
+
export function externalSqlTextValue(value) {
|
|
26
|
+
return typeof value === "string" && value.startsWith(TEXT_VALUE)
|
|
27
|
+
? value.slice(TEXT_VALUE.length)
|
|
28
|
+
: value;
|
|
29
|
+
}
|
|
30
|
+
function pow10(exponent) {
|
|
31
|
+
if (!Number.isSafeInteger(exponent) || exponent < 0 || exponent > 100_000) {
|
|
32
|
+
throw new RangeError(`Decimal scale is outside the supported range: ${String(exponent)}`);
|
|
33
|
+
}
|
|
34
|
+
// Retaining every intermediate power up to 100,000 consumes quadratic memory in the number
|
|
35
|
+
// of decimal digits. Native exponentiation constructs only the requested result.
|
|
36
|
+
return 10n ** BigInt(exponent);
|
|
37
|
+
}
|
|
38
|
+
function decimalParts(value) {
|
|
39
|
+
const source = String(value).trim();
|
|
40
|
+
assertBoundedDomainString(source, "NUMERIC value");
|
|
41
|
+
const match = /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?\d+))?$/.exec(source);
|
|
42
|
+
if (match === null)
|
|
43
|
+
throw new TypeError(`Invalid NUMERIC value: ${String(value)}`);
|
|
44
|
+
const fraction = match[3] ?? match[4] ?? "";
|
|
45
|
+
const digits = `${match[2] ?? "0"}${fraction}`.replace(/^0+(?=\d)/, "");
|
|
46
|
+
if (digits.length > MAX_SQL_NUMERIC_DIGITS) {
|
|
47
|
+
throw new RangeError(`NUMERIC value exceeds ${String(MAX_SQL_NUMERIC_DIGITS)} significant digits`);
|
|
48
|
+
}
|
|
49
|
+
const exponent = Number(match[5] ?? 0);
|
|
50
|
+
if (!Number.isSafeInteger(exponent))
|
|
51
|
+
throw new RangeError(`Invalid NUMERIC exponent: ${source}`);
|
|
52
|
+
let coefficient = BigInt(digits || "0") * (match[1] === "-" ? -1n : 1n);
|
|
53
|
+
let scale = fraction.length - exponent;
|
|
54
|
+
if (scale < 0) {
|
|
55
|
+
coefficient *= pow10(-scale);
|
|
56
|
+
scale = 0;
|
|
57
|
+
}
|
|
58
|
+
return { coefficient, scale };
|
|
59
|
+
}
|
|
60
|
+
function normalizeDecimal(parts) {
|
|
61
|
+
let { coefficient, scale } = parts;
|
|
62
|
+
while (scale > 0 && coefficient % 10n === 0n) {
|
|
63
|
+
coefficient /= 10n;
|
|
64
|
+
scale -= 1;
|
|
65
|
+
}
|
|
66
|
+
return { coefficient, scale };
|
|
67
|
+
}
|
|
68
|
+
function rescaleDecimal(parts, scale) {
|
|
69
|
+
if (!Number.isSafeInteger(scale) || scale < 0 || scale > 100_000) {
|
|
70
|
+
throw new RangeError(`NUMERIC scale is outside the supported range: ${String(scale)}`);
|
|
71
|
+
}
|
|
72
|
+
if (parts.scale <= scale) {
|
|
73
|
+
return { coefficient: parts.coefficient * pow10(scale - parts.scale), scale };
|
|
74
|
+
}
|
|
75
|
+
const divisor = pow10(parts.scale - scale);
|
|
76
|
+
let quotient = parts.coefficient / divisor;
|
|
77
|
+
const remainder = parts.coefficient % divisor;
|
|
78
|
+
if ((remainder < 0n ? -remainder : remainder) * 2n >= divisor) {
|
|
79
|
+
quotient += parts.coefficient < 0n ? -1n : 1n;
|
|
80
|
+
}
|
|
81
|
+
return { coefficient: quotient, scale };
|
|
82
|
+
}
|
|
83
|
+
function formatDecimal(parts) {
|
|
84
|
+
const negative = parts.coefficient < 0n;
|
|
85
|
+
const digits = (negative ? -parts.coefficient : parts.coefficient).toString();
|
|
86
|
+
if (parts.scale === 0)
|
|
87
|
+
return `${negative ? "-" : ""}${digits}`;
|
|
88
|
+
const padded = digits.padStart(parts.scale + 1, "0");
|
|
89
|
+
const split = padded.length - parts.scale;
|
|
90
|
+
return `${negative ? "-" : ""}${padded.slice(0, split)}.${padded.slice(split)}`;
|
|
91
|
+
}
|
|
92
|
+
function taggedDecimalParts(value) {
|
|
93
|
+
if (typeof value !== "string" || !value.startsWith(NUMERIC))
|
|
94
|
+
return undefined;
|
|
95
|
+
return decimalParts(value.slice(NUMERIC.length));
|
|
96
|
+
}
|
|
97
|
+
export function isExactNumeric(value) {
|
|
98
|
+
return taggedDecimalParts(value) !== undefined;
|
|
99
|
+
}
|
|
100
|
+
export function exactNumericValue(value, precision, scale) {
|
|
101
|
+
if (value === null || value === undefined)
|
|
102
|
+
return null;
|
|
103
|
+
const raw = taggedDecimalParts(value) ??
|
|
104
|
+
(typeof value === "number" || typeof value === "string" ? decimalParts(value) : undefined);
|
|
105
|
+
if (raw === undefined)
|
|
106
|
+
throw new TypeError("NUMERIC accepts a number or decimal string");
|
|
107
|
+
const adjusted = scale === undefined ? normalizeDecimal(raw) : rescaleDecimal(raw, scale);
|
|
108
|
+
if (precision !== undefined) {
|
|
109
|
+
if (!Number.isSafeInteger(precision) || precision < 1 || precision > 100_000) {
|
|
110
|
+
throw new RangeError(`NUMERIC precision is outside the supported range: ${String(precision)}`);
|
|
111
|
+
}
|
|
112
|
+
const declaredScale = scale ?? 0;
|
|
113
|
+
if (declaredScale > precision) {
|
|
114
|
+
throw new TypeError("NUMERIC scale cannot exceed its precision");
|
|
115
|
+
}
|
|
116
|
+
const integer = adjusted.coefficient < 0n ? -adjusted.coefficient : adjusted.coefficient;
|
|
117
|
+
const integerDigits = (integer / pow10(adjusted.scale)).toString().length;
|
|
118
|
+
if (integer !== 0n && integerDigits > precision - declaredScale) {
|
|
119
|
+
throw new RangeError(`NUMERIC(${String(precision)}, ${String(declaredScale)}) overflow`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(adjusted)), "NUMERIC value");
|
|
123
|
+
}
|
|
124
|
+
export function exactNumericBinary(operator, left, right) {
|
|
125
|
+
const leftTagged = taggedDecimalParts(left);
|
|
126
|
+
const rightTagged = taggedDecimalParts(right);
|
|
127
|
+
if (leftTagged === undefined && rightTagged === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
const a = leftTagged ?? decimalParts(typeof left === "number" ? left : String(left));
|
|
130
|
+
const b = rightTagged ?? decimalParts(typeof right === "number" ? right : String(right));
|
|
131
|
+
if ((operator === "/" || operator === "%") && b.coefficient === 0n)
|
|
132
|
+
return null;
|
|
133
|
+
let result;
|
|
134
|
+
if (operator === "+" || operator === "-") {
|
|
135
|
+
const scale = Math.max(a.scale, b.scale);
|
|
136
|
+
const ac = a.coefficient * pow10(scale - a.scale);
|
|
137
|
+
const bc = b.coefficient * pow10(scale - b.scale);
|
|
138
|
+
result = { coefficient: operator === "+" ? ac + bc : ac - bc, scale };
|
|
139
|
+
}
|
|
140
|
+
else if (operator === "*") {
|
|
141
|
+
result = { coefficient: a.coefficient * b.coefficient, scale: a.scale + b.scale };
|
|
142
|
+
}
|
|
143
|
+
else if (operator === "%") {
|
|
144
|
+
const scale = Math.max(a.scale, b.scale);
|
|
145
|
+
result = {
|
|
146
|
+
coefficient: (a.coefficient * pow10(scale - a.scale)) % (b.coefficient * pow10(scale - b.scale)),
|
|
147
|
+
scale,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// PostgreSQL NUMERIC division is arbitrary precision. Twenty fractional digits gives a
|
|
152
|
+
// deterministic exact decimal rounding boundary without ever crossing binary Float64.
|
|
153
|
+
const scale = Math.max(20, a.scale, b.scale);
|
|
154
|
+
const numerator = a.coefficient * pow10(scale + b.scale);
|
|
155
|
+
const denominator = b.coefficient * pow10(a.scale);
|
|
156
|
+
let coefficient = numerator / denominator;
|
|
157
|
+
const remainder = numerator % denominator;
|
|
158
|
+
const magnitude = denominator < 0n ? -denominator : denominator;
|
|
159
|
+
if ((remainder < 0n ? -remainder : remainder) * 2n >= magnitude) {
|
|
160
|
+
coefficient += numerator < 0n !== denominator < 0n ? -1n : 1n;
|
|
161
|
+
}
|
|
162
|
+
result = { coefficient, scale };
|
|
163
|
+
}
|
|
164
|
+
return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(result)), "NUMERIC result");
|
|
165
|
+
}
|
|
166
|
+
export function exactNumericCompare(left, right) {
|
|
167
|
+
const leftTagged = taggedDecimalParts(left);
|
|
168
|
+
const rightTagged = taggedDecimalParts(right);
|
|
169
|
+
if (leftTagged === undefined && rightTagged === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
const a = leftTagged ??
|
|
172
|
+
(typeof left === "number" || typeof left === "string" ? decimalParts(left) : undefined);
|
|
173
|
+
const b = rightTagged ??
|
|
174
|
+
(typeof right === "number" || typeof right === "string" ? decimalParts(right) : undefined);
|
|
175
|
+
if (a === undefined || b === undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
const scale = Math.max(a.scale, b.scale);
|
|
178
|
+
const ac = a.coefficient * pow10(scale - a.scale);
|
|
179
|
+
const bc = b.coefficient * pow10(scale - b.scale);
|
|
180
|
+
return ac === bc ? 0 : ac < bc ? -1 : 1;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Serializes a JSON value while bounding traversal, nesting, and output before concatenation.
|
|
184
|
+
* Canonical mode sorts object names directly in the wire text (including integer-looking names,
|
|
185
|
+
* which JavaScript object enumeration would otherwise silently reorder).
|
|
186
|
+
*/
|
|
187
|
+
export function boundedJsonText(value, canonical, label = "JSON value") {
|
|
188
|
+
const state = { active: new Set(), pieces: [], items: 0, length: 0 };
|
|
189
|
+
encodeBoundedJson(value, canonical, label, 0, state);
|
|
190
|
+
return state.pieces.join("");
|
|
191
|
+
}
|
|
192
|
+
function encodeBoundedJson(value, canonical, label, depth, state) {
|
|
193
|
+
state.items += 1;
|
|
194
|
+
if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
|
|
195
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
196
|
+
}
|
|
197
|
+
if (depth > MAX_SQL_STRUCTURED_VALUE_DEPTH) {
|
|
198
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_DEPTH)} levels`);
|
|
199
|
+
}
|
|
200
|
+
if (value === null) {
|
|
201
|
+
appendJsonPiece(state, "null", label);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (typeof value === "boolean") {
|
|
205
|
+
appendJsonPiece(state, value ? "true" : "false", label);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (typeof value === "number") {
|
|
209
|
+
if (!Number.isFinite(value))
|
|
210
|
+
throw new TypeError("JSON numbers must be finite");
|
|
211
|
+
appendJsonPiece(state, JSON.stringify(value), label);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (typeof value === "string") {
|
|
215
|
+
assertBoundedDomainString(value, label);
|
|
216
|
+
appendJsonPiece(state, JSON.stringify(value), label);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (value instanceof Date) {
|
|
220
|
+
const timestamp = dateIsoString(value);
|
|
221
|
+
appendJsonPiece(state, JSON.stringify(timestamp), label);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (typeof value !== "object")
|
|
225
|
+
throw new TypeError(`${label} cannot be represented as JSON`);
|
|
226
|
+
const object = value;
|
|
227
|
+
if (state.active.has(object))
|
|
228
|
+
throw new TypeError(`${label} cannot contain a cycle`);
|
|
229
|
+
state.active.add(object);
|
|
230
|
+
try {
|
|
231
|
+
if (Array.isArray(value)) {
|
|
232
|
+
appendJsonPiece(state, "[", label);
|
|
233
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
234
|
+
if (index > 0)
|
|
235
|
+
appendJsonPiece(state, ",", label);
|
|
236
|
+
encodeBoundedJson(value[index], canonical, label, depth + 1, state);
|
|
237
|
+
}
|
|
238
|
+
appendJsonPiece(state, "]", label);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const prototype = Object.getPrototypeOf(value);
|
|
242
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
243
|
+
throw new TypeError(`${label} accepts only JSON objects, arrays, and scalar values`);
|
|
244
|
+
}
|
|
245
|
+
const record = value;
|
|
246
|
+
// Object.keys() would allocate one array slot per property before the item cap can fire.
|
|
247
|
+
// Plain JSON objects need no prototype walk, so collect at most the admitted count instead.
|
|
248
|
+
const keys = [];
|
|
249
|
+
for (const key in record) {
|
|
250
|
+
if (!Object.hasOwn(record, key))
|
|
251
|
+
continue;
|
|
252
|
+
if (keys.length >= MAX_SQL_STRUCTURED_VALUE_ITEMS - state.items) {
|
|
253
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
254
|
+
}
|
|
255
|
+
keys.push(key);
|
|
256
|
+
}
|
|
257
|
+
if (canonical)
|
|
258
|
+
keys.sort();
|
|
259
|
+
appendJsonPiece(state, "{", label);
|
|
260
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
261
|
+
const key = keys[index] ?? "";
|
|
262
|
+
state.items += 1;
|
|
263
|
+
if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
|
|
264
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
265
|
+
}
|
|
266
|
+
assertBoundedDomainString(key, `${label} object name`);
|
|
267
|
+
if (index > 0)
|
|
268
|
+
appendJsonPiece(state, ",", label);
|
|
269
|
+
appendJsonPiece(state, JSON.stringify(key), label);
|
|
270
|
+
appendJsonPiece(state, ":", label);
|
|
271
|
+
encodeBoundedJson(record[key], canonical, label, depth + 1, state);
|
|
272
|
+
}
|
|
273
|
+
appendJsonPiece(state, "}", label);
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
state.active.delete(object);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function appendJsonPiece(state, piece, label) {
|
|
280
|
+
const next = state.length + piece.length;
|
|
281
|
+
if (!Number.isSafeInteger(next) || next > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
282
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
283
|
+
}
|
|
284
|
+
state.length = next;
|
|
285
|
+
state.pieces.push(piece);
|
|
286
|
+
}
|
|
287
|
+
function assertBoundedDomainString(value, label) {
|
|
288
|
+
if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
289
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
290
|
+
}
|
|
291
|
+
assertWellFormedString(value, label);
|
|
292
|
+
}
|
|
293
|
+
export function jsonDomainValue(value, binary) {
|
|
294
|
+
if (value === null || value === undefined)
|
|
295
|
+
return null;
|
|
296
|
+
let parsed = value;
|
|
297
|
+
if (typeof value === "string") {
|
|
298
|
+
const prefix = value.startsWith(JSONB_VALUE)
|
|
299
|
+
? JSONB_VALUE
|
|
300
|
+
: value.startsWith(JSON_VALUE)
|
|
301
|
+
? JSON_VALUE
|
|
302
|
+
: undefined;
|
|
303
|
+
const source = prefix === undefined ? value : value.slice(prefix.length);
|
|
304
|
+
assertBoundedDomainString(source, "JSON value");
|
|
305
|
+
try {
|
|
306
|
+
parsed = JSON.parse(source);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
throw new TypeError("Invalid JSON value");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const text = boundedJsonText(parsed, binary, binary ? "JSONB value" : "JSON value");
|
|
313
|
+
return boundedTaggedDomainValue(binary ? JSONB_VALUE : JSON_VALUE, text, binary ? "JSONB value" : "JSON value");
|
|
314
|
+
}
|
|
315
|
+
export function uuidDomainValue(value) {
|
|
316
|
+
if (value === null || value === undefined)
|
|
317
|
+
return null;
|
|
318
|
+
if (typeof value !== "string")
|
|
319
|
+
throw new TypeError("UUID accepts a string value");
|
|
320
|
+
assertBoundedDomainString(value, "UUID value");
|
|
321
|
+
const source = (value.startsWith(UUID_VALUE) ? value.slice(UUID_VALUE.length) : value).toLowerCase();
|
|
322
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(source)) {
|
|
323
|
+
throw new TypeError(`Invalid UUID value: ${value}`);
|
|
324
|
+
}
|
|
325
|
+
return UUID_VALUE + source;
|
|
326
|
+
}
|
|
327
|
+
export function timeDomainValue(value) {
|
|
328
|
+
if (value === null || value === undefined)
|
|
329
|
+
return null;
|
|
330
|
+
if (typeof value !== "string")
|
|
331
|
+
throw new TypeError("TIME accepts a string value");
|
|
332
|
+
assertBoundedDomainString(value, "TIME value");
|
|
333
|
+
const source = value.startsWith(TIME_VALUE) ? value.slice(TIME_VALUE.length) : value;
|
|
334
|
+
const match = /^(\d{2}):(\d{2})(?::(\d{2})(\.\d{1,6})?)?$/.exec(source);
|
|
335
|
+
const hours = match?.[1];
|
|
336
|
+
const minutes = match?.[2];
|
|
337
|
+
if (match === null ||
|
|
338
|
+
hours === undefined ||
|
|
339
|
+
minutes === undefined ||
|
|
340
|
+
Number(hours) > 23 ||
|
|
341
|
+
Number(minutes) > 59 ||
|
|
342
|
+
Number(match[3] ?? 0) > 59) {
|
|
343
|
+
throw new TypeError(`Invalid TIME value: ${value}`);
|
|
344
|
+
}
|
|
345
|
+
return TIME_VALUE + `${hours}:${minutes}:${match[3] ?? "00"}${match[4] ?? ""}`;
|
|
346
|
+
}
|
|
347
|
+
export function intervalDomainValue(value) {
|
|
348
|
+
if (value === null || value === undefined)
|
|
349
|
+
return null;
|
|
350
|
+
if (typeof value !== "string")
|
|
351
|
+
throw new TypeError("INTERVAL accepts a string value");
|
|
352
|
+
assertBoundedDomainString(value, "INTERVAL value");
|
|
353
|
+
if (value.startsWith(INTERVAL_VALUE)) {
|
|
354
|
+
const source = value.slice(INTERVAL_VALUE.length);
|
|
355
|
+
let decoded;
|
|
356
|
+
try {
|
|
357
|
+
decoded = JSON.parse(source);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
throw new TypeError("Invalid tagged INTERVAL value");
|
|
361
|
+
}
|
|
362
|
+
if (!Array.isArray(decoded) ||
|
|
363
|
+
decoded.length !== 3 ||
|
|
364
|
+
!decoded.every((part) => Number.isSafeInteger(part)) ||
|
|
365
|
+
JSON.stringify(decoded) !== source) {
|
|
366
|
+
throw new TypeError("Invalid tagged INTERVAL value");
|
|
367
|
+
}
|
|
368
|
+
return value;
|
|
369
|
+
}
|
|
370
|
+
const source = value.trim();
|
|
371
|
+
let months = 0;
|
|
372
|
+
let days = 0;
|
|
373
|
+
let microseconds = 0;
|
|
374
|
+
let matched = 0;
|
|
375
|
+
const partPattern = /([+-]?\d+(?:\.\d+)?)\s*([a-zA-Z]+)/g;
|
|
376
|
+
const units = new Map([
|
|
377
|
+
["year", (amount) => (months += amount * 12)],
|
|
378
|
+
["month", (amount) => (months += amount)],
|
|
379
|
+
["week", (amount) => (days += amount * 7)],
|
|
380
|
+
["day", (amount) => (days += amount)],
|
|
381
|
+
["hour", (amount) => (microseconds += amount * 3_600_000_000)],
|
|
382
|
+
["minute", (amount) => (microseconds += amount * 60_000_000)],
|
|
383
|
+
["second", (amount) => (microseconds += amount * 1_000_000)],
|
|
384
|
+
]);
|
|
385
|
+
for (const part of source.matchAll(partPattern)) {
|
|
386
|
+
const unit = (part[2] ?? "").toLowerCase().replace(/s$/, "");
|
|
387
|
+
const apply = units.get(unit);
|
|
388
|
+
if (apply === undefined)
|
|
389
|
+
throw new TypeError(`Unknown INTERVAL unit: ${unit}`);
|
|
390
|
+
const amount = Number(part[1]);
|
|
391
|
+
if ((unit === "year" || unit === "month" || unit === "week" || unit === "day") &&
|
|
392
|
+
!Number.isInteger(amount)) {
|
|
393
|
+
throw new TypeError(`INTERVAL ${unit} must be a whole number`);
|
|
394
|
+
}
|
|
395
|
+
apply(amount);
|
|
396
|
+
matched += 1;
|
|
397
|
+
}
|
|
398
|
+
const residue = source.replace(partPattern, "").trim();
|
|
399
|
+
if (matched === 0 ||
|
|
400
|
+
residue !== "" ||
|
|
401
|
+
!Number.isSafeInteger(months) ||
|
|
402
|
+
!Number.isSafeInteger(days) ||
|
|
403
|
+
!Number.isSafeInteger(microseconds)) {
|
|
404
|
+
throw new TypeError(`Invalid INTERVAL value: ${value}`);
|
|
405
|
+
}
|
|
406
|
+
return boundedTaggedDomainValue(INTERVAL_VALUE, JSON.stringify([months, days, microseconds]), "INTERVAL value");
|
|
407
|
+
}
|
|
408
|
+
export function arrayDomainValue(values) {
|
|
409
|
+
return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(values.map(externalSqlDomainValue), true, "ARRAY value"), "ARRAY value");
|
|
410
|
+
}
|
|
411
|
+
export function enumDomainValue(value, name, values) {
|
|
412
|
+
if (value === null || value === undefined)
|
|
413
|
+
return null;
|
|
414
|
+
if (typeof value !== "string")
|
|
415
|
+
throw new TypeError(`Enum ${name} accepts a string value`);
|
|
416
|
+
assertBoundedDomainString(value, `Enum ${name} value`);
|
|
417
|
+
let source = value;
|
|
418
|
+
// Enum labels are arbitrary strings. Prefer an exact declared label before recognizing the
|
|
419
|
+
// internal tag namespace, so a legal label can begin with that prefix just like ordinary TEXT.
|
|
420
|
+
if (!values.includes(value) && value.startsWith(ENUM_VALUE)) {
|
|
421
|
+
const encoded = value.slice(ENUM_VALUE.length);
|
|
422
|
+
let decoded;
|
|
423
|
+
try {
|
|
424
|
+
decoded = JSON.parse(encoded);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
throw new TypeError(`Invalid tagged enum ${name} value`);
|
|
428
|
+
}
|
|
429
|
+
if (!Array.isArray(decoded) ||
|
|
430
|
+
decoded.length !== 3 ||
|
|
431
|
+
decoded[0] !== name ||
|
|
432
|
+
!Number.isSafeInteger(decoded[1]) ||
|
|
433
|
+
typeof decoded[2] !== "string" ||
|
|
434
|
+
JSON.stringify(decoded) !== encoded) {
|
|
435
|
+
throw new TypeError(`Invalid tagged enum ${name} value`);
|
|
436
|
+
}
|
|
437
|
+
source = decoded[2];
|
|
438
|
+
}
|
|
439
|
+
const index = values.indexOf(source);
|
|
440
|
+
if (index === -1)
|
|
441
|
+
throw new TypeError(`${source} is not a value of enum ${name}`);
|
|
442
|
+
return boundedTaggedDomainValue(ENUM_VALUE, boundedJsonText([name, index, source], false, `Enum ${name} value`), `Enum ${name} value`);
|
|
443
|
+
}
|
|
444
|
+
export function enumDomainCompare(left, right) {
|
|
445
|
+
if (typeof left !== "string" ||
|
|
446
|
+
typeof right !== "string" ||
|
|
447
|
+
!left.startsWith(ENUM_VALUE) ||
|
|
448
|
+
!right.startsWith(ENUM_VALUE)) {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
const a = JSON.parse(left.slice(ENUM_VALUE.length));
|
|
452
|
+
const b = JSON.parse(right.slice(ENUM_VALUE.length));
|
|
453
|
+
if (a[0] !== b[0])
|
|
454
|
+
throw new TypeError("Cannot compare values of different enum types");
|
|
455
|
+
return a[1] - b[1];
|
|
456
|
+
}
|
|
457
|
+
export function normalizeSqlDomainValue(domain, value) {
|
|
458
|
+
if (domain.kind === "numeric") {
|
|
459
|
+
return exactNumericValue(value, domain.precision, domain.scale);
|
|
460
|
+
}
|
|
461
|
+
if (domain.kind === "json")
|
|
462
|
+
return jsonDomainValue(value, false);
|
|
463
|
+
if (domain.kind === "jsonb")
|
|
464
|
+
return jsonDomainValue(value, true);
|
|
465
|
+
if (domain.kind === "uuid")
|
|
466
|
+
return uuidDomainValue(value);
|
|
467
|
+
if (domain.kind === "time")
|
|
468
|
+
return timeDomainValue(value);
|
|
469
|
+
if (domain.kind === "interval")
|
|
470
|
+
return intervalDomainValue(value);
|
|
471
|
+
if (domain.kind === "enum")
|
|
472
|
+
return enumDomainValue(value, domain.name, domain.values);
|
|
473
|
+
if (value === null || value === undefined)
|
|
474
|
+
return null;
|
|
475
|
+
if (typeof value !== "string")
|
|
476
|
+
throw new TypeError("ARRAY columns accept JSON array text");
|
|
477
|
+
const source = value.startsWith(ARRAY_VALUE) ? value.slice(ARRAY_VALUE.length) : value;
|
|
478
|
+
assertBoundedDomainString(source, "ARRAY value");
|
|
479
|
+
let parsed;
|
|
480
|
+
try {
|
|
481
|
+
parsed = JSON.parse(source);
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
throw new TypeError("Invalid ARRAY value");
|
|
485
|
+
}
|
|
486
|
+
if (!Array.isArray(parsed))
|
|
487
|
+
throw new TypeError("ARRAY value must be an array");
|
|
488
|
+
// JSON array text contains ordinary JSON strings. Do not interpret a string that happens to
|
|
489
|
+
// begin with Minnow's internal domain prefix as an already-tagged SQL scalar.
|
|
490
|
+
return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(parsed, true, "ARRAY value"), "ARRAY value");
|
|
491
|
+
}
|
|
492
|
+
const collators = new Map();
|
|
493
|
+
const MAX_COLLATOR_CACHE_ENTRIES = 64;
|
|
494
|
+
export function collatedDomainValue(value, collation) {
|
|
495
|
+
if (value === null || value === undefined)
|
|
496
|
+
return null;
|
|
497
|
+
value = externalSqlDomainValue(value);
|
|
498
|
+
if (typeof value !== "string" || typeof collation !== "string") {
|
|
499
|
+
throw new TypeError("COLLATE requires a string value and collation name");
|
|
500
|
+
}
|
|
501
|
+
assertBoundedDomainString(value, "COLLATE value");
|
|
502
|
+
assertBoundedDomainString(collation, "COLLATE name");
|
|
503
|
+
const locale = collation === "POSIX" ? "C" : collation;
|
|
504
|
+
if (locale !== "C")
|
|
505
|
+
collatorFor(locale, collation);
|
|
506
|
+
return boundedTaggedDomainValue(COLLATION_VALUE, boundedJsonText([locale, value], false, "COLLATE value"), "COLLATE value");
|
|
507
|
+
}
|
|
508
|
+
function boundedTaggedDomainValue(prefix, value, label) {
|
|
509
|
+
// The prefix is an internal physical tag and is removed at the JavaScript boundary. Limit the
|
|
510
|
+
// user-visible payload, while the fixed tag adds only a few bounded bytes in storage.
|
|
511
|
+
if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
512
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
513
|
+
}
|
|
514
|
+
return prefix + value;
|
|
515
|
+
}
|
|
516
|
+
export function collatedDomainCompare(left, right) {
|
|
517
|
+
const decode = (value) => typeof value === "string" && value.startsWith(COLLATION_VALUE)
|
|
518
|
+
? JSON.parse(value.slice(COLLATION_VALUE.length))
|
|
519
|
+
: undefined;
|
|
520
|
+
const a = decode(left);
|
|
521
|
+
const b = decode(right);
|
|
522
|
+
if (a === undefined && b === undefined)
|
|
523
|
+
return undefined;
|
|
524
|
+
if (a === undefined && typeof left !== "string")
|
|
525
|
+
return undefined;
|
|
526
|
+
if (b === undefined && typeof right !== "string")
|
|
527
|
+
return undefined;
|
|
528
|
+
const locale = a?.[0] ?? b?.[0] ?? "C";
|
|
529
|
+
if ((a !== undefined && a[0] !== locale) || (b !== undefined && b[0] !== locale)) {
|
|
530
|
+
throw new TypeError("Cannot compare values with different collations");
|
|
531
|
+
}
|
|
532
|
+
const leftValue = a?.[1] ?? left;
|
|
533
|
+
const rightValue = b?.[1] ?? right;
|
|
534
|
+
if (locale === "C")
|
|
535
|
+
return leftValue === rightValue ? 0 : leftValue < rightValue ? -1 : 1;
|
|
536
|
+
return collatorFor(locale, locale).compare(leftValue, rightValue);
|
|
537
|
+
}
|
|
538
|
+
function collatorFor(locale, displayName) {
|
|
539
|
+
const cached = collators.get(locale);
|
|
540
|
+
if (cached !== undefined) {
|
|
541
|
+
collators.delete(locale);
|
|
542
|
+
collators.set(locale, cached);
|
|
543
|
+
return cached;
|
|
544
|
+
}
|
|
545
|
+
let created;
|
|
546
|
+
try {
|
|
547
|
+
created = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" });
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
throw new TypeError(`Unsupported collation: ${displayName}`);
|
|
551
|
+
}
|
|
552
|
+
if (collators.size >= MAX_COLLATOR_CACHE_ENTRIES) {
|
|
553
|
+
const oldest = collators.keys().next().value;
|
|
554
|
+
if (oldest !== undefined)
|
|
555
|
+
collators.delete(oldest);
|
|
556
|
+
}
|
|
557
|
+
collators.set(locale, created);
|
|
558
|
+
return created;
|
|
559
|
+
}
|
|
560
|
+
export function externalSqlDomainValue(value) {
|
|
561
|
+
if (typeof value !== "string")
|
|
562
|
+
return value;
|
|
563
|
+
if (value.startsWith(TEXT_VALUE))
|
|
564
|
+
return value.slice(TEXT_VALUE.length);
|
|
565
|
+
for (const prefix of [NUMERIC, JSON_VALUE, JSONB_VALUE, UUID_VALUE, TIME_VALUE]) {
|
|
566
|
+
if (value.startsWith(prefix))
|
|
567
|
+
return value.slice(prefix.length);
|
|
568
|
+
}
|
|
569
|
+
if (value.startsWith(INTERVAL_VALUE)) {
|
|
570
|
+
const decoded = JSON.parse(value.slice(INTERVAL_VALUE.length));
|
|
571
|
+
return `${String(decoded[0])} mons ${String(decoded[1])} days ${String(decoded[2])} usecs`;
|
|
572
|
+
}
|
|
573
|
+
if (value.startsWith(ARRAY_VALUE))
|
|
574
|
+
return value.slice(ARRAY_VALUE.length);
|
|
575
|
+
if (value.startsWith(COLLATION_VALUE)) {
|
|
576
|
+
return JSON.parse(value.slice(COLLATION_VALUE.length))[1];
|
|
577
|
+
}
|
|
578
|
+
if (value.startsWith(ENUM_VALUE)) {
|
|
579
|
+
return JSON.parse(value.slice(ENUM_VALUE.length))[2];
|
|
580
|
+
}
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
export function isSqlDomainValue(value) {
|
|
584
|
+
return typeof value === "string" && value.startsWith(PREFIX);
|
|
585
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Catalog } from "./catalog.js";
|
|
2
|
+
import type { ExecuteResult, QueryCursorOptions, QueryOptions } from "./database.js";
|
|
3
|
+
import type { QueryResult, QueryValue } from "./query.js";
|
|
4
|
+
/**
|
|
5
|
+
* The stable SQL boundary used by clients and third-party adapters.
|
|
6
|
+
*
|
|
7
|
+
* It is structural: both `MinnowDatabase` and `MinnowDatabaseClient` implement it without a
|
|
8
|
+
* wrapper. Adapters should emit PostgreSQL-style SQL and keep engine-specific APIs outside
|
|
9
|
+
* this small contract.
|
|
10
|
+
*/
|
|
11
|
+
export interface MinnowSqlExecutor {
|
|
12
|
+
query(sql: string, options?: QueryOptions): Promise<QueryResult>;
|
|
13
|
+
queryCursor(sql: string, options?: QueryCursorOptions): AsyncIterableIterator<QueryResult>;
|
|
14
|
+
execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
|
|
15
|
+
}
|
|
16
|
+
/** SQL execution plus the catalog surface schema-aware adapters need. */
|
|
17
|
+
export interface MinnowSqlDriver extends MinnowSqlExecutor {
|
|
18
|
+
introspect(): Promise<Catalog>;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* forms need a row-producing operator (JSON_TABLE) the executors do not have.
|
|
2
|
+
* SQL/JSON scalar support (T801 family). Documents may be ordinary JSON text or native JSON/JSONB
|
|
3
|
+
* domain values carried by the string vector representation. Scalar paths take one unambiguous
|
|
4
|
+
* result: `$`, member steps, and array subscripts. JSON_TABLE owns its separate `$`/`$[*]`
|
|
5
|
+
* row-producing subset in the parser.
|
|
7
6
|
*/
|
|
8
7
|
interface JsonPathStep {
|
|
9
8
|
kind: "member" | "index";
|
|
@@ -21,10 +20,9 @@ export declare function jsonIsValid(document: unknown, kind: string): boolean;
|
|
|
21
20
|
/** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
|
|
22
21
|
export declare function jsonValueOf(value: unknown): unknown;
|
|
23
22
|
/**
|
|
24
|
-
* JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812).
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). The omitted null-handling clause is
|
|
24
|
+
* `NULL ON NULL`: SQL NULL becomes a JSON null. `ABSENT ON NULL` is a separate spelling rather
|
|
25
|
+
* than the default. Constructors return JSON text at the JavaScript boundary.
|
|
27
26
|
*/
|
|
28
27
|
export declare function jsonConstructor(name: "JSON_ARRAY" | "JSON_OBJECT", values: readonly unknown[]): string;
|
|
29
28
|
export {};
|
|
30
|
-
//# sourceMappingURL=sql-json.d.ts.map
|