@minnowdb/core 0.6.9 → 0.7.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/dist/block-format/block.js +254 -325
- package/dist/block-format/checksum.js +30 -52
- package/dist/block-format/codecs.js +112 -125
- package/dist/block-format/column.js +187 -205
- package/dist/block-format/index.js +5 -1
- package/dist/block-format/physical.js +371 -387
- package/dist/block-format/types.js +4 -1
- package/dist/block-format/unicode.js +30 -39
- package/dist/date-value.d.ts +5 -0
- package/dist/date-value.js +71 -41
- package/dist/engine/artifact-cache.js +106 -116
- package/dist/engine/batch.js +36 -44
- package/dist/engine/buffered-writer.js +156 -158
- package/dist/engine/byte-estimates.js +20 -22
- package/dist/engine/cache-limits.js +24 -25
- package/dist/engine/cancellation.js +5 -3
- package/dist/engine/catalog.js +64 -73
- package/dist/engine/client.js +715 -780
- package/dist/engine/database.js +15952 -18465
- package/dist/engine/defaults.js +104 -111
- package/dist/engine/errors.js +113 -128
- package/dist/engine/fts.js +233 -280
- package/dist/engine/group-index.js +287 -321
- package/dist/engine/index.js +23 -12
- package/dist/engine/join-index.js +177 -186
- package/dist/engine/keyed-live.js +168 -173
- package/dist/engine/live-api.js +0 -1
- package/dist/engine/live-equal.js +32 -37
- package/dist/engine/live.js +603 -647
- package/dist/engine/memory.js +109 -111
- package/dist/engine/optimizer.d.ts +19 -1
- package/dist/engine/optimizer.js +2642 -2515
- package/dist/engine/point-read.d.ts +4 -2
- package/dist/engine/point-read.js +121 -171
- package/dist/engine/query-api.js +12 -3
- package/dist/engine/query-cache.js +61 -69
- package/dist/engine/query.d.ts +45 -0
- package/dist/engine/query.js +8506 -9375
- package/dist/engine/result-wire.js +221 -252
- package/dist/engine/schema-wire.js +97 -112
- package/dist/engine/schema.js +1099 -1254
- package/dist/engine/sort-keys.js +315 -389
- package/dist/engine/sql-domains.d.ts +7 -1
- package/dist/engine/sql-domains.js +648 -734
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-functions.d.ts +11 -0
- package/dist/engine/sql-functions.js +1092 -0
- package/dist/engine/sql-json.js +199 -218
- package/dist/engine/sql-semantics.d.ts +25 -4
- package/dist/engine/sql-semantics.js +579 -513
- package/dist/engine/typed-live.js +271 -293
- package/dist/engine/vector.d.ts +7 -1
- package/dist/engine/vector.js +4513 -4580
- package/dist/engine/worker-host.d.ts +8 -63
- package/dist/engine/worker-host.js +28 -1130
- package/dist/engine/worker-indexeddb.d.ts +1 -0
- package/dist/engine/worker-indexeddb.js +3 -0
- package/dist/engine/worker-memory.d.ts +1 -0
- package/dist/engine/worker-memory.js +3 -0
- package/dist/engine/worker-opfs.d.ts +1 -0
- package/dist/engine/worker-opfs.js +3 -0
- package/dist/engine/worker-server.d.ts +93 -0
- package/dist/engine/worker-server.js +999 -0
- package/dist/engine/worker-store-indexeddb.d.ts +2 -0
- package/dist/engine/worker-store-indexeddb.js +11 -0
- package/dist/engine/worker-store-memory.d.ts +2 -0
- package/dist/engine/worker-store-memory.js +6 -0
- package/dist/engine/worker-store-opfs.d.ts +2 -0
- package/dist/engine/worker-store-opfs.js +9 -0
- package/dist/engine/worker.js +0 -12
- package/dist/engine/write-block-planner.js +93 -100
- package/dist/plan/index.js +15 -16
- package/dist/plan/model.d.ts +9 -1
- package/dist/plan/model.js +20 -27
- package/dist/storage/index.js +0 -12
- package/dist/storage/indexeddb.js +11776 -13171
- package/dist/storage/memory.js +1198 -1241
- package/dist/storage/opfs/files.js +238 -295
- package/dist/storage/opfs/index.js +9 -3
- package/dist/storage/opfs/leader.js +4386 -4856
- package/dist/storage/opfs/rpc.js +223 -259
- package/dist/storage/opfs/snapshot-ledger.js +219 -260
- package/dist/storage/opfs/store.js +884 -1003
- package/dist/storage/persistence.js +59 -74
- package/dist/storage/snapshot-stream.js +788 -826
- package/dist/storage/snapshot.js +36 -37
- package/dist/storage/toolkit/extents.js +444 -520
- package/dist/storage/toolkit/index.js +28 -29
- package/dist/storage/toolkit/record-core.js +5792 -6377
- package/dist/storage/toolkit/sync-file.js +34 -42
- package/dist/storage/toolkit/wal.js +92 -127
- package/dist/storage/toolkit/wire.js +232 -256
- package/dist/storage/types.js +2952 -3228
- package/dist/testing/block-store-conformance.js +1556 -1629
- package/dist/testing/index.js +310 -291
- package/dist/testing/opfs-shim.js +280 -312
- package/dist/testing/simulator.js +496 -534
- package/dist/testing/sqllogictest.js +425 -365
- package/dist/transactions/index.js +1464 -1712
- package/dist/worker-protocol/index.js +70 -69
- package/package.json +20 -2
- package/postgres-feature-profile.json +15 -5
- package/sql-feature-matrix.json +252 -2
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { assertWellFormedString } from "../block-format/unicode.js";
|
|
2
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
|
|
4
|
-
|
|
5
|
-
const PREFIX = "\u0000minnow-domain:";
|
|
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
|
+
const PREFIX = "\0minnow-domain:";
|
|
6
5
|
const TEXT_VALUE = `${PREFIX}text:`;
|
|
7
6
|
const NUMERIC = `${PREFIX}numeric:`;
|
|
8
7
|
const JSON_VALUE = `${PREFIX}json:`;
|
|
@@ -14,773 +13,688 @@ const INTERVAL_VALUE = `${PREFIX}interval:`;
|
|
|
14
13
|
const ARRAY_VALUE = `${PREFIX}array:`;
|
|
15
14
|
const COLLATION_VALUE = `${PREFIX}collation:`;
|
|
16
15
|
const ENUM_VALUE = `${PREFIX}enum:`;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export function protectedSqlTextValue(value) {
|
|
23
|
-
return value.startsWith(PREFIX) ? TEXT_VALUE + value : value;
|
|
24
|
-
}
|
|
25
|
-
/** Removes only the ordinary-TEXT wrapper, leaving real domain values tagged. */
|
|
26
|
-
export function externalSqlTextValue(value) {
|
|
27
|
-
return typeof value === "string" && value.startsWith(TEXT_VALUE)
|
|
28
|
-
? value.slice(TEXT_VALUE.length)
|
|
29
|
-
: value;
|
|
16
|
+
function protectedSqlTextValue(value) {
|
|
17
|
+
return value.startsWith(PREFIX) ? TEXT_VALUE + value : value;
|
|
18
|
+
}
|
|
19
|
+
function externalSqlTextValue(value) {
|
|
20
|
+
return typeof value === "string" && value.startsWith(TEXT_VALUE) ? value.slice(TEXT_VALUE.length) : value;
|
|
30
21
|
}
|
|
31
22
|
function pow10(exponent) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// of decimal digits. Native exponentiation constructs only the requested result.
|
|
37
|
-
return 10n ** BigInt(exponent);
|
|
23
|
+
if (!Number.isSafeInteger(exponent) || exponent < 0 || exponent > 1e5) {
|
|
24
|
+
throw new RangeError(`Decimal scale is outside the supported range: ${String(exponent)}`);
|
|
25
|
+
}
|
|
26
|
+
return 10n ** BigInt(exponent);
|
|
38
27
|
}
|
|
39
28
|
function decimalParts(value) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
29
|
+
const source = String(value).trim();
|
|
30
|
+
assertBoundedDomainString(source, "NUMERIC value");
|
|
31
|
+
const match = /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?\d+))?$/.exec(source);
|
|
32
|
+
if (match === null)
|
|
33
|
+
throw new TypeError(`Invalid NUMERIC value: ${String(value)}`);
|
|
34
|
+
const fraction = match[3] ?? match[4] ?? "";
|
|
35
|
+
const digits = `${match[2] ?? "0"}${fraction}`.replace(/^0+(?=\d)/, "");
|
|
36
|
+
if (digits.length > MAX_SQL_NUMERIC_DIGITS) {
|
|
37
|
+
throw new RangeError(`NUMERIC value exceeds ${String(MAX_SQL_NUMERIC_DIGITS)} significant digits`);
|
|
38
|
+
}
|
|
39
|
+
const exponent = Number(match[5] ?? 0);
|
|
40
|
+
if (!Number.isSafeInteger(exponent))
|
|
41
|
+
throw new RangeError(`Invalid NUMERIC exponent: ${source}`);
|
|
42
|
+
let coefficient = BigInt(digits || "0") * (match[1] === "-" ? -1n : 1n);
|
|
43
|
+
let scale = fraction.length - exponent;
|
|
44
|
+
if (scale < 0) {
|
|
45
|
+
if (-scale > 1e5) {
|
|
46
|
+
throw new RangeError(`NUMERIC exponent is outside the supported range: ${source}`);
|
|
47
|
+
}
|
|
48
|
+
coefficient *= pow10(-scale);
|
|
49
|
+
scale = 0;
|
|
50
|
+
}
|
|
51
|
+
return { coefficient, scale };
|
|
63
52
|
}
|
|
64
53
|
function normalizeDecimal(parts) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
let { coefficient, scale } = parts;
|
|
55
|
+
while (scale > 0 && coefficient % 10n === 0n) {
|
|
56
|
+
coefficient /= 10n;
|
|
57
|
+
scale -= 1;
|
|
58
|
+
}
|
|
59
|
+
return { coefficient, scale };
|
|
71
60
|
}
|
|
72
61
|
function rescaleDecimal(parts, scale) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
62
|
+
if (!Number.isSafeInteger(scale) || scale < 0 || scale > 1e5) {
|
|
63
|
+
throw new RangeError(`NUMERIC scale is outside the supported range: ${String(scale)}`);
|
|
64
|
+
}
|
|
65
|
+
if (parts.scale <= scale) {
|
|
66
|
+
return { coefficient: parts.coefficient * pow10(scale - parts.scale), scale };
|
|
67
|
+
}
|
|
68
|
+
const divisor = pow10(parts.scale - scale);
|
|
69
|
+
let quotient = parts.coefficient / divisor;
|
|
70
|
+
const remainder = parts.coefficient % divisor;
|
|
71
|
+
if ((remainder < 0n ? -remainder : remainder) * 2n >= divisor) {
|
|
72
|
+
quotient += parts.coefficient < 0n ? -1n : 1n;
|
|
73
|
+
}
|
|
74
|
+
return { coefficient: quotient, scale };
|
|
86
75
|
}
|
|
87
76
|
function formatDecimal(parts) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
77
|
+
const negative = parts.coefficient < 0n;
|
|
78
|
+
const digits = (negative ? -parts.coefficient : parts.coefficient).toString();
|
|
79
|
+
if (parts.scale === 0)
|
|
80
|
+
return `${negative ? "-" : ""}${digits}`;
|
|
81
|
+
const padded = digits.padStart(parts.scale + 1, "0");
|
|
82
|
+
const split = padded.length - parts.scale;
|
|
83
|
+
return `${negative ? "-" : ""}${padded.slice(0, split)}.${padded.slice(split)}`;
|
|
95
84
|
}
|
|
96
85
|
function taggedDecimalParts(value) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (precision
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* canonical rendering re-parses to the same decimal value, so `0.1` qualifies while
|
|
144
|
-
* `9007199254740993` does not — or undefined when the trip through a number would visibly
|
|
145
|
-
* round. A value that survives stays an ordinary number so every number-typed path keeps
|
|
146
|
-
* running; only visibly rounded values stay tagged NUMERIC.
|
|
147
|
-
*/
|
|
148
|
-
export function exactNumericAsNumber(value) {
|
|
149
|
-
const parts = taggedDecimalParts(value);
|
|
150
|
-
if (parts === undefined)
|
|
151
|
-
return undefined;
|
|
152
|
-
const candidate = Number(value.slice(NUMERIC.length));
|
|
153
|
-
if (!Number.isFinite(candidate))
|
|
154
|
-
return undefined;
|
|
155
|
-
const roundTrip = normalizeDecimal(decimalParts(String(candidate)));
|
|
156
|
-
const exact = normalizeDecimal(parts);
|
|
157
|
-
return roundTrip.coefficient === exact.coefficient && roundTrip.scale === exact.scale
|
|
158
|
-
? candidate
|
|
159
|
-
: undefined;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* The leading base-10000 digit of a decimal, aligned at the decimal point the way PostgreSQL's
|
|
163
|
-
* NUMERIC digit array is: group k covers the decimal digits at 10^(4k)..10^(4k+3). `weight` is
|
|
164
|
-
* the group index of the first nonzero group and `firstDigit` its value (1..9999); zero reports
|
|
165
|
-
* both as 0, matching PostgreSQL's defaults when no nonzero digit exists.
|
|
166
|
-
*/
|
|
86
|
+
if (typeof value !== "string" || !value.startsWith(NUMERIC))
|
|
87
|
+
return void 0;
|
|
88
|
+
return decimalParts(value.slice(NUMERIC.length));
|
|
89
|
+
}
|
|
90
|
+
function isExactNumeric(value) {
|
|
91
|
+
return taggedDecimalParts(value) !== void 0;
|
|
92
|
+
}
|
|
93
|
+
function exactNumericValue(value, precision, scale) {
|
|
94
|
+
if (value === null || value === void 0)
|
|
95
|
+
return null;
|
|
96
|
+
const raw = taggedDecimalParts(value) ?? (typeof value === "number" || typeof value === "string" ? decimalParts(value) : void 0);
|
|
97
|
+
if (raw === void 0)
|
|
98
|
+
throw new TypeError("NUMERIC accepts a number or decimal string");
|
|
99
|
+
const adjusted = scale === void 0 ? normalizeDecimal(raw) : rescaleDecimal(raw, scale);
|
|
100
|
+
if (precision !== void 0) {
|
|
101
|
+
if (!Number.isSafeInteger(precision) || precision < 1 || precision > 1e5) {
|
|
102
|
+
throw new RangeError(`NUMERIC precision is outside the supported range: ${String(precision)}`);
|
|
103
|
+
}
|
|
104
|
+
const declaredScale = scale ?? 0;
|
|
105
|
+
if (declaredScale > precision) {
|
|
106
|
+
throw new TypeError("NUMERIC scale cannot exceed its precision");
|
|
107
|
+
}
|
|
108
|
+
const integer = adjusted.coefficient < 0n ? -adjusted.coefficient : adjusted.coefficient;
|
|
109
|
+
const integerDigits = (integer / pow10(adjusted.scale)).toString().length;
|
|
110
|
+
if (integer !== 0n && integerDigits > precision - declaredScale) {
|
|
111
|
+
throw new RangeError(`NUMERIC(${String(precision)}, ${String(declaredScale)}) overflow`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(adjusted)), "NUMERIC value");
|
|
115
|
+
}
|
|
116
|
+
function exactNumericLiteral(text) {
|
|
117
|
+
const parts = decimalParts(text);
|
|
118
|
+
const plain = /[eE]/.test(text) ? formatDecimal(parts) : text;
|
|
119
|
+
return boundedTaggedDomainValue(NUMERIC, plain, "NUMERIC literal");
|
|
120
|
+
}
|
|
121
|
+
function exactNumericAsNumber(value) {
|
|
122
|
+
const parts = taggedDecimalParts(value);
|
|
123
|
+
if (parts === void 0)
|
|
124
|
+
return void 0;
|
|
125
|
+
const candidate = Number(value.slice(NUMERIC.length));
|
|
126
|
+
if (!Number.isFinite(candidate))
|
|
127
|
+
return void 0;
|
|
128
|
+
const roundTrip = normalizeDecimal(decimalParts(String(candidate)));
|
|
129
|
+
const exact = normalizeDecimal(parts);
|
|
130
|
+
return roundTrip.coefficient === exact.coefficient && roundTrip.scale === exact.scale ? candidate : void 0;
|
|
131
|
+
}
|
|
167
132
|
function nbaseLeading(parts) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
133
|
+
const magnitude = parts.coefficient < 0n ? -parts.coefficient : parts.coefficient;
|
|
134
|
+
if (magnitude === 0n)
|
|
135
|
+
return { weight: 0, firstDigit: 0 };
|
|
136
|
+
const fractionGroups = Math.ceil(parts.scale / 4);
|
|
137
|
+
const aligned = magnitude * pow10(fractionGroups * 4 - parts.scale);
|
|
138
|
+
const groups = Math.ceil(aligned.toString().length / 4);
|
|
139
|
+
return {
|
|
140
|
+
weight: groups - 1 - fractionGroups,
|
|
141
|
+
firstDigit: Number(aligned / pow10((groups - 1) * 4))
|
|
142
|
+
};
|
|
178
143
|
}
|
|
179
|
-
/**
|
|
180
|
-
* PostgreSQL's `select_div_scale`: the fractional digits a NUMERIC quotient is computed and
|
|
181
|
-
* rounded to. The estimated quotient weight guarantees roughly sixteen significant digits, and
|
|
182
|
-
* the result never displays fewer fractional digits than either operand. `minimumScale` stands
|
|
183
|
-
* in for display scale the canonical encoding cannot carry: PostgreSQL floors this selection at
|
|
184
|
-
* each operand's dscale, which for an AVG over a declared-scale column is that declared scale.
|
|
185
|
-
*/
|
|
186
144
|
function quotientScale(a, b, minimumScale) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const b = rightTagged ?? decimalParts(typeof right === "number" ? right : String(right));
|
|
207
|
-
if ((operator === "/" || operator === "%") && b.coefficient === 0n)
|
|
208
|
-
return null;
|
|
209
|
-
let result;
|
|
210
|
-
if (operator === "+" || operator === "-") {
|
|
211
|
-
const scale = Math.max(a.scale, b.scale);
|
|
212
|
-
const ac = a.coefficient * pow10(scale - a.scale);
|
|
213
|
-
const bc = b.coefficient * pow10(scale - b.scale);
|
|
214
|
-
result = { coefficient: operator === "+" ? ac + bc : ac - bc, scale };
|
|
215
|
-
}
|
|
216
|
-
else if (operator === "*") {
|
|
217
|
-
result = { coefficient: a.coefficient * b.coefficient, scale: a.scale + b.scale };
|
|
218
|
-
}
|
|
219
|
-
else if (operator === "%") {
|
|
220
|
-
const scale = Math.max(a.scale, b.scale);
|
|
221
|
-
result = {
|
|
222
|
-
coefficient: (a.coefficient * pow10(scale - a.scale)) % (b.coefficient * pow10(scale - b.scale)),
|
|
223
|
-
scale,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
// PostgreSQL NUMERIC division computes to its selected result scale and rounds the final
|
|
228
|
-
// digit half away from zero; matching the scale selection keeps every rendered digit in
|
|
229
|
-
// agreement, deterministically and without ever crossing binary Float64.
|
|
230
|
-
const scale = quotientScale(a, b, minimumQuotientScale);
|
|
231
|
-
const numerator = a.coefficient * pow10(scale + b.scale);
|
|
232
|
-
const denominator = b.coefficient * pow10(a.scale);
|
|
233
|
-
let coefficient = numerator / denominator;
|
|
234
|
-
const remainder = numerator % denominator;
|
|
235
|
-
const magnitude = denominator < 0n ? -denominator : denominator;
|
|
236
|
-
if ((remainder < 0n ? -remainder : remainder) * 2n >= magnitude) {
|
|
237
|
-
coefficient += numerator < 0n !== denominator < 0n ? -1n : 1n;
|
|
238
|
-
}
|
|
239
|
-
result = { coefficient, scale };
|
|
240
|
-
}
|
|
241
|
-
return boundedTaggedDomainValue(NUMERIC, formatDecimal(canonicalize ? normalizeDecimal(result) : result), "NUMERIC result");
|
|
242
|
-
}
|
|
243
|
-
export function exactNumericCompare(left, right) {
|
|
244
|
-
const leftTagged = taggedDecimalParts(left);
|
|
245
|
-
const rightTagged = taggedDecimalParts(right);
|
|
246
|
-
if (leftTagged === undefined && rightTagged === undefined)
|
|
247
|
-
return undefined;
|
|
248
|
-
const a = leftTagged ??
|
|
249
|
-
(typeof left === "number" || typeof left === "string" ? decimalParts(left) : undefined);
|
|
250
|
-
const b = rightTagged ??
|
|
251
|
-
(typeof right === "number" || typeof right === "string" ? decimalParts(right) : undefined);
|
|
252
|
-
if (a === undefined || b === undefined)
|
|
253
|
-
return undefined;
|
|
145
|
+
const leftLeading = nbaseLeading(a);
|
|
146
|
+
const rightLeading = nbaseLeading(b);
|
|
147
|
+
let estimatedWeight = leftLeading.weight - rightLeading.weight;
|
|
148
|
+
if (leftLeading.firstDigit <= rightLeading.firstDigit)
|
|
149
|
+
estimatedWeight -= 1;
|
|
150
|
+
const scale = Math.max(16 - estimatedWeight * 4, a.scale, b.scale, minimumScale, 0);
|
|
151
|
+
return Math.min(scale, 1e3);
|
|
152
|
+
}
|
|
153
|
+
function exactNumericBinary(operator, left, right, minimumQuotientScale = 0, canonicalize = true) {
|
|
154
|
+
const leftTagged = taggedDecimalParts(left);
|
|
155
|
+
const rightTagged = taggedDecimalParts(right);
|
|
156
|
+
if (leftTagged === void 0 && rightTagged === void 0)
|
|
157
|
+
return void 0;
|
|
158
|
+
const a = leftTagged ?? decimalParts(typeof left === "number" ? left : String(left));
|
|
159
|
+
const b = rightTagged ?? decimalParts(typeof right === "number" ? right : String(right));
|
|
160
|
+
if ((operator === "/" || operator === "%") && b.coefficient === 0n)
|
|
161
|
+
return null;
|
|
162
|
+
let result;
|
|
163
|
+
if (operator === "+" || operator === "-") {
|
|
254
164
|
const scale = Math.max(a.scale, b.scale);
|
|
255
165
|
const ac = a.coefficient * pow10(scale - a.scale);
|
|
256
166
|
const bc = b.coefficient * pow10(scale - b.scale);
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
167
|
+
result = { coefficient: operator === "+" ? ac + bc : ac - bc, scale };
|
|
168
|
+
} else if (operator === "*") {
|
|
169
|
+
result = { coefficient: a.coefficient * b.coefficient, scale: a.scale + b.scale };
|
|
170
|
+
} else if (operator === "%") {
|
|
171
|
+
const scale = Math.max(a.scale, b.scale);
|
|
172
|
+
result = {
|
|
173
|
+
coefficient: a.coefficient * pow10(scale - a.scale) % (b.coefficient * pow10(scale - b.scale)),
|
|
174
|
+
scale
|
|
175
|
+
};
|
|
176
|
+
} else {
|
|
177
|
+
const scale = quotientScale(a, b, minimumQuotientScale);
|
|
178
|
+
const numerator = a.coefficient * pow10(scale + b.scale);
|
|
179
|
+
const denominator = b.coefficient * pow10(a.scale);
|
|
180
|
+
let coefficient = numerator / denominator;
|
|
181
|
+
const remainder = numerator % denominator;
|
|
182
|
+
const magnitude = denominator < 0n ? -denominator : denominator;
|
|
183
|
+
if ((remainder < 0n ? -remainder : remainder) * 2n >= magnitude) {
|
|
184
|
+
coefficient += numerator < 0n !== denominator < 0n ? -1n : 1n;
|
|
185
|
+
}
|
|
186
|
+
result = { coefficient, scale };
|
|
187
|
+
}
|
|
188
|
+
return boundedTaggedDomainValue(NUMERIC, formatDecimal(canonicalize ? normalizeDecimal(result) : result), "NUMERIC result");
|
|
189
|
+
}
|
|
190
|
+
function exactNumericCompare(left, right) {
|
|
191
|
+
const leftTagged = taggedDecimalParts(left);
|
|
192
|
+
const rightTagged = taggedDecimalParts(right);
|
|
193
|
+
if (leftTagged === void 0 && rightTagged === void 0)
|
|
194
|
+
return void 0;
|
|
195
|
+
const a = leftTagged ?? (typeof left === "number" || typeof left === "string" ? decimalParts(left) : void 0);
|
|
196
|
+
const b = rightTagged ?? (typeof right === "number" || typeof right === "string" ? decimalParts(right) : void 0);
|
|
197
|
+
if (a === void 0 || b === void 0)
|
|
198
|
+
return void 0;
|
|
199
|
+
const scale = Math.max(a.scale, b.scale);
|
|
200
|
+
const ac = a.coefficient * pow10(scale - a.scale);
|
|
201
|
+
const bc = b.coefficient * pow10(scale - b.scale);
|
|
202
|
+
return ac === bc ? 0 : ac < bc ? -1 : 1;
|
|
203
|
+
}
|
|
204
|
+
function boundedJsonText(value, canonical, label = "JSON value") {
|
|
205
|
+
const state = { active: /* @__PURE__ */ new Set(), pieces: [], items: 0, length: 0 };
|
|
206
|
+
encodeBoundedJson(value, canonical, label, 0, state);
|
|
207
|
+
return state.pieces.join("");
|
|
268
208
|
}
|
|
269
209
|
function encodeBoundedJson(value, canonical, label, depth, state) {
|
|
270
|
-
|
|
271
|
-
|
|
210
|
+
state.items += 1;
|
|
211
|
+
if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
|
|
212
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
213
|
+
}
|
|
214
|
+
if (depth > MAX_SQL_STRUCTURED_VALUE_DEPTH) {
|
|
215
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_DEPTH)} levels`);
|
|
216
|
+
}
|
|
217
|
+
if (value === null) {
|
|
218
|
+
appendJsonPiece(state, "null", label);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (typeof value === "boolean") {
|
|
222
|
+
appendJsonPiece(state, value ? "true" : "false", label);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (typeof value === "number") {
|
|
226
|
+
if (!Number.isFinite(value))
|
|
227
|
+
throw new TypeError("JSON numbers must be finite");
|
|
228
|
+
appendJsonPiece(state, JSON.stringify(value), label);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (typeof value === "string") {
|
|
232
|
+
assertBoundedDomainString(value, label);
|
|
233
|
+
appendJsonPiece(state, JSON.stringify(value), label);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (value instanceof Date) {
|
|
237
|
+
const timestamp = dateIsoString(value);
|
|
238
|
+
appendJsonPiece(state, JSON.stringify(timestamp), label);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (typeof value !== "object")
|
|
242
|
+
throw new TypeError(`${label} cannot be represented as JSON`);
|
|
243
|
+
const object = value;
|
|
244
|
+
if (state.active.has(object))
|
|
245
|
+
throw new TypeError(`${label} cannot contain a cycle`);
|
|
246
|
+
state.active.add(object);
|
|
247
|
+
try {
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
appendJsonPiece(state, "[", label);
|
|
250
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
251
|
+
if (index > 0)
|
|
252
|
+
appendJsonPiece(state, ",", label);
|
|
253
|
+
encodeBoundedJson(value[index], canonical, label, depth + 1, state);
|
|
254
|
+
}
|
|
255
|
+
appendJsonPiece(state, "]", label);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const prototype = Object.getPrototypeOf(value);
|
|
259
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
260
|
+
throw new TypeError(`${label} accepts only JSON objects, arrays, and scalar values`);
|
|
261
|
+
}
|
|
262
|
+
const record = value;
|
|
263
|
+
const keys = [];
|
|
264
|
+
for (const key in record) {
|
|
265
|
+
if (!Object.hasOwn(record, key))
|
|
266
|
+
continue;
|
|
267
|
+
if (keys.length >= MAX_SQL_STRUCTURED_VALUE_ITEMS - state.items) {
|
|
272
268
|
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (value instanceof Date) {
|
|
297
|
-
const timestamp = dateIsoString(value);
|
|
298
|
-
appendJsonPiece(state, JSON.stringify(timestamp), label);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (typeof value !== "object")
|
|
302
|
-
throw new TypeError(`${label} cannot be represented as JSON`);
|
|
303
|
-
const object = value;
|
|
304
|
-
if (state.active.has(object))
|
|
305
|
-
throw new TypeError(`${label} cannot contain a cycle`);
|
|
306
|
-
state.active.add(object);
|
|
307
|
-
try {
|
|
308
|
-
if (Array.isArray(value)) {
|
|
309
|
-
appendJsonPiece(state, "[", label);
|
|
310
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
311
|
-
if (index > 0)
|
|
312
|
-
appendJsonPiece(state, ",", label);
|
|
313
|
-
encodeBoundedJson(value[index], canonical, label, depth + 1, state);
|
|
314
|
-
}
|
|
315
|
-
appendJsonPiece(state, "]", label);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
const prototype = Object.getPrototypeOf(value);
|
|
319
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
320
|
-
throw new TypeError(`${label} accepts only JSON objects, arrays, and scalar values`);
|
|
321
|
-
}
|
|
322
|
-
const record = value;
|
|
323
|
-
// Object.keys() would allocate one array slot per property before the item cap can fire.
|
|
324
|
-
// Plain JSON objects need no prototype walk, so collect at most the admitted count instead.
|
|
325
|
-
const keys = [];
|
|
326
|
-
for (const key in record) {
|
|
327
|
-
if (!Object.hasOwn(record, key))
|
|
328
|
-
continue;
|
|
329
|
-
if (keys.length >= MAX_SQL_STRUCTURED_VALUE_ITEMS - state.items) {
|
|
330
|
-
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
331
|
-
}
|
|
332
|
-
keys.push(key);
|
|
333
|
-
}
|
|
334
|
-
if (canonical)
|
|
335
|
-
keys.sort();
|
|
336
|
-
appendJsonPiece(state, "{", label);
|
|
337
|
-
for (let index = 0; index < keys.length; index += 1) {
|
|
338
|
-
const key = keys[index] ?? "";
|
|
339
|
-
state.items += 1;
|
|
340
|
-
if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
|
|
341
|
-
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
342
|
-
}
|
|
343
|
-
assertBoundedDomainString(key, `${label} object name`);
|
|
344
|
-
if (index > 0)
|
|
345
|
-
appendJsonPiece(state, ",", label);
|
|
346
|
-
appendJsonPiece(state, JSON.stringify(key), label);
|
|
347
|
-
appendJsonPiece(state, ":", label);
|
|
348
|
-
encodeBoundedJson(record[key], canonical, label, depth + 1, state);
|
|
349
|
-
}
|
|
350
|
-
appendJsonPiece(state, "}", label);
|
|
351
|
-
}
|
|
352
|
-
finally {
|
|
353
|
-
state.active.delete(object);
|
|
354
|
-
}
|
|
269
|
+
}
|
|
270
|
+
keys.push(key);
|
|
271
|
+
}
|
|
272
|
+
if (canonical)
|
|
273
|
+
keys.sort();
|
|
274
|
+
appendJsonPiece(state, "{", label);
|
|
275
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
276
|
+
const key = keys[index] ?? "";
|
|
277
|
+
state.items += 1;
|
|
278
|
+
if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
|
|
279
|
+
throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
|
|
280
|
+
}
|
|
281
|
+
assertBoundedDomainString(key, `${label} object name`);
|
|
282
|
+
if (index > 0)
|
|
283
|
+
appendJsonPiece(state, ",", label);
|
|
284
|
+
appendJsonPiece(state, JSON.stringify(key), label);
|
|
285
|
+
appendJsonPiece(state, ":", label);
|
|
286
|
+
encodeBoundedJson(record[key], canonical, label, depth + 1, state);
|
|
287
|
+
}
|
|
288
|
+
appendJsonPiece(state, "}", label);
|
|
289
|
+
} finally {
|
|
290
|
+
state.active.delete(object);
|
|
291
|
+
}
|
|
355
292
|
}
|
|
356
293
|
function appendJsonPiece(state, piece, label) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
294
|
+
const next = state.length + piece.length;
|
|
295
|
+
if (!Number.isSafeInteger(next) || next > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
296
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
297
|
+
}
|
|
298
|
+
state.length = next;
|
|
299
|
+
state.pieces.push(piece);
|
|
363
300
|
}
|
|
364
301
|
function assertBoundedDomainString(value, label) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
? JSON_VALUE
|
|
379
|
-
: undefined;
|
|
380
|
-
const source = prefix === undefined ? value : value.slice(prefix.length);
|
|
381
|
-
assertBoundedDomainString(source, "JSON value");
|
|
382
|
-
try {
|
|
383
|
-
parsed = JSON.parse(source);
|
|
384
|
-
}
|
|
385
|
-
catch {
|
|
386
|
-
throw new TypeError("Invalid JSON value");
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
const text = boundedJsonText(parsed, binary, binary ? "JSONB value" : "JSON value");
|
|
390
|
-
return boundedTaggedDomainValue(binary ? JSONB_VALUE : JSON_VALUE, text, binary ? "JSONB value" : "JSON value");
|
|
391
|
-
}
|
|
392
|
-
/** Returns the JSON document carried by an internal JSON/JSONB scalar, if any. */
|
|
393
|
-
export function jsonDomainDocument(value) {
|
|
394
|
-
if (typeof value !== "string")
|
|
395
|
-
return undefined;
|
|
396
|
-
if (value.startsWith(JSONB_VALUE))
|
|
397
|
-
return value.slice(JSONB_VALUE.length);
|
|
398
|
-
if (value.startsWith(JSON_VALUE))
|
|
399
|
-
return value.slice(JSON_VALUE.length);
|
|
400
|
-
return undefined;
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Tags already-constructed JSON without parsing and re-stringifying it. The validation parse
|
|
404
|
-
* rejects malformed documents, while retaining duplicate object names and the constructor's
|
|
405
|
-
* exact member order for embedding in an outer JSON value.
|
|
406
|
-
*/
|
|
407
|
-
export function preservedJsonDomainValue(document, binary = false) {
|
|
408
|
-
assertBoundedDomainString(document, binary ? "JSONB value" : "JSON value");
|
|
302
|
+
if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
303
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
304
|
+
}
|
|
305
|
+
assertWellFormedString(value, label);
|
|
306
|
+
}
|
|
307
|
+
function jsonDomainValue(value, binary) {
|
|
308
|
+
if (value === null || value === void 0)
|
|
309
|
+
return null;
|
|
310
|
+
let parsed = value;
|
|
311
|
+
if (typeof value === "string") {
|
|
312
|
+
const prefix = value.startsWith(JSONB_VALUE) ? JSONB_VALUE : value.startsWith(JSON_VALUE) ? JSON_VALUE : void 0;
|
|
313
|
+
const source = prefix === void 0 ? value : value.slice(prefix.length);
|
|
314
|
+
assertBoundedDomainString(source, "JSON value");
|
|
409
315
|
try {
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
throw new TypeError("Invalid tagged INTERVAL value");
|
|
505
|
-
}
|
|
506
|
-
return value;
|
|
507
|
-
}
|
|
508
|
-
const source = value.trim();
|
|
509
|
-
let months = 0;
|
|
510
|
-
let days = 0;
|
|
511
|
-
let microseconds = 0;
|
|
512
|
-
let matched = 0;
|
|
513
|
-
const partPattern = /([+-]?\d+(?:\.\d+)?)\s*([a-zA-Z]+)/g;
|
|
514
|
-
const units = new Map([
|
|
515
|
-
["year", (amount) => (months += amount * 12)],
|
|
516
|
-
["month", (amount) => (months += amount)],
|
|
517
|
-
["week", (amount) => (days += amount * 7)],
|
|
518
|
-
["day", (amount) => (days += amount)],
|
|
519
|
-
["hour", (amount) => (microseconds += amount * 3_600_000_000)],
|
|
520
|
-
["minute", (amount) => (microseconds += amount * 60_000_000)],
|
|
521
|
-
["second", (amount) => (microseconds += amount * 1_000_000)],
|
|
522
|
-
]);
|
|
523
|
-
for (const part of source.matchAll(partPattern)) {
|
|
524
|
-
const unit = (part[2] ?? "").toLowerCase().replace(/s$/, "");
|
|
525
|
-
const apply = units.get(unit);
|
|
526
|
-
if (apply === undefined)
|
|
527
|
-
throw new TypeError(`Unknown INTERVAL unit: ${unit}`);
|
|
528
|
-
const amount = Number(part[1]);
|
|
529
|
-
if ((unit === "year" || unit === "month" || unit === "week" || unit === "day") &&
|
|
530
|
-
!Number.isInteger(amount)) {
|
|
531
|
-
throw new TypeError(`INTERVAL ${unit} must be a whole number`);
|
|
532
|
-
}
|
|
533
|
-
apply(amount);
|
|
534
|
-
matched += 1;
|
|
316
|
+
parsed = JSON.parse(source);
|
|
317
|
+
} catch {
|
|
318
|
+
throw new TypeError("Invalid JSON value");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const text = boundedJsonText(parsed, binary, binary ? "JSONB value" : "JSON value");
|
|
322
|
+
return boundedTaggedDomainValue(binary ? JSONB_VALUE : JSON_VALUE, text, binary ? "JSONB value" : "JSON value");
|
|
323
|
+
}
|
|
324
|
+
function jsonDomainDocument(value) {
|
|
325
|
+
if (typeof value !== "string")
|
|
326
|
+
return void 0;
|
|
327
|
+
if (value.startsWith(JSONB_VALUE))
|
|
328
|
+
return value.slice(JSONB_VALUE.length);
|
|
329
|
+
if (value.startsWith(JSON_VALUE))
|
|
330
|
+
return value.slice(JSON_VALUE.length);
|
|
331
|
+
return void 0;
|
|
332
|
+
}
|
|
333
|
+
function preservedJsonDomainValue(document, binary = false) {
|
|
334
|
+
assertBoundedDomainString(document, binary ? "JSONB value" : "JSON value");
|
|
335
|
+
try {
|
|
336
|
+
JSON.parse(document);
|
|
337
|
+
} catch {
|
|
338
|
+
throw new TypeError("Invalid JSON value");
|
|
339
|
+
}
|
|
340
|
+
return boundedTaggedDomainValue(binary ? JSONB_VALUE : JSON_VALUE, document, binary ? "JSONB value" : "JSON value");
|
|
341
|
+
}
|
|
342
|
+
function uuidDomainValue(value) {
|
|
343
|
+
if (value === null || value === void 0)
|
|
344
|
+
return null;
|
|
345
|
+
if (typeof value !== "string")
|
|
346
|
+
throw new TypeError("UUID accepts a string value");
|
|
347
|
+
assertBoundedDomainString(value, "UUID value");
|
|
348
|
+
const source = (value.startsWith(UUID_VALUE) ? value.slice(UUID_VALUE.length) : value).toLowerCase();
|
|
349
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(source)) {
|
|
350
|
+
throw new TypeError(`Invalid UUID value: ${value}`);
|
|
351
|
+
}
|
|
352
|
+
return UUID_VALUE + source;
|
|
353
|
+
}
|
|
354
|
+
function dateDomainValue(value) {
|
|
355
|
+
if (value === null || value === void 0)
|
|
356
|
+
return null;
|
|
357
|
+
let source;
|
|
358
|
+
if (value instanceof Date) {
|
|
359
|
+
if (!Number.isFinite(value.getTime()))
|
|
360
|
+
throw new TypeError("DATE accepts a valid Date value");
|
|
361
|
+
source = dateIsoString(value).slice(0, 10);
|
|
362
|
+
} else if (typeof value === "string") {
|
|
363
|
+
assertBoundedDomainString(value, "DATE value");
|
|
364
|
+
source = value.startsWith(DATE_VALUE) ? value.slice(DATE_VALUE.length) : value;
|
|
365
|
+
} else {
|
|
366
|
+
throw new TypeError("DATE accepts a YYYY-MM-DD string value");
|
|
367
|
+
}
|
|
368
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(source);
|
|
369
|
+
const year = Number(match?.[1]);
|
|
370
|
+
const month = Number(match?.[2]);
|
|
371
|
+
const day = Number(match?.[3]);
|
|
372
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
373
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
374
|
+
if (match === null || year < 1 || month < 1 || month > 12 || day < 1 || day > (days[month - 1] ?? 0)) {
|
|
375
|
+
throw new TypeError(`Invalid DATE value: ${String(value)}`);
|
|
376
|
+
}
|
|
377
|
+
return DATE_VALUE + source;
|
|
378
|
+
}
|
|
379
|
+
function isDateDomainValue(value) {
|
|
380
|
+
return typeof value === "string" && value.startsWith(DATE_VALUE);
|
|
381
|
+
}
|
|
382
|
+
function timeDomainValue(value) {
|
|
383
|
+
if (value === null || value === void 0)
|
|
384
|
+
return null;
|
|
385
|
+
if (typeof value !== "string")
|
|
386
|
+
throw new TypeError("TIME accepts a string value");
|
|
387
|
+
assertBoundedDomainString(value, "TIME value");
|
|
388
|
+
const source = value.startsWith(TIME_VALUE) ? value.slice(TIME_VALUE.length) : value;
|
|
389
|
+
const match = /^(\d{2}):(\d{2})(?::(\d{2})(\.\d{1,6})?)?$/.exec(source);
|
|
390
|
+
const hours = match?.[1];
|
|
391
|
+
const minutes = match?.[2];
|
|
392
|
+
if (match === null || hours === void 0 || minutes === void 0 || Number(hours) > 23 || Number(minutes) > 59 || Number(match[3] ?? 0) > 59) {
|
|
393
|
+
throw new TypeError(`Invalid TIME value: ${value}`);
|
|
394
|
+
}
|
|
395
|
+
return TIME_VALUE + `${hours}:${minutes}:${match[3] ?? "00"}${match[4] ?? ""}`;
|
|
396
|
+
}
|
|
397
|
+
function intervalDomainValue(value) {
|
|
398
|
+
if (value === null || value === void 0)
|
|
399
|
+
return null;
|
|
400
|
+
if (typeof value !== "string")
|
|
401
|
+
throw new TypeError("INTERVAL accepts a string value");
|
|
402
|
+
assertBoundedDomainString(value, "INTERVAL value");
|
|
403
|
+
if (value.startsWith(INTERVAL_VALUE)) {
|
|
404
|
+
const source2 = value.slice(INTERVAL_VALUE.length);
|
|
405
|
+
let decoded;
|
|
406
|
+
try {
|
|
407
|
+
decoded = JSON.parse(source2);
|
|
408
|
+
} catch {
|
|
409
|
+
throw new TypeError("Invalid tagged INTERVAL value");
|
|
535
410
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
residue !== "" ||
|
|
539
|
-
!Number.isSafeInteger(months) ||
|
|
540
|
-
!Number.isSafeInteger(days) ||
|
|
541
|
-
!Number.isSafeInteger(microseconds)) {
|
|
542
|
-
throw new TypeError(`Invalid INTERVAL value: ${value}`);
|
|
411
|
+
if (!Array.isArray(decoded) || decoded.length !== 3 || !decoded.every((part) => Number.isSafeInteger(part)) || JSON.stringify(decoded) !== source2) {
|
|
412
|
+
throw new TypeError("Invalid tagged INTERVAL value");
|
|
543
413
|
}
|
|
544
|
-
return
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
414
|
+
return value;
|
|
415
|
+
}
|
|
416
|
+
const source = value.trim();
|
|
417
|
+
let months = 0;
|
|
418
|
+
let days = 0;
|
|
419
|
+
let microseconds = 0;
|
|
420
|
+
let matched = 0;
|
|
421
|
+
const partPattern = /([+-]?\d+(?:\.\d+)?)\s*([a-zA-Z]+)/g;
|
|
422
|
+
const units = /* @__PURE__ */ new Map([
|
|
423
|
+
["year", (amount) => months += amount * 12],
|
|
424
|
+
["month", (amount) => months += amount],
|
|
425
|
+
["week", (amount) => days += amount * 7],
|
|
426
|
+
["day", (amount) => days += amount],
|
|
427
|
+
["hour", (amount) => microseconds += amount * 36e8],
|
|
428
|
+
["minute", (amount) => microseconds += amount * 6e7],
|
|
429
|
+
["second", (amount) => microseconds += amount * 1e6]
|
|
430
|
+
]);
|
|
431
|
+
for (const part of source.matchAll(partPattern)) {
|
|
432
|
+
const unit = (part[2] ?? "").toLowerCase().replace(/s$/, "");
|
|
433
|
+
const apply = units.get(unit);
|
|
434
|
+
if (apply === void 0)
|
|
435
|
+
throw new TypeError(`Unknown INTERVAL unit: ${unit}`);
|
|
436
|
+
const amount = Number(part[1]);
|
|
437
|
+
if ((unit === "year" || unit === "month" || unit === "week" || unit === "day") && !Number.isInteger(amount)) {
|
|
438
|
+
throw new TypeError(`INTERVAL ${unit} must be a whole number`);
|
|
439
|
+
}
|
|
440
|
+
apply(amount);
|
|
441
|
+
matched += 1;
|
|
442
|
+
}
|
|
443
|
+
const residue = source.replace(partPattern, "").trim();
|
|
444
|
+
if (matched === 0 || residue !== "" || !Number.isSafeInteger(months) || !Number.isSafeInteger(days) || !Number.isSafeInteger(microseconds)) {
|
|
445
|
+
throw new TypeError(`Invalid INTERVAL value: ${value}`);
|
|
446
|
+
}
|
|
447
|
+
return boundedTaggedDomainValue(INTERVAL_VALUE, JSON.stringify([months, days, microseconds]), "INTERVAL value");
|
|
448
|
+
}
|
|
449
|
+
function arrayDomainValue(values) {
|
|
450
|
+
return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(values.map(externalSqlDomainValue), true, "ARRAY value"), "ARRAY value");
|
|
548
451
|
}
|
|
549
452
|
function enumDomainValue(value, name, values) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const encoded = value.slice(ENUM_VALUE.length);
|
|
560
|
-
let decoded;
|
|
561
|
-
try {
|
|
562
|
-
decoded = JSON.parse(encoded);
|
|
563
|
-
}
|
|
564
|
-
catch {
|
|
565
|
-
throw new TypeError(`Invalid tagged enum ${name} value`);
|
|
566
|
-
}
|
|
567
|
-
if (!Array.isArray(decoded) ||
|
|
568
|
-
decoded.length !== 3 ||
|
|
569
|
-
decoded[0] !== name ||
|
|
570
|
-
!Number.isSafeInteger(decoded[1]) ||
|
|
571
|
-
typeof decoded[2] !== "string" ||
|
|
572
|
-
JSON.stringify(decoded) !== encoded) {
|
|
573
|
-
throw new TypeError(`Invalid tagged enum ${name} value`);
|
|
574
|
-
}
|
|
575
|
-
source = decoded[2];
|
|
576
|
-
}
|
|
577
|
-
const index = values.indexOf(source);
|
|
578
|
-
if (index === -1)
|
|
579
|
-
throw new TypeError(`${source} is not a value of enum ${name}`);
|
|
580
|
-
return boundedTaggedDomainValue(ENUM_VALUE, boundedJsonText([name, index, source], false, `Enum ${name} value`), `Enum ${name} value`);
|
|
581
|
-
}
|
|
582
|
-
export function enumDomainCompare(left, right) {
|
|
583
|
-
if (typeof left !== "string" ||
|
|
584
|
-
typeof right !== "string" ||
|
|
585
|
-
!left.startsWith(ENUM_VALUE) ||
|
|
586
|
-
!right.startsWith(ENUM_VALUE)) {
|
|
587
|
-
return undefined;
|
|
588
|
-
}
|
|
589
|
-
const a = JSON.parse(left.slice(ENUM_VALUE.length));
|
|
590
|
-
const b = JSON.parse(right.slice(ENUM_VALUE.length));
|
|
591
|
-
if (a[0] !== b[0])
|
|
592
|
-
throw new TypeError("Cannot compare values of different enum types");
|
|
593
|
-
return a[1] - b[1];
|
|
594
|
-
}
|
|
595
|
-
export function normalizeSqlDomainValue(domain, value) {
|
|
596
|
-
if (domain.kind === "numeric") {
|
|
597
|
-
return exactNumericValue(value, domain.precision, domain.scale);
|
|
598
|
-
}
|
|
599
|
-
if (domain.kind === "json")
|
|
600
|
-
return jsonDomainValue(value, false);
|
|
601
|
-
if (domain.kind === "jsonb")
|
|
602
|
-
return jsonDomainValue(value, true);
|
|
603
|
-
if (domain.kind === "uuid")
|
|
604
|
-
return uuidDomainValue(value);
|
|
605
|
-
if (domain.kind === "date")
|
|
606
|
-
return dateDomainValue(value);
|
|
607
|
-
if (domain.kind === "time")
|
|
608
|
-
return timeDomainValue(value);
|
|
609
|
-
if (domain.kind === "interval")
|
|
610
|
-
return intervalDomainValue(value);
|
|
611
|
-
if (domain.kind === "enum")
|
|
612
|
-
return enumDomainValue(value, domain.name, domain.values);
|
|
613
|
-
if (value === null || value === undefined)
|
|
614
|
-
return null;
|
|
615
|
-
if (typeof value !== "string")
|
|
616
|
-
throw new TypeError("ARRAY columns accept JSON array text");
|
|
617
|
-
const source = value.startsWith(ARRAY_VALUE) ? value.slice(ARRAY_VALUE.length) : value;
|
|
618
|
-
assertBoundedDomainString(source, "ARRAY value");
|
|
619
|
-
let parsed;
|
|
453
|
+
if (value === null || value === void 0)
|
|
454
|
+
return null;
|
|
455
|
+
if (typeof value !== "string")
|
|
456
|
+
throw new TypeError(`Enum ${name} accepts a string value`);
|
|
457
|
+
assertBoundedDomainString(value, `Enum ${name} value`);
|
|
458
|
+
let source = value;
|
|
459
|
+
if (!values.includes(value) && value.startsWith(ENUM_VALUE)) {
|
|
460
|
+
const encoded = value.slice(ENUM_VALUE.length);
|
|
461
|
+
let decoded;
|
|
620
462
|
try {
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
463
|
+
decoded = JSON.parse(encoded);
|
|
464
|
+
} catch {
|
|
465
|
+
throw new TypeError(`Invalid tagged enum ${name} value`);
|
|
466
|
+
}
|
|
467
|
+
if (!Array.isArray(decoded) || decoded.length !== 3 || decoded[0] !== name || !Number.isSafeInteger(decoded[1]) || typeof decoded[2] !== "string" || JSON.stringify(decoded) !== encoded) {
|
|
468
|
+
throw new TypeError(`Invalid tagged enum ${name} value`);
|
|
469
|
+
}
|
|
470
|
+
source = decoded[2];
|
|
471
|
+
}
|
|
472
|
+
const index = values.indexOf(source);
|
|
473
|
+
if (index === -1)
|
|
474
|
+
throw new TypeError(`${source} is not a value of enum ${name}`);
|
|
475
|
+
return boundedTaggedDomainValue(ENUM_VALUE, boundedJsonText([name, index, source], false, `Enum ${name} value`), `Enum ${name} value`);
|
|
476
|
+
}
|
|
477
|
+
function enumDomainCompare(left, right) {
|
|
478
|
+
if (typeof left !== "string" || typeof right !== "string" || !left.startsWith(ENUM_VALUE) || !right.startsWith(ENUM_VALUE)) {
|
|
479
|
+
return void 0;
|
|
480
|
+
}
|
|
481
|
+
const a = JSON.parse(left.slice(ENUM_VALUE.length));
|
|
482
|
+
const b = JSON.parse(right.slice(ENUM_VALUE.length));
|
|
483
|
+
if (a[0] !== b[0])
|
|
484
|
+
throw new TypeError("Cannot compare values of different enum types");
|
|
485
|
+
return a[1] - b[1];
|
|
486
|
+
}
|
|
487
|
+
function normalizeSqlDomainValue(domain, value) {
|
|
488
|
+
if (domain.kind === "numeric") {
|
|
489
|
+
return exactNumericValue(value, domain.precision, domain.scale);
|
|
490
|
+
}
|
|
491
|
+
if (domain.kind === "json")
|
|
492
|
+
return jsonDomainValue(value, false);
|
|
493
|
+
if (domain.kind === "jsonb")
|
|
494
|
+
return jsonDomainValue(value, true);
|
|
495
|
+
if (domain.kind === "uuid")
|
|
496
|
+
return uuidDomainValue(value);
|
|
497
|
+
if (domain.kind === "date")
|
|
498
|
+
return dateDomainValue(value);
|
|
499
|
+
if (domain.kind === "time")
|
|
500
|
+
return timeDomainValue(value);
|
|
501
|
+
if (domain.kind === "interval")
|
|
502
|
+
return intervalDomainValue(value);
|
|
503
|
+
if (domain.kind === "enum")
|
|
504
|
+
return enumDomainValue(value, domain.name, domain.values);
|
|
505
|
+
if (value === null || value === void 0)
|
|
506
|
+
return null;
|
|
507
|
+
if (typeof value !== "string")
|
|
508
|
+
throw new TypeError("ARRAY columns accept JSON array text");
|
|
509
|
+
const source = value.startsWith(ARRAY_VALUE) ? value.slice(ARRAY_VALUE.length) : value;
|
|
510
|
+
assertBoundedDomainString(source, "ARRAY value");
|
|
511
|
+
let parsed;
|
|
512
|
+
try {
|
|
513
|
+
parsed = JSON.parse(source);
|
|
514
|
+
} catch {
|
|
515
|
+
throw new TypeError("Invalid ARRAY value");
|
|
516
|
+
}
|
|
517
|
+
if (!Array.isArray(parsed))
|
|
518
|
+
throw new TypeError("ARRAY value must be an array");
|
|
519
|
+
return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(parsed, true, "ARRAY value"), "ARRAY value");
|
|
520
|
+
}
|
|
521
|
+
const collators = /* @__PURE__ */ new Map();
|
|
633
522
|
const MAX_COLLATOR_CACHE_ENTRIES = 64;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
523
|
+
function collatedDomainValue(value, collation) {
|
|
524
|
+
if (value === null || value === void 0)
|
|
525
|
+
return null;
|
|
526
|
+
value = externalSqlDomainValue(value);
|
|
527
|
+
if (typeof value !== "string" || typeof collation !== "string") {
|
|
528
|
+
throw new TypeError("COLLATE requires a string value and collation name");
|
|
529
|
+
}
|
|
530
|
+
assertBoundedDomainString(value, "COLLATE value");
|
|
531
|
+
assertBoundedDomainString(collation, "COLLATE name");
|
|
532
|
+
const locale = collation === "POSIX" ? "C" : collation;
|
|
533
|
+
if (locale !== "C")
|
|
534
|
+
collatorFor(locale, collation);
|
|
535
|
+
return boundedTaggedDomainValue(COLLATION_VALUE, boundedJsonText([locale, value], false, "COLLATE value"), "COLLATE value");
|
|
647
536
|
}
|
|
648
537
|
function boundedTaggedDomainValue(prefix, value, label) {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const rightValue = b?.[1] ?? right;
|
|
674
|
-
if (locale === "C")
|
|
675
|
-
return leftValue === rightValue ? 0 : leftValue < rightValue ? -1 : 1;
|
|
676
|
-
return collatorFor(locale, locale).compare(leftValue, rightValue);
|
|
538
|
+
if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
|
|
539
|
+
throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
|
|
540
|
+
}
|
|
541
|
+
return prefix + value;
|
|
542
|
+
}
|
|
543
|
+
function collatedDomainCompare(left, right) {
|
|
544
|
+
const decode = (value) => typeof value === "string" && value.startsWith(COLLATION_VALUE) ? JSON.parse(value.slice(COLLATION_VALUE.length)) : void 0;
|
|
545
|
+
const a = decode(left);
|
|
546
|
+
const b = decode(right);
|
|
547
|
+
if (a === void 0 && b === void 0)
|
|
548
|
+
return void 0;
|
|
549
|
+
if (a === void 0 && typeof left !== "string")
|
|
550
|
+
return void 0;
|
|
551
|
+
if (b === void 0 && typeof right !== "string")
|
|
552
|
+
return void 0;
|
|
553
|
+
const locale = a?.[0] ?? b?.[0] ?? "C";
|
|
554
|
+
if (a !== void 0 && a[0] !== locale || b !== void 0 && b[0] !== locale) {
|
|
555
|
+
throw new TypeError("Cannot compare values with different collations");
|
|
556
|
+
}
|
|
557
|
+
const leftValue = a?.[1] ?? left;
|
|
558
|
+
const rightValue = b?.[1] ?? right;
|
|
559
|
+
if (locale === "C")
|
|
560
|
+
return leftValue === rightValue ? 0 : leftValue < rightValue ? -1 : 1;
|
|
561
|
+
return collatorFor(locale, locale).compare(leftValue, rightValue);
|
|
677
562
|
}
|
|
678
563
|
function collatorFor(locale, displayName) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
typeof value === "string" &&
|
|
713
|
-
value.startsWith(NUMERIC)) {
|
|
714
|
-
const text = value.slice(NUMERIC.length);
|
|
715
|
-
const dot = text.indexOf(".");
|
|
716
|
-
const fractionDigits = dot === -1 ? 0 : text.length - dot - 1;
|
|
717
|
-
if (fractionDigits >= domain.scale)
|
|
718
|
-
return text;
|
|
719
|
-
return (dot === -1 ? `${text}.` : text) + "0".repeat(domain.scale - fractionDigits);
|
|
720
|
-
}
|
|
721
|
-
return externalSqlDomainValue(value);
|
|
722
|
-
}
|
|
723
|
-
export function externalSqlDomainValue(value) {
|
|
724
|
-
if (typeof value !== "string")
|
|
725
|
-
return value;
|
|
726
|
-
if (value.startsWith(TEXT_VALUE))
|
|
727
|
-
return value.slice(TEXT_VALUE.length);
|
|
728
|
-
for (const prefix of [NUMERIC, JSON_VALUE, JSONB_VALUE, UUID_VALUE, DATE_VALUE, TIME_VALUE]) {
|
|
729
|
-
if (value.startsWith(prefix))
|
|
730
|
-
return value.slice(prefix.length);
|
|
731
|
-
}
|
|
732
|
-
if (value.startsWith(INTERVAL_VALUE)) {
|
|
733
|
-
const decoded = JSON.parse(value.slice(INTERVAL_VALUE.length));
|
|
734
|
-
return `${String(decoded[0])} mons ${String(decoded[1])} days ${String(decoded[2])} usecs`;
|
|
735
|
-
}
|
|
736
|
-
if (value.startsWith(ARRAY_VALUE))
|
|
737
|
-
return value.slice(ARRAY_VALUE.length);
|
|
738
|
-
if (value.startsWith(COLLATION_VALUE)) {
|
|
739
|
-
return JSON.parse(value.slice(COLLATION_VALUE.length))[1];
|
|
740
|
-
}
|
|
741
|
-
if (value.startsWith(ENUM_VALUE)) {
|
|
742
|
-
return JSON.parse(value.slice(ENUM_VALUE.length))[2];
|
|
743
|
-
}
|
|
564
|
+
const cached = collators.get(locale);
|
|
565
|
+
if (cached !== void 0) {
|
|
566
|
+
collators.delete(locale);
|
|
567
|
+
collators.set(locale, cached);
|
|
568
|
+
return cached;
|
|
569
|
+
}
|
|
570
|
+
let created;
|
|
571
|
+
try {
|
|
572
|
+
created = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" });
|
|
573
|
+
} catch {
|
|
574
|
+
throw new TypeError(`Unsupported collation: ${displayName}`);
|
|
575
|
+
}
|
|
576
|
+
if (collators.size >= MAX_COLLATOR_CACHE_ENTRIES) {
|
|
577
|
+
const oldest = collators.keys().next().value;
|
|
578
|
+
if (oldest !== void 0)
|
|
579
|
+
collators.delete(oldest);
|
|
580
|
+
}
|
|
581
|
+
collators.set(locale, created);
|
|
582
|
+
return created;
|
|
583
|
+
}
|
|
584
|
+
function externalSqlDomainColumnValue(value, domain) {
|
|
585
|
+
if (domain?.kind === "numeric" && domain.scale !== void 0 && domain.scale > 0 && typeof value === "string" && value.startsWith(NUMERIC)) {
|
|
586
|
+
const text = value.slice(NUMERIC.length);
|
|
587
|
+
const dot = text.indexOf(".");
|
|
588
|
+
const fractionDigits = dot === -1 ? 0 : text.length - dot - 1;
|
|
589
|
+
if (fractionDigits >= domain.scale)
|
|
590
|
+
return text;
|
|
591
|
+
return (dot === -1 ? `${text}.` : text) + "0".repeat(domain.scale - fractionDigits);
|
|
592
|
+
}
|
|
593
|
+
return externalSqlDomainValue(value);
|
|
594
|
+
}
|
|
595
|
+
function externalSqlDomainValue(value) {
|
|
596
|
+
if (typeof value !== "string")
|
|
744
597
|
return value;
|
|
598
|
+
if (value.startsWith(TEXT_VALUE))
|
|
599
|
+
return value.slice(TEXT_VALUE.length);
|
|
600
|
+
for (const prefix of [NUMERIC, JSON_VALUE, JSONB_VALUE, UUID_VALUE, DATE_VALUE, TIME_VALUE]) {
|
|
601
|
+
if (value.startsWith(prefix))
|
|
602
|
+
return value.slice(prefix.length);
|
|
603
|
+
}
|
|
604
|
+
if (value.startsWith(INTERVAL_VALUE)) {
|
|
605
|
+
const decoded = JSON.parse(value.slice(INTERVAL_VALUE.length));
|
|
606
|
+
return `${String(decoded[0])} mons ${String(decoded[1])} days ${String(decoded[2])} usecs`;
|
|
607
|
+
}
|
|
608
|
+
if (value.startsWith(ARRAY_VALUE))
|
|
609
|
+
return value.slice(ARRAY_VALUE.length);
|
|
610
|
+
if (value.startsWith(COLLATION_VALUE)) {
|
|
611
|
+
return JSON.parse(value.slice(COLLATION_VALUE.length))[1];
|
|
612
|
+
}
|
|
613
|
+
if (value.startsWith(ENUM_VALUE)) {
|
|
614
|
+
return JSON.parse(value.slice(ENUM_VALUE.length))[2];
|
|
615
|
+
}
|
|
616
|
+
return value;
|
|
745
617
|
}
|
|
746
|
-
/**
|
|
747
|
-
* The domain kind carried by an internal string value, or undefined for ordinary text. The
|
|
748
|
-
* protected-TEXT wrapper and collated text are both ordinary text to PostgreSQL's operator
|
|
749
|
-
* resolution, so they report no kind.
|
|
750
|
-
*/
|
|
751
618
|
function concatenationDomainKind(value) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
619
|
+
if (!value.startsWith(PREFIX))
|
|
620
|
+
return void 0;
|
|
621
|
+
if (value.startsWith(TEXT_VALUE) || value.startsWith(COLLATION_VALUE))
|
|
622
|
+
return void 0;
|
|
623
|
+
const rest = value.slice(PREFIX.length);
|
|
624
|
+
const colon = rest.indexOf(":");
|
|
625
|
+
return colon === -1 ? void 0 : rest.slice(0, colon);
|
|
626
|
+
}
|
|
627
|
+
function concatenatedSqlValue(leftValue, rightValue) {
|
|
628
|
+
const leftText = typeof leftValue === "string";
|
|
629
|
+
const rightText = typeof rightValue === "string";
|
|
630
|
+
if (!leftText && !rightText)
|
|
631
|
+
throw new TypeError("|| requires a string operand");
|
|
632
|
+
const left = leftText ? leftValue : concatenationOperandText(leftValue);
|
|
633
|
+
const right = rightText ? rightValue : concatenationOperandText(rightValue);
|
|
634
|
+
const leftKind = concatenationDomainKind(left);
|
|
635
|
+
const rightKind = concatenationDomainKind(right);
|
|
636
|
+
for (const kind of [leftKind, rightKind]) {
|
|
637
|
+
if (kind === "array") {
|
|
638
|
+
throw new TypeError("PostgreSQL array concatenation (||) is not supported");
|
|
639
|
+
}
|
|
640
|
+
if (kind === "jsonb") {
|
|
641
|
+
throw new TypeError("PostgreSQL JSONB concatenation (||) is not supported");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (leftKind !== void 0 && rightKind !== void 0) {
|
|
645
|
+
throw new TypeError(`No || operator for ${leftKind} and ${rightKind} values`);
|
|
646
|
+
}
|
|
647
|
+
return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
|
|
648
|
+
}
|
|
649
|
+
function concatenationOperandText(value) {
|
|
650
|
+
if (typeof value === "number")
|
|
651
|
+
return String(value);
|
|
652
|
+
if (typeof value === "boolean")
|
|
653
|
+
return value ? "true" : "false";
|
|
654
|
+
if (value instanceof Date)
|
|
655
|
+
return dateIsoString(value);
|
|
656
|
+
throw new TypeError("|| requires a string operand");
|
|
657
|
+
}
|
|
658
|
+
function intervalDomainCompare(left, right) {
|
|
659
|
+
if (typeof left !== "string" || typeof right !== "string" || !left.startsWith(INTERVAL_VALUE) || !right.startsWith(INTERVAL_VALUE)) {
|
|
660
|
+
return void 0;
|
|
661
|
+
}
|
|
662
|
+
const total = (value) => {
|
|
663
|
+
const [months, days, microseconds] = JSON.parse(value.slice(INTERVAL_VALUE.length));
|
|
664
|
+
return (months * 30 + days) * 864e8 + microseconds;
|
|
665
|
+
};
|
|
666
|
+
const difference = total(left) - total(right);
|
|
667
|
+
return difference === 0 ? 0 : difference < 0 ? -1 : 1;
|
|
668
|
+
}
|
|
669
|
+
function isSqlDomainValue(value) {
|
|
670
|
+
return typeof value === "string" && value.startsWith(PREFIX);
|
|
671
|
+
}
|
|
672
|
+
export {
|
|
673
|
+
arrayDomainValue,
|
|
674
|
+
boundedJsonText,
|
|
675
|
+
collatedDomainCompare,
|
|
676
|
+
collatedDomainValue,
|
|
677
|
+
concatenatedSqlValue,
|
|
678
|
+
dateDomainValue,
|
|
679
|
+
enumDomainCompare,
|
|
680
|
+
exactNumericAsNumber,
|
|
681
|
+
exactNumericBinary,
|
|
682
|
+
exactNumericCompare,
|
|
683
|
+
exactNumericLiteral,
|
|
684
|
+
exactNumericValue,
|
|
685
|
+
externalSqlDomainColumnValue,
|
|
686
|
+
externalSqlDomainValue,
|
|
687
|
+
externalSqlTextValue,
|
|
688
|
+
intervalDomainCompare,
|
|
689
|
+
intervalDomainValue,
|
|
690
|
+
isDateDomainValue,
|
|
691
|
+
isExactNumeric,
|
|
692
|
+
isSqlDomainValue,
|
|
693
|
+
jsonDomainDocument,
|
|
694
|
+
jsonDomainValue,
|
|
695
|
+
normalizeSqlDomainValue,
|
|
696
|
+
preservedJsonDomainValue,
|
|
697
|
+
protectedSqlTextValue,
|
|
698
|
+
timeDomainValue,
|
|
699
|
+
uuidDomainValue
|
|
700
|
+
};
|