@minnowdb/core 0.7.0 → 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.
Files changed (95) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.js +67 -78
  10. package/dist/engine/artifact-cache.js +106 -116
  11. package/dist/engine/batch.js +36 -44
  12. package/dist/engine/buffered-writer.js +156 -158
  13. package/dist/engine/byte-estimates.js +20 -22
  14. package/dist/engine/cache-limits.js +24 -25
  15. package/dist/engine/cancellation.js +5 -3
  16. package/dist/engine/catalog.js +64 -73
  17. package/dist/engine/client.js +715 -780
  18. package/dist/engine/database.js +15941 -19313
  19. package/dist/engine/defaults.js +104 -111
  20. package/dist/engine/errors.js +113 -128
  21. package/dist/engine/fts.js +233 -280
  22. package/dist/engine/group-index.js +287 -321
  23. package/dist/engine/index.js +23 -12
  24. package/dist/engine/join-index.js +177 -186
  25. package/dist/engine/keyed-live.js +168 -173
  26. package/dist/engine/live-api.js +0 -1
  27. package/dist/engine/live-equal.js +32 -37
  28. package/dist/engine/live.js +603 -647
  29. package/dist/engine/memory.js +109 -111
  30. package/dist/engine/optimizer.d.ts +19 -1
  31. package/dist/engine/optimizer.js +2633 -2934
  32. package/dist/engine/point-read.js +122 -182
  33. package/dist/engine/query-api.js +12 -3
  34. package/dist/engine/query-cache.js +61 -69
  35. package/dist/engine/query.d.ts +6 -0
  36. package/dist/engine/query.js +8503 -9980
  37. package/dist/engine/result-wire.js +221 -252
  38. package/dist/engine/schema-wire.js +97 -112
  39. package/dist/engine/schema.js +1099 -1254
  40. package/dist/engine/sort-keys.js +315 -389
  41. package/dist/engine/sql-domains.js +646 -769
  42. package/dist/engine/sql-driver.js +0 -1
  43. package/dist/engine/sql-functions.js +1031 -1125
  44. package/dist/engine/sql-json.js +199 -218
  45. package/dist/engine/sql-semantics.js +572 -639
  46. package/dist/engine/typed-live.js +271 -293
  47. package/dist/engine/vector.js +4484 -5145
  48. package/dist/engine/worker-host.d.ts +8 -63
  49. package/dist/engine/worker-host.js +28 -1130
  50. package/dist/engine/worker-indexeddb.d.ts +1 -0
  51. package/dist/engine/worker-indexeddb.js +3 -0
  52. package/dist/engine/worker-memory.d.ts +1 -0
  53. package/dist/engine/worker-memory.js +3 -0
  54. package/dist/engine/worker-opfs.d.ts +1 -0
  55. package/dist/engine/worker-opfs.js +3 -0
  56. package/dist/engine/worker-server.d.ts +93 -0
  57. package/dist/engine/worker-server.js +999 -0
  58. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  59. package/dist/engine/worker-store-indexeddb.js +11 -0
  60. package/dist/engine/worker-store-memory.d.ts +2 -0
  61. package/dist/engine/worker-store-memory.js +6 -0
  62. package/dist/engine/worker-store-opfs.d.ts +2 -0
  63. package/dist/engine/worker-store-opfs.js +9 -0
  64. package/dist/engine/worker.js +0 -12
  65. package/dist/engine/write-block-planner.js +93 -100
  66. package/dist/plan/index.js +15 -16
  67. package/dist/plan/model.d.ts +6 -0
  68. package/dist/plan/model.js +20 -27
  69. package/dist/storage/index.js +0 -12
  70. package/dist/storage/indexeddb.js +11776 -13171
  71. package/dist/storage/memory.js +1198 -1241
  72. package/dist/storage/opfs/files.js +238 -295
  73. package/dist/storage/opfs/index.js +9 -3
  74. package/dist/storage/opfs/leader.js +4386 -4856
  75. package/dist/storage/opfs/rpc.js +223 -259
  76. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  77. package/dist/storage/opfs/store.js +884 -1003
  78. package/dist/storage/persistence.js +59 -74
  79. package/dist/storage/snapshot-stream.js +788 -826
  80. package/dist/storage/snapshot.js +36 -37
  81. package/dist/storage/toolkit/extents.js +444 -520
  82. package/dist/storage/toolkit/index.js +28 -29
  83. package/dist/storage/toolkit/record-core.js +5792 -6377
  84. package/dist/storage/toolkit/sync-file.js +34 -42
  85. package/dist/storage/toolkit/wal.js +92 -127
  86. package/dist/storage/toolkit/wire.js +232 -256
  87. package/dist/storage/types.js +2948 -3250
  88. package/dist/testing/block-store-conformance.js +1556 -1629
  89. package/dist/testing/index.js +310 -291
  90. package/dist/testing/opfs-shim.js +280 -312
  91. package/dist/testing/simulator.js +496 -534
  92. package/dist/testing/sqllogictest.js +425 -365
  93. package/dist/transactions/index.js +1464 -1712
  94. package/dist/worker-protocol/index.js +70 -69
  95. package/package.json +20 -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, } from "./cache-limits.js";
4
- /** Internal string encodings for PostgreSQL value domains carried by string vectors. */
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,810 +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
- * Protects an ordinary SQL string that happens to use the internal domain namespace. Physical
19
- * TEXT remains unchanged on disk; this wrapper is applied at execution boundaries so a user
20
- * value can never be mistaken for NUMERIC, INTERVAL, enum, or another tagged logical value.
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
- if (!Number.isSafeInteger(exponent) || exponent < 0 || exponent > 100_000) {
33
- throw new RangeError(`Decimal scale is outside the supported range: ${String(exponent)}`);
34
- }
35
- // Retaining every intermediate power up to 100,000 consumes quadratic memory in the number
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
- const source = String(value).trim();
41
- assertBoundedDomainString(source, "NUMERIC value");
42
- const match = /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?\d+))?$/.exec(source);
43
- if (match === null)
44
- throw new TypeError(`Invalid NUMERIC value: ${String(value)}`);
45
- const fraction = match[3] ?? match[4] ?? "";
46
- const digits = `${match[2] ?? "0"}${fraction}`.replace(/^0+(?=\d)/, "");
47
- if (digits.length > MAX_SQL_NUMERIC_DIGITS) {
48
- throw new RangeError(`NUMERIC value exceeds ${String(MAX_SQL_NUMERIC_DIGITS)} significant digits`);
49
- }
50
- const exponent = Number(match[5] ?? 0);
51
- if (!Number.isSafeInteger(exponent))
52
- throw new RangeError(`Invalid NUMERIC exponent: ${source}`);
53
- let coefficient = BigInt(digits || "0") * (match[1] === "-" ? -1n : 1n);
54
- let scale = fraction.length - exponent;
55
- if (scale < 0) {
56
- if (-scale > 100_000) {
57
- throw new RangeError(`NUMERIC exponent is outside the supported range: ${source}`);
58
- }
59
- coefficient *= pow10(-scale);
60
- scale = 0;
61
- }
62
- return { coefficient, scale };
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
- let { coefficient, scale } = parts;
66
- while (scale > 0 && coefficient % 10n === 0n) {
67
- coefficient /= 10n;
68
- scale -= 1;
69
- }
70
- return { coefficient, scale };
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
- if (!Number.isSafeInteger(scale) || scale < 0 || scale > 100_000) {
74
- throw new RangeError(`NUMERIC scale is outside the supported range: ${String(scale)}`);
75
- }
76
- if (parts.scale <= scale) {
77
- return { coefficient: parts.coefficient * pow10(scale - parts.scale), scale };
78
- }
79
- const divisor = pow10(parts.scale - scale);
80
- let quotient = parts.coefficient / divisor;
81
- const remainder = parts.coefficient % divisor;
82
- if ((remainder < 0n ? -remainder : remainder) * 2n >= divisor) {
83
- quotient += parts.coefficient < 0n ? -1n : 1n;
84
- }
85
- return { coefficient: quotient, scale };
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
- const negative = parts.coefficient < 0n;
89
- const digits = (negative ? -parts.coefficient : parts.coefficient).toString();
90
- if (parts.scale === 0)
91
- return `${negative ? "-" : ""}${digits}`;
92
- const padded = digits.padStart(parts.scale + 1, "0");
93
- const split = padded.length - parts.scale;
94
- return `${negative ? "-" : ""}${padded.slice(0, split)}.${padded.slice(split)}`;
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
- if (typeof value !== "string" || !value.startsWith(NUMERIC))
98
- return undefined;
99
- return decimalParts(value.slice(NUMERIC.length));
100
- }
101
- export function isExactNumeric(value) {
102
- return taggedDecimalParts(value) !== undefined;
103
- }
104
- export function exactNumericValue(value, precision, scale) {
105
- if (value === null || value === undefined)
106
- return null;
107
- const raw = taggedDecimalParts(value) ??
108
- (typeof value === "number" || typeof value === "string" ? decimalParts(value) : undefined);
109
- if (raw === undefined)
110
- throw new TypeError("NUMERIC accepts a number or decimal string");
111
- const adjusted = scale === undefined ? normalizeDecimal(raw) : rescaleDecimal(raw, scale);
112
- if (precision !== undefined) {
113
- if (!Number.isSafeInteger(precision) || precision < 1 || precision > 100_000) {
114
- throw new RangeError(`NUMERIC precision is outside the supported range: ${String(precision)}`);
115
- }
116
- const declaredScale = scale ?? 0;
117
- if (declaredScale > precision) {
118
- throw new TypeError("NUMERIC scale cannot exceed its precision");
119
- }
120
- const integer = adjusted.coefficient < 0n ? -adjusted.coefficient : adjusted.coefficient;
121
- const integerDigits = (integer / pow10(adjusted.scale)).toString().length;
122
- if (integer !== 0n && integerDigits > precision - declaredScale) {
123
- throw new RangeError(`NUMERIC(${String(precision)}, ${String(declaredScale)}) overflow`);
124
- }
125
- }
126
- return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(adjusted)), "NUMERIC value");
127
- }
128
- /**
129
- * Tags a SQL numeric constant with its exact digits, as written. Unlike `exactNumericValue`
130
- * this does not canonicalize: trailing fractional zeros are PostgreSQL display scale, and
131
- * division selects its result scale from the operands' scales, so a literal's digits are part
132
- * of its meaning. Scientific notation is the exception — PostgreSQL expands the exponent when
133
- * it parses the literal, so `1.5e2` is `150` and `1e400` is the full digit string, and the
134
- * display scale comes from that expansion. The text is still validated and bounded.
135
- */
136
- export function exactNumericLiteral(text) {
137
- const parts = decimalParts(text);
138
- const plain = /[eE]/.test(text) ? formatDecimal(parts) : text;
139
- return boundedTaggedDomainValue(NUMERIC, plain, "NUMERIC literal");
140
- }
141
- /**
142
- * The Float64 this exact numeric value reads back identically from — the nearest float's
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
- const magnitude = parts.coefficient < 0n ? -parts.coefficient : parts.coefficient;
169
- if (magnitude === 0n)
170
- return { weight: 0, firstDigit: 0 };
171
- const fractionGroups = Math.ceil(parts.scale / 4);
172
- const aligned = magnitude * pow10(fractionGroups * 4 - parts.scale);
173
- const groups = Math.ceil(aligned.toString().length / 4);
174
- return {
175
- weight: groups - 1 - fractionGroups,
176
- firstDigit: Number(aligned / pow10((groups - 1) * 4)),
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
- const leftLeading = nbaseLeading(a);
188
- const rightLeading = nbaseLeading(b);
189
- let estimatedWeight = leftLeading.weight - rightLeading.weight;
190
- if (leftLeading.firstDigit <= rightLeading.firstDigit)
191
- estimatedWeight -= 1;
192
- const scale = Math.max(16 - estimatedWeight * 4, a.scale, b.scale, minimumScale, 0);
193
- return Math.min(scale, 1000);
194
- }
195
- export function exactNumericBinary(operator, left, right, minimumQuotientScale = 0,
196
- // Stored and returned values are canonical (trailing fractional zeros stripped). Constant
197
- // folding keeps the computed scale instead: PostgreSQL carries display scale through each
198
- // step max for +, -, %, the operands' sum for *, the selected scale for / — and a later
199
- // division selects its own scale from it, so folding must not strip what execution never sees.
200
- canonicalize = true) {
201
- const leftTagged = taggedDecimalParts(left);
202
- const rightTagged = taggedDecimalParts(right);
203
- if (leftTagged === undefined && rightTagged === undefined)
204
- return undefined;
205
- const a = leftTagged ?? decimalParts(typeof left === "number" ? left : String(left));
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
- return ac === bc ? 0 : ac < bc ? -1 : 1;
258
- }
259
- /**
260
- * Serializes a JSON value while bounding traversal, nesting, and output before concatenation.
261
- * Canonical mode sorts object names directly in the wire text (including integer-looking names,
262
- * which JavaScript object enumeration would otherwise silently reorder).
263
- */
264
- export function boundedJsonText(value, canonical, label = "JSON value") {
265
- const state = { active: new Set(), pieces: [], items: 0, length: 0 };
266
- encodeBoundedJson(value, canonical, label, 0, state);
267
- return state.pieces.join("");
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
- state.items += 1;
271
- if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
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
- if (depth > MAX_SQL_STRUCTURED_VALUE_DEPTH) {
275
- throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_DEPTH)} levels`);
276
- }
277
- if (value === null) {
278
- appendJsonPiece(state, "null", label);
279
- return;
280
- }
281
- if (typeof value === "boolean") {
282
- appendJsonPiece(state, value ? "true" : "false", label);
283
- return;
284
- }
285
- if (typeof value === "number") {
286
- if (!Number.isFinite(value))
287
- throw new TypeError("JSON numbers must be finite");
288
- appendJsonPiece(state, JSON.stringify(value), label);
289
- return;
290
- }
291
- if (typeof value === "string") {
292
- assertBoundedDomainString(value, label);
293
- appendJsonPiece(state, JSON.stringify(value), label);
294
- return;
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
- const next = state.length + piece.length;
358
- if (!Number.isSafeInteger(next) || next > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
359
- throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
360
- }
361
- state.length = next;
362
- state.pieces.push(piece);
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
- if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
366
- throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
367
- }
368
- assertWellFormedString(value, label);
369
- }
370
- export function jsonDomainValue(value, binary) {
371
- if (value === null || value === undefined)
372
- return null;
373
- let parsed = value;
374
- if (typeof value === "string") {
375
- const prefix = value.startsWith(JSONB_VALUE)
376
- ? JSONB_VALUE
377
- : value.startsWith(JSON_VALUE)
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
- JSON.parse(document);
411
- }
412
- catch {
413
- throw new TypeError("Invalid JSON value");
414
- }
415
- return boundedTaggedDomainValue(binary ? JSONB_VALUE : JSON_VALUE, document, binary ? "JSONB value" : "JSON value");
416
- }
417
- export function uuidDomainValue(value) {
418
- if (value === null || value === undefined)
419
- return null;
420
- if (typeof value !== "string")
421
- throw new TypeError("UUID accepts a string value");
422
- assertBoundedDomainString(value, "UUID value");
423
- const source = (value.startsWith(UUID_VALUE) ? value.slice(UUID_VALUE.length) : value).toLowerCase();
424
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(source)) {
425
- throw new TypeError(`Invalid UUID value: ${value}`);
426
- }
427
- return UUID_VALUE + source;
428
- }
429
- /** Canonical, zoneless SQL DATE value. No JavaScript time zone participates in validation. */
430
- export function dateDomainValue(value) {
431
- if (value === null || value === undefined)
432
- return null;
433
- let source;
434
- if (value instanceof Date) {
435
- if (!Number.isFinite(value.getTime()))
436
- throw new TypeError("DATE accepts a valid Date value");
437
- source = dateIsoString(value).slice(0, 10);
438
- }
439
- else if (typeof value === "string") {
440
- assertBoundedDomainString(value, "DATE value");
441
- source = value.startsWith(DATE_VALUE) ? value.slice(DATE_VALUE.length) : value;
442
- }
443
- else {
444
- throw new TypeError("DATE accepts a YYYY-MM-DD string value");
445
- }
446
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(source);
447
- const year = Number(match?.[1]);
448
- const month = Number(match?.[2]);
449
- const day = Number(match?.[3]);
450
- const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
451
- const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
452
- if (match === null ||
453
- year < 1 ||
454
- month < 1 ||
455
- month > 12 ||
456
- day < 1 ||
457
- day > (days[month - 1] ?? 0)) {
458
- throw new TypeError(`Invalid DATE value: ${String(value)}`);
459
- }
460
- return DATE_VALUE + source;
461
- }
462
- export function isDateDomainValue(value) {
463
- return typeof value === "string" && value.startsWith(DATE_VALUE);
464
- }
465
- export function timeDomainValue(value) {
466
- if (value === null || value === undefined)
467
- return null;
468
- if (typeof value !== "string")
469
- throw new TypeError("TIME accepts a string value");
470
- assertBoundedDomainString(value, "TIME value");
471
- const source = value.startsWith(TIME_VALUE) ? value.slice(TIME_VALUE.length) : value;
472
- const match = /^(\d{2}):(\d{2})(?::(\d{2})(\.\d{1,6})?)?$/.exec(source);
473
- const hours = match?.[1];
474
- const minutes = match?.[2];
475
- if (match === null ||
476
- hours === undefined ||
477
- minutes === undefined ||
478
- Number(hours) > 23 ||
479
- Number(minutes) > 59 ||
480
- Number(match[3] ?? 0) > 59) {
481
- throw new TypeError(`Invalid TIME value: ${value}`);
482
- }
483
- return TIME_VALUE + `${hours}:${minutes}:${match[3] ?? "00"}${match[4] ?? ""}`;
484
- }
485
- export function intervalDomainValue(value) {
486
- if (value === null || value === undefined)
487
- return null;
488
- if (typeof value !== "string")
489
- throw new TypeError("INTERVAL accepts a string value");
490
- assertBoundedDomainString(value, "INTERVAL value");
491
- if (value.startsWith(INTERVAL_VALUE)) {
492
- const source = value.slice(INTERVAL_VALUE.length);
493
- let decoded;
494
- try {
495
- decoded = JSON.parse(source);
496
- }
497
- catch {
498
- throw new TypeError("Invalid tagged INTERVAL value");
499
- }
500
- if (!Array.isArray(decoded) ||
501
- decoded.length !== 3 ||
502
- !decoded.every((part) => Number.isSafeInteger(part)) ||
503
- JSON.stringify(decoded) !== source) {
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
- const residue = source.replace(partPattern, "").trim();
537
- if (matched === 0 ||
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 boundedTaggedDomainValue(INTERVAL_VALUE, JSON.stringify([months, days, microseconds]), "INTERVAL value");
545
- }
546
- export function arrayDomainValue(values) {
547
- return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(values.map(externalSqlDomainValue), true, "ARRAY value"), "ARRAY value");
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
- if (value === null || value === undefined)
551
- return null;
552
- if (typeof value !== "string")
553
- throw new TypeError(`Enum ${name} accepts a string value`);
554
- assertBoundedDomainString(value, `Enum ${name} value`);
555
- let source = value;
556
- // Enum labels are arbitrary strings. Prefer an exact declared label before recognizing the
557
- // internal tag namespace, so a legal label can begin with that prefix just like ordinary TEXT.
558
- if (!values.includes(value) && value.startsWith(ENUM_VALUE)) {
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
- parsed = JSON.parse(source);
622
- }
623
- catch {
624
- throw new TypeError("Invalid ARRAY value");
625
- }
626
- if (!Array.isArray(parsed))
627
- throw new TypeError("ARRAY value must be an array");
628
- // JSON array text contains ordinary JSON strings. Do not interpret a string that happens to
629
- // begin with Minnow's internal domain prefix as an already-tagged SQL scalar.
630
- return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(parsed, true, "ARRAY value"), "ARRAY value");
631
- }
632
- const collators = new Map();
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
- export function collatedDomainValue(value, collation) {
635
- if (value === null || value === undefined)
636
- return null;
637
- value = externalSqlDomainValue(value);
638
- if (typeof value !== "string" || typeof collation !== "string") {
639
- throw new TypeError("COLLATE requires a string value and collation name");
640
- }
641
- assertBoundedDomainString(value, "COLLATE value");
642
- assertBoundedDomainString(collation, "COLLATE name");
643
- const locale = collation === "POSIX" ? "C" : collation;
644
- if (locale !== "C")
645
- collatorFor(locale, collation);
646
- return boundedTaggedDomainValue(COLLATION_VALUE, boundedJsonText([locale, value], false, "COLLATE value"), "COLLATE value");
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
- // The prefix is an internal physical tag and is removed at the JavaScript boundary. Limit the
650
- // user-visible payload, while the fixed tag adds only a few bounded bytes in storage.
651
- if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
652
- throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
653
- }
654
- return prefix + value;
655
- }
656
- export function collatedDomainCompare(left, right) {
657
- const decode = (value) => typeof value === "string" && value.startsWith(COLLATION_VALUE)
658
- ? JSON.parse(value.slice(COLLATION_VALUE.length))
659
- : undefined;
660
- const a = decode(left);
661
- const b = decode(right);
662
- if (a === undefined && b === undefined)
663
- return undefined;
664
- if (a === undefined && typeof left !== "string")
665
- return undefined;
666
- if (b === undefined && typeof right !== "string")
667
- return undefined;
668
- const locale = a?.[0] ?? b?.[0] ?? "C";
669
- if ((a !== undefined && a[0] !== locale) || (b !== undefined && b[0] !== locale)) {
670
- throw new TypeError("Cannot compare values with different collations");
671
- }
672
- const leftValue = a?.[1] ?? left;
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
- const cached = collators.get(locale);
680
- if (cached !== undefined) {
681
- collators.delete(locale);
682
- collators.set(locale, cached);
683
- return cached;
684
- }
685
- let created;
686
- try {
687
- created = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" });
688
- }
689
- catch {
690
- throw new TypeError(`Unsupported collation: ${displayName}`);
691
- }
692
- if (collators.size >= MAX_COLLATOR_CACHE_ENTRIES) {
693
- const oldest = collators.keys().next().value;
694
- if (oldest !== undefined)
695
- collators.delete(oldest);
696
- }
697
- collators.set(locale, created);
698
- return created;
699
- }
700
- /**
701
- * Renders one result value at the JavaScript boundary using its column's logical domain.
702
- * A NUMERIC column with a declared scale displays PostgreSQL-style at exactly that scale:
703
- * the physical encoding is canonical (trailing fractional zeros stripped), so the declared
704
- * scale is restored by padding — never rounding — and a value carrying more fractional
705
- * digits than the declaration keeps every digit it has. Every other domain, and every value
706
- * without one, renders exactly as externalSqlDomainValue.
707
- */
708
- export function externalSqlDomainColumnValue(value, domain) {
709
- if (domain?.kind === "numeric" &&
710
- domain.scale !== undefined &&
711
- domain.scale > 0 &&
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
- if (!value.startsWith(PREFIX))
753
- return undefined;
754
- if (value.startsWith(TEXT_VALUE) || value.startsWith(COLLATION_VALUE))
755
- return undefined;
756
- const rest = value.slice(PREFIX.length);
757
- const colon = rest.indexOf(":");
758
- return colon === -1 ? undefined : rest.slice(0, colon);
759
- }
760
- /**
761
- * SQL `||` over internal string values, shared by both executors and constant folding so the
762
- * three paths cannot disagree. PostgreSQL resolves `||` to text concatenation only when one
763
- * side is text: its array and JSONB `||` operators are structural concatenation, which this
764
- * engine does not implement, and two non-text operands have no `||` operator at all. Refusing
765
- * those shapes keeps `||` from inventing a text concatenation PostgreSQL does not have —
766
- * and from ever concatenating internal domain encodings.
767
- */
768
- export function concatenatedSqlValue(leftValue, rightValue) {
769
- // PostgreSQL has text || anynonarray and anynonarray || text: one side is text and the other
770
- // renders as text. Two non-text operands (1 || 2, TRUE || FALSE) have no || operator.
771
- const leftText = typeof leftValue === "string";
772
- const rightText = typeof rightValue === "string";
773
- if (!leftText && !rightText)
774
- throw new TypeError("|| requires a string operand");
775
- const left = leftText ? leftValue : concatenationOperandText(leftValue);
776
- const right = rightText ? rightValue : concatenationOperandText(rightValue);
777
- const leftKind = concatenationDomainKind(left);
778
- const rightKind = concatenationDomainKind(right);
779
- for (const kind of [leftKind, rightKind]) {
780
- if (kind === "array") {
781
- throw new TypeError("PostgreSQL array concatenation (||) is not supported");
782
- }
783
- if (kind === "jsonb") {
784
- throw new TypeError("PostgreSQL JSONB concatenation (||) is not supported");
785
- }
786
- }
787
- if (leftKind !== undefined && rightKind !== undefined) {
788
- throw new TypeError(`No || operator for ${leftKind} and ${rightKind} values`);
789
- }
790
- return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
791
- }
792
- /** The text a non-string || operand contributes: PostgreSQL's output rendering of the value. */
793
- function concatenationOperandText(value) {
794
- if (typeof value === "number")
795
- return String(value);
796
- if (typeof value === "boolean")
797
- return value ? "true" : "false";
798
- if (value instanceof Date)
799
- return dateIsoString(value);
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)
800
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)));
801
648
  }
802
- /**
803
- * Orders two INTERVAL values the way PostgreSQL's interval comparison does: a month counts as
804
- * 30 days and a day as 24 hours, so INTERVAL '3 months' equals INTERVAL '90 days' and
805
- * INTERVAL '2 days' sorts below INTERVAL '10 days'. Undefined unless both are intervals.
806
- */
807
- export function intervalDomainCompare(left, right) {
808
- if (typeof left !== "string" ||
809
- typeof right !== "string" ||
810
- !left.startsWith(INTERVAL_VALUE) ||
811
- !right.startsWith(INTERVAL_VALUE)) {
812
- return undefined;
813
- }
814
- const total = (value) => {
815
- const [months, days, microseconds] = JSON.parse(value.slice(INTERVAL_VALUE.length));
816
- return (months * 30 + days) * 86_400_000_000 + microseconds;
817
- };
818
- const difference = total(left) - total(right);
819
- return difference === 0 ? 0 : difference < 0 ? -1 : 1;
820
- }
821
- export function isSqlDomainValue(value) {
822
- return typeof value === "string" && value.startsWith(PREFIX);
823
- }
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
+ };