@minnowdb/core 0.6.5 → 0.6.7

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 (57) hide show
  1. package/dist/block-format/codecs.d.ts +0 -8
  2. package/dist/block-format/codecs.js +1 -1
  3. package/dist/engine/artifact-cache.d.ts +2 -1
  4. package/dist/engine/batch.d.ts +0 -1
  5. package/dist/engine/batch.js +1 -1
  6. package/dist/engine/buffered-writer.js +1 -12
  7. package/dist/engine/byte-estimates.d.ts +11 -0
  8. package/dist/engine/byte-estimates.js +25 -0
  9. package/dist/engine/database.js +58 -34
  10. package/dist/engine/fts.d.ts +0 -1
  11. package/dist/engine/fts.js +1 -1
  12. package/dist/engine/group-index.d.ts +2 -0
  13. package/dist/engine/group-index.js +4 -8
  14. package/dist/engine/join-index.d.ts +0 -1
  15. package/dist/engine/join-index.js +1 -15
  16. package/dist/engine/keyed-live.js +2 -36
  17. package/dist/engine/live-equal.d.ts +7 -0
  18. package/dist/engine/live-equal.js +42 -0
  19. package/dist/engine/optimizer.js +11 -16
  20. package/dist/engine/point-read.d.ts +1 -1
  21. package/dist/engine/point-read.js +3 -2
  22. package/dist/engine/query-cache.js +1 -12
  23. package/dist/engine/query.d.ts +20 -59
  24. package/dist/engine/query.js +286 -54
  25. package/dist/engine/result-wire.d.ts +2 -1
  26. package/dist/engine/schema.d.ts +15 -11
  27. package/dist/engine/schema.js +11 -19
  28. package/dist/engine/sort-keys.d.ts +2 -3
  29. package/dist/engine/sort-keys.js +1 -1
  30. package/dist/engine/sql-domains.d.ts +27 -2
  31. package/dist/engine/sql-domains.js +120 -6
  32. package/dist/engine/sql-json.d.ts +0 -2
  33. package/dist/engine/sql-json.js +1 -1
  34. package/dist/engine/sql-semantics.d.ts +2 -1
  35. package/dist/engine/typed-live.js +3 -41
  36. package/dist/engine/vector.d.ts +7 -7
  37. package/dist/engine/vector.js +11 -30
  38. package/dist/engine/write-block-planner.d.ts +2 -1
  39. package/dist/plan/model.d.ts +22 -0
  40. package/dist/plan/model.js +29 -1
  41. package/dist/storage/indexeddb.js +13 -23
  42. package/dist/storage/opfs/leader.d.ts +0 -4
  43. package/dist/storage/opfs/leader.js +5 -14
  44. package/dist/storage/opfs/snapshot-ledger.d.ts +3 -2
  45. package/dist/storage/toolkit/index.d.ts +1 -1
  46. package/dist/storage/toolkit/record-core.d.ts +0 -1
  47. package/dist/storage/toolkit/record-core.js +3 -19
  48. package/dist/storage/types.d.ts +4 -3
  49. package/dist/storage/types.js +13 -2
  50. package/dist/testing/block-store-conformance.js +28 -0
  51. package/dist/testing/opfs-shim.d.ts +2 -1
  52. package/dist/testing/simulator.js +3 -0
  53. package/dist/worker-protocol/index.d.ts +0 -32
  54. package/dist/worker-protocol/index.js +0 -40
  55. package/package.json +2 -1
  56. package/postgres-feature-profile.json +8 -3
  57. package/sql-feature-matrix.json +71 -11
@@ -49,12 +49,16 @@ export interface TableForeignKey<TColumnName extends string = string> {
49
49
  */
50
50
  export declare function foreignKeyName(tableName: string, columnName: string): string;
51
51
  /**
52
- * A flavored value: a column whose slot the engine can fill, so inserts may omit it. The brand
53
- * is an optional phantom property plain values stay assignable in both directions.
52
+ * A JSON column's select value carrying a declared document shape. The value is still JSON
53
+ * text — the brand is an optional phantom property, so plain strings stay assignable in both
54
+ * directions — and adapters read the shape to offer typed traversal. The tuple wrapper keeps
55
+ * `never` and union shapes intact through inference.
54
56
  */
55
- export type HasDefault<TValue> = TValue & {
56
- readonly __minnowHasDefault?: true;
57
+ export type JsonShape<TShape> = string & {
58
+ readonly __minnowJsonShape?: readonly [TShape];
57
59
  };
60
+ /** A JSON column without a declared shape selects as exactly `string`, with no brand. */
61
+ type JsonColumnValue<TShape> = [TShape] extends [never] ? string : JsonShape<TShape>;
58
62
  export interface ColumnBuilder<TValue extends SchemaValue, TNullable extends boolean, TUnique extends boolean = false, THasDefault extends boolean = false, TInput extends SchemaValue = TValue, TDomain extends SqlDomain["kind"] | undefined = undefined, TGenerated extends boolean = false> {
59
63
  readonly kind: "column";
60
64
  readonly type: SchemaColumnType;
@@ -160,11 +164,6 @@ interface AnyColumn {
160
164
  readonly renamedFromName?: string;
161
165
  readonly reference?: ColumnReferenceSpec;
162
166
  }
163
- /**
164
- * Rebuilds a column carrying an exact default spec — the wire layer's escape hatch, since the
165
- * public `.default()` accepts literals and cannot express auto-increment catalog metadata.
166
- */
167
- export declare function columnWithDefaultSpec(base: Pick<AnyColumn, "type" | "isNullable" | "isUnique" | "renamedFromName" | "reference" | "enumValues"> & Partial<Pick<AnyColumn, "integer" | "sqlDomain">>, spec: ColumnDefault): AnyColumn;
168
167
  /** Rebuilds a fluent column from structured-clone-safe metadata. */
169
168
  export declare function columnFromState(state: Pick<AnyColumn, "type" | "isNullable" | "isUnique"> & Partial<Pick<AnyColumn, "integer" | "sqlDomain" | "renamedFromName" | "reference" | "defaultSpec" | "generatedSpec" | "enumValues" | "backfillValue">>): AnyColumn;
170
169
  export declare const column: {
@@ -179,8 +178,13 @@ export declare const column: {
179
178
  precision?: number;
180
179
  scale?: number;
181
180
  }) => ColumnBuilder<string, false, false, false, string | number, "numeric", false>;
182
- json: () => ColumnBuilder<string, false, false, false, string, "json", false>;
183
- jsonb: () => ColumnBuilder<string, false, false, false, string, "jsonb", false>;
181
+ /**
182
+ * JSON text at the JavaScript boundary. Optionally declare the document's shape —
183
+ * `column.json<{ name: string }>()` — for adapters to type `->`/`->>` traversal; the runtime
184
+ * value stays JSON text either way.
185
+ */
186
+ json: <TShape = never>() => ColumnBuilder<JsonColumnValue<TShape>, false, false, false, string, "json", false>;
187
+ jsonb: <TShape = never>() => ColumnBuilder<JsonColumnValue<TShape>, false, false, false, string, "jsonb", false>;
184
188
  uuid: () => ColumnBuilder<string, false, false, false, string, "uuid", false>;
185
189
  /** A zoneless calendar date, represented publicly as canonical `YYYY-MM-DD` text. */
186
190
  date: () => ColumnBuilder<string, false, false, false, string, "date", false>;
@@ -184,23 +184,6 @@ function validateSchemaName(name, kind) {
184
184
  throw new TypeError(`${kind} name cannot start or end with whitespace: ${JSON.stringify(name)}`);
185
185
  }
186
186
  }
187
- /**
188
- * Rebuilds a column carrying an exact default spec — the wire layer's escape hatch, since the
189
- * public `.default()` accepts literals and cannot express auto-increment catalog metadata.
190
- */
191
- export function columnWithDefaultSpec(base, spec) {
192
- return columnFromState({
193
- type: base.type,
194
- isNullable: base.isNullable,
195
- isUnique: base.isUnique,
196
- integer: base.integer ?? false,
197
- ...(base.sqlDomain === undefined ? {} : { sqlDomain: base.sqlDomain }),
198
- ...(base.renamedFromName === undefined ? {} : { renamedFromName: base.renamedFromName }),
199
- ...(base.reference === undefined ? {} : { reference: base.reference }),
200
- ...(base.enumValues === undefined ? {} : { enumValues: base.enumValues }),
201
- defaultSpec: spec,
202
- });
203
- }
204
187
  /** Rebuilds a fluent column from structured-clone-safe metadata. */
205
188
  export function columnFromState(state) {
206
189
  return createColumn(state.type, {
@@ -228,8 +211,17 @@ export const column = {
228
211
  const sqlDomain = validateSqlDomain({ kind: "numeric", ...options }, "numeric column");
229
212
  return createColumn("string", { sqlDomain });
230
213
  },
231
- json: () => createColumn("string", { sqlDomain: { kind: "json" } }),
232
- jsonb: () => createColumn("string", { sqlDomain: { kind: "jsonb" } }),
214
+ /**
215
+ * JSON text at the JavaScript boundary. Optionally declare the document's shape —
216
+ * `column.json<{ name: string }>()` — for adapters to type `->`/`->>` traversal; the runtime
217
+ * value stays JSON text either way.
218
+ */
219
+ json: () => createColumn("string", {
220
+ sqlDomain: { kind: "json" },
221
+ }),
222
+ jsonb: () => createColumn("string", {
223
+ sqlDomain: { kind: "jsonb" },
224
+ }),
233
225
  uuid: () => createColumn("string", { sqlDomain: { kind: "uuid" } }),
234
226
  /** A zoneless calendar date, represented publicly as canonical `YYYY-MM-DD` text. */
235
227
  date: () => createColumn("string", { sqlDomain: { kind: "date" } }),
@@ -19,7 +19,7 @@
19
19
  * next, so what a multi-term sort pays for its later terms is proportional to how many rows tie
20
20
  * on the earlier ones.
21
21
  */
22
- export interface SortKeyColumn {
22
+ interface SortKeyColumn {
23
23
  /**
24
24
  * Ascending comparison with NULL smallest. This is the column's raw comparison, not SQL's
25
25
  * default placement: the sorter applies PostgreSQL's NULLS LAST for ASC and NULLS FIRST for
@@ -45,8 +45,6 @@ export interface SortKeyTerm {
45
45
  readonly descending: boolean;
46
46
  readonly nulls: "first" | "last" | undefined;
47
47
  }
48
- /** Unwraps a datetime to the number the comparisons use; every other value passes through. */
49
- export declare function comparableSortValue(value: unknown): unknown;
50
48
  /**
51
49
  * Extracts one term's values into a comparison-ready column. `valueAt` is called once per row,
52
50
  * so a caller that reads through a row object or a vector pays that cost once rather than once
@@ -72,3 +70,4 @@ export declare function buildSortKeyColumn(count: number, valueAt: (index: numbe
72
70
  * is already in order before doing anything else, since a scan often arrives sorted.
73
71
  */
74
72
  export declare function sortKeyIndexes(count: number, terms: readonly SortKeyTerm[]): Uint32Array;
73
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { dateMilliseconds } from "../date-value.js";
2
2
  import { compareSqlStrings, compareSqlValues } from "./sql-semantics.js";
3
3
  /** Unwraps a datetime to the number the comparisons use; every other value passes through. */
4
- export function comparableSortValue(value) {
4
+ function comparableSortValue(value) {
5
5
  return value instanceof Date ? dateMilliseconds(value) : value;
6
6
  }
7
7
  /**
@@ -9,7 +9,24 @@ export declare function protectedSqlTextValue(value: string): string;
9
9
  export declare function externalSqlTextValue(value: unknown): unknown;
10
10
  export declare function isExactNumeric(value: unknown): value is string;
11
11
  export declare function exactNumericValue(value: unknown, precision?: number, scale?: number): string | null;
12
- export declare function exactNumericBinary(operator: "+" | "-" | "*" | "/" | "%", left: unknown, right: unknown): string | null | undefined;
12
+ /**
13
+ * Tags a SQL numeric constant with its exact digits, as written. Unlike `exactNumericValue`
14
+ * this does not canonicalize: trailing fractional zeros are PostgreSQL display scale, and
15
+ * division selects its result scale from the operands' scales, so a literal's digits are part
16
+ * of its meaning. Scientific notation is the exception — PostgreSQL expands the exponent when
17
+ * it parses the literal, so `1.5e2` is `150` and `1e400` is the full digit string, and the
18
+ * display scale comes from that expansion. The text is still validated and bounded.
19
+ */
20
+ export declare function exactNumericLiteral(text: string): string;
21
+ /**
22
+ * The Float64 this exact numeric value reads back identically from — the nearest float's
23
+ * canonical rendering re-parses to the same decimal value, so `0.1` qualifies while
24
+ * `9007199254740993` does not — or undefined when the trip through a number would visibly
25
+ * round. A value that survives stays an ordinary number so every number-typed path keeps
26
+ * running; only visibly rounded values stay tagged NUMERIC.
27
+ */
28
+ export declare function exactNumericAsNumber(value: string): number | undefined;
29
+ export declare function exactNumericBinary(operator: "+" | "-" | "*" | "/" | "%", left: unknown, right: unknown, minimumQuotientScale?: number, canonicalize?: boolean): string | null | undefined;
13
30
  export declare function exactNumericCompare(left: unknown, right: unknown): number | undefined;
14
31
  /**
15
32
  * Serializes a JSON value while bounding traversal, nesting, and output before concatenation.
@@ -33,7 +50,6 @@ export declare function isDateDomainValue(value: unknown): value is string;
33
50
  export declare function timeDomainValue(value: unknown): string | null;
34
51
  export declare function intervalDomainValue(value: unknown): string | null;
35
52
  export declare function arrayDomainValue(values: readonly unknown[]): string;
36
- export declare function enumDomainValue(value: unknown, name: string, values: readonly string[]): string | null;
37
53
  export declare function enumDomainCompare(left: unknown, right: unknown): number | undefined;
38
54
  export declare function normalizeSqlDomainValue(domain: SqlDomain, value: unknown): string | null;
39
55
  export declare function collatedDomainValue(value: unknown, collation: unknown): string | null;
@@ -48,4 +64,13 @@ export declare function collatedDomainCompare(left: unknown, right: unknown): nu
48
64
  */
49
65
  export declare function externalSqlDomainColumnValue(value: unknown, domain: SqlDomain | null | undefined): unknown;
50
66
  export declare function externalSqlDomainValue(value: unknown): unknown;
67
+ /**
68
+ * SQL `||` over internal string values, shared by both executors and constant folding so the
69
+ * three paths cannot disagree. PostgreSQL resolves `||` to text concatenation only when one
70
+ * side is text: its array and JSONB `||` operators are structural concatenation, which this
71
+ * engine does not implement, and two non-text operands have no `||` operator at all. Refusing
72
+ * those shapes keeps `||` from inventing a text concatenation PostgreSQL does not have —
73
+ * and from ever concatenating internal domain encodings.
74
+ */
75
+ export declare function concatenatedSqlValue(left: string, right: string): string;
51
76
  export declare function isSqlDomainValue(value: unknown): value is string;
@@ -53,6 +53,9 @@ function decimalParts(value) {
53
53
  let coefficient = BigInt(digits || "0") * (match[1] === "-" ? -1n : 1n);
54
54
  let scale = fraction.length - exponent;
55
55
  if (scale < 0) {
56
+ if (-scale > 100_000) {
57
+ throw new RangeError(`NUMERIC exponent is outside the supported range: ${source}`);
58
+ }
56
59
  coefficient *= pow10(-scale);
57
60
  scale = 0;
58
61
  }
@@ -122,7 +125,79 @@ export function exactNumericValue(value, precision, scale) {
122
125
  }
123
126
  return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(adjusted)), "NUMERIC value");
124
127
  }
125
- export function exactNumericBinary(operator, left, right) {
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
+ */
167
+ 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
+ };
178
+ }
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
+ 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) {
126
201
  const leftTagged = taggedDecimalParts(left);
127
202
  const rightTagged = taggedDecimalParts(right);
128
203
  if (leftTagged === undefined && rightTagged === undefined)
@@ -149,9 +224,10 @@ export function exactNumericBinary(operator, left, right) {
149
224
  };
150
225
  }
151
226
  else {
152
- // PostgreSQL NUMERIC division is arbitrary precision. Twenty fractional digits gives a
153
- // deterministic exact decimal rounding boundary without ever crossing binary Float64.
154
- const scale = Math.max(20, a.scale, b.scale);
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);
155
231
  const numerator = a.coefficient * pow10(scale + b.scale);
156
232
  const denominator = b.coefficient * pow10(a.scale);
157
233
  let coefficient = numerator / denominator;
@@ -162,7 +238,7 @@ export function exactNumericBinary(operator, left, right) {
162
238
  }
163
239
  result = { coefficient, scale };
164
240
  }
165
- return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(result)), "NUMERIC result");
241
+ return boundedTaggedDomainValue(NUMERIC, formatDecimal(canonicalize ? normalizeDecimal(result) : result), "NUMERIC result");
166
242
  }
167
243
  export function exactNumericCompare(left, right) {
168
244
  const leftTagged = taggedDecimalParts(left);
@@ -470,7 +546,7 @@ export function intervalDomainValue(value) {
470
546
  export function arrayDomainValue(values) {
471
547
  return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(values.map(externalSqlDomainValue), true, "ARRAY value"), "ARRAY value");
472
548
  }
473
- export function enumDomainValue(value, name, values) {
549
+ function enumDomainValue(value, name, values) {
474
550
  if (value === null || value === undefined)
475
551
  return null;
476
552
  if (typeof value !== "string")
@@ -667,6 +743,44 @@ export function externalSqlDomainValue(value) {
667
743
  }
668
744
  return value;
669
745
  }
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
+ 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(left, right) {
769
+ const leftKind = concatenationDomainKind(left);
770
+ const rightKind = concatenationDomainKind(right);
771
+ for (const kind of [leftKind, rightKind]) {
772
+ if (kind === "array") {
773
+ throw new TypeError("PostgreSQL array concatenation (||) is not supported");
774
+ }
775
+ if (kind === "jsonb") {
776
+ throw new TypeError("PostgreSQL JSONB concatenation (||) is not supported");
777
+ }
778
+ }
779
+ if (leftKind !== undefined && rightKind !== undefined) {
780
+ throw new TypeError(`No || operator for ${leftKind} and ${rightKind} values`);
781
+ }
782
+ return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
783
+ }
670
784
  export function isSqlDomainValue(value) {
671
785
  return typeof value === "string" && value.startsWith(PREFIX);
672
786
  }
@@ -29,8 +29,6 @@ export declare function jsonArrowStep(document: unknown, key: unknown, caller: s
29
29
  };
30
30
  /** Whether a value is JSON text of the requested shape (T825). */
31
31
  export declare function jsonIsValid(document: unknown, kind: string): boolean;
32
- /** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
33
- export declare function jsonValueOf(value: unknown): unknown;
34
32
  /**
35
33
  * JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). The omitted null-handling clause is
36
34
  * `NULL ON NULL`: SQL NULL becomes a JSON null. `ABSENT ON NULL` is a separate spelling rather
@@ -143,7 +143,7 @@ export function jsonIsValid(document, kind) {
143
143
  }
144
144
  }
145
145
  /** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
146
- export function jsonValueOf(value) {
146
+ function jsonValueOf(value) {
147
147
  const external = externalSqlDomainValue(value);
148
148
  return external instanceof Date ? dateIsoString(external) : external;
149
149
  }
@@ -19,7 +19,7 @@ export declare function encodeSqlEqualityValue(value: unknown): readonly unknown
19
19
  */
20
20
  export declare function roundSqlNumber(value: number, precision?: number): number;
21
21
  /** A whole-string SQL pattern matcher. Unlike RegExp, test never coerces its input. */
22
- export interface SqlPatternMatcher {
22
+ interface SqlPatternMatcher {
23
23
  test(value: string): boolean;
24
24
  }
25
25
  /** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
@@ -34,3 +34,4 @@ export declare function defineSqlResultProperty(target: Record<string, unknown>,
34
34
  * a silent coercion here would make `LENGTH(42)` answer instead of failing.
35
35
  */
36
36
  export declare function stringArgument(name: string, value: unknown): string;
37
+ export {};
@@ -1,42 +1,4 @@
1
- import { dateMilliseconds } from "../date-value.js";
2
- function sameLiveValue(left, right) {
3
- if (Object.is(left, right))
4
- return true;
5
- if (left instanceof Date || right instanceof Date) {
6
- return (left instanceof Date &&
7
- right instanceof Date &&
8
- Object.is(dateMilliseconds(left), dateMilliseconds(right)));
9
- }
10
- if (Array.isArray(left) || Array.isArray(right)) {
11
- if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
12
- return false;
13
- for (let index = 0; index < left.length; index += 1) {
14
- if (!sameLiveValue(left[index], right[index]))
15
- return false;
16
- }
17
- return true;
18
- }
19
- if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
20
- return false;
21
- }
22
- const leftRecord = left;
23
- const rightRecord = right;
24
- const leftKeys = Object.keys(leftRecord);
25
- const rightKeys = Object.keys(rightRecord);
26
- if (leftKeys.length !== rightKeys.length)
27
- return false;
28
- for (let index = 0; index < leftKeys.length; index += 1) {
29
- const key = leftKeys[index];
30
- if (key === undefined || key !== rightKeys[index])
31
- return false;
32
- if (!sameLiveValue(leftRecord[key], rightRecord[key]))
33
- return false;
34
- }
35
- return true;
36
- }
37
- function sameRows(left, right) {
38
- return sameLiveValue(left, right);
39
- }
1
+ import { sameLiveValue } from "./live-equal.js";
40
2
  function immutableRows(rows) {
41
3
  // Adapters own their values. Copy the array so mutating the returned builder result cannot
42
4
  // change the snapshot identity retained for exact suppression.
@@ -191,10 +153,10 @@ export class LiveQuery {
191
153
  const previous = this.#snapshot.rows;
192
154
  if (this.#snapshot.status === "ready" &&
193
155
  this.#snapshot.version === invalidation.manifestVersion &&
194
- sameRows(previous, rows)) {
156
+ sameLiveValue(previous, rows)) {
195
157
  continue;
196
158
  }
197
- if (sameRows(previous, rows) && this.#snapshot.status !== "loading") {
159
+ if (sameLiveValue(previous, rows) && this.#snapshot.status !== "loading") {
198
160
  // Advance the version without replacing the immutable row array.
199
161
  this.#snapshot = {
200
162
  status: "ready",
@@ -2,7 +2,7 @@ import type { DatabaseRow } from "./database.js";
2
2
  import type { CompiledQuery, QueryResult, QueryValue } from "./query.js";
3
3
  import { type FtsStats } from "./fts.js";
4
4
  import { QueryMemoryContext, type QueryMemoryUsage } from "./memory.js";
5
- export type VectorType = "boolean" | "number" | "string" | "datetime";
5
+ type VectorType = "boolean" | "number" | "string" | "datetime";
6
6
  /**
7
7
  * A resident slice of a logically longer streamed column. Window arrays index rows relative to
8
8
  * `start`; `length` on the vector remains the logical table length.
@@ -16,25 +16,25 @@ interface VectorBase {
16
16
  readonly length: number;
17
17
  readonly window?: VectorWindow;
18
18
  }
19
- export interface BooleanVector extends VectorBase {
19
+ interface BooleanVector extends VectorBase {
20
20
  readonly kind: "boolean";
21
21
  readonly values: Uint8Array;
22
22
  }
23
- export interface NumberVector extends VectorBase {
23
+ interface NumberVector extends VectorBase {
24
24
  readonly kind: "number";
25
25
  readonly values: Float64Array;
26
26
  }
27
- export interface DateTimeVector extends VectorBase {
27
+ interface DateTimeVector extends VectorBase {
28
28
  readonly kind: "datetime";
29
29
  readonly values: Float64Array;
30
30
  }
31
- export interface StringVector extends VectorBase {
31
+ interface StringVector extends VectorBase {
32
32
  readonly kind: "string";
33
33
  readonly codes: Uint32Array;
34
34
  readonly dictionary: readonly string[];
35
35
  }
36
36
  export type ColumnVector = BooleanVector | NumberVector | DateTimeVector | StringVector;
37
- export interface ColumnarColumnInput {
37
+ interface ColumnarColumnInput {
38
38
  readonly type: VectorType;
39
39
  readonly values: readonly QueryValue[];
40
40
  }
@@ -91,7 +91,7 @@ export interface QueryBatchExecutionOptions extends AsyncQueryExecutionOptions {
91
91
  /** Maximum result rows handed to the consumer at once. */
92
92
  readonly batchRows: number;
93
93
  }
94
- export interface PrepareVectorQueryOptions {
94
+ interface PrepareVectorQueryOptions {
95
95
  readonly memoryContext?: QueryMemoryContext;
96
96
  /**
97
97
  * Exact BM25 corpus statistics served by the persisted full-text index, keyed by each
@@ -1,4 +1,5 @@
1
1
  import { dateMilliseconds } from "../date-value.js";
2
+ import { crossJoinPlan, isCrossJoinPlan } from "../plan/model.js";
2
3
  import { MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, } from "../storage/types.js";
3
4
  import { throwIfAborted } from "./cancellation.js";
4
5
  import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, unknownColumnDomains, } from "./query.js";
@@ -9,7 +10,7 @@ import { ByteJoinIndex } from "./join-index.js";
9
10
  import { UnknownTableError } from "./errors.js";
10
11
  import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
11
12
  import { compareSqlValues, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
12
- import { exactNumericBinary, externalSqlDomainValue, isDateDomainValue, isExactNumeric, preservedJsonDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
13
+ import { concatenatedSqlValue, exactNumericBinary, externalSqlDomainValue, isDateDomainValue, isExactNumeric, preservedJsonDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
13
14
  import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
14
15
  const DEFAULT_BATCH_ROWS = 2_048;
15
16
  /** Above this, locating each IN member separately costs more than scanning between them. */
@@ -409,7 +410,7 @@ function disjunctiveNormalForm(predicate) {
409
410
  function orderCartesianJoins(plan, tables) {
410
411
  if (plan.joins.length < 2 ||
411
412
  plan.select.some((item) => item.expression.kind === "wildcard") ||
412
- plan.joins.some((join) => !isCartesianJoin(join))) {
413
+ plan.joins.some((join) => !isCrossJoinPlan(join))) {
413
414
  return plan;
414
415
  }
415
416
  const entries = [
@@ -454,21 +455,11 @@ function orderCartesianJoins(plan, tables) {
454
455
  return plan;
455
456
  const keyIndex = predicates.findIndex((predicate) => predicateJoinsSource(predicate, sourceIndex, available, entries, sourceTables));
456
457
  const key = keyIndex < 0 ? undefined : predicates.splice(keyIndex, 1)[0];
457
- joins.push(key === undefined ? cartesianJoin(source) : keyedInnerJoin(source, key));
458
+ joins.push(key === undefined ? crossJoinPlan(source) : keyedInnerJoin(source, key));
458
459
  available.add(sourceIndex);
459
460
  }
460
461
  return { ...plan, base: plan.base, joins, predicates };
461
462
  }
462
- function isCartesianJoin(join) {
463
- const condition = join.on;
464
- return (join.kind === "inner" &&
465
- condition?.kind === "condition" &&
466
- condition.operator === "=" &&
467
- condition.left.kind === "literal" &&
468
- condition.left.value === 1 &&
469
- condition.right.kind === "literal" &&
470
- condition.right.value === 1);
471
- }
472
463
  function tableSourceOf(join) {
473
464
  const { kind, left, right, on, full, natural, ...source } = join;
474
465
  void kind;
@@ -479,20 +470,6 @@ function tableSourceOf(join) {
479
470
  void natural;
480
471
  return source;
481
472
  }
482
- function cartesianJoin(source) {
483
- return {
484
- ...source,
485
- kind: "inner",
486
- left: { kind: "literal", value: null },
487
- right: { kind: "literal", value: null },
488
- on: {
489
- kind: "condition",
490
- operator: "=",
491
- left: { kind: "literal", value: 1 },
492
- right: { kind: "literal", value: 1 },
493
- },
494
- };
495
- }
496
473
  function keyedInnerJoin(source, predicate) {
497
474
  return {
498
475
  ...source,
@@ -892,6 +869,9 @@ function bindExpression(expression, sources, aggregateSpecs, aggregateIndexes, m
892
869
  ...(expression.distinct === true ? { distinct: true } : {}),
893
870
  ...(rawDatetime === undefined ? {} : { rawDatetime }),
894
871
  ...(rawNumber === undefined ? {} : { rawNumber }),
872
+ ...(expression.avgArgumentScale === undefined
873
+ ? {}
874
+ : { avgScale: expression.avgArgumentScale }),
895
875
  });
896
876
  }
897
877
  return {
@@ -4029,8 +4009,9 @@ function evaluateFinalExpression(plan, expression, group) {
4029
4009
  if (expression.name === "SUM")
4030
4010
  return isExactNumeric(value) ? value : (group.sums[aggregateIndex] ?? 0);
4031
4011
  if (expression.name === "AVG") {
4032
- if (isExactNumeric(value))
4033
- return exactNumericBinary("/", value, count);
4012
+ if (isExactNumeric(value)) {
4013
+ return exactNumericBinary("/", value, count, plan.aggregates[aggregateIndex]?.avgScale);
4014
+ }
4034
4015
  return (group.sums[aggregateIndex] ?? 0) / count;
4035
4016
  }
4036
4017
  // Raw-millisecond datetime extremes re-box into a Date only here, once per surviving group.
@@ -4764,7 +4745,7 @@ function binaryValue(operator, left, right) {
4764
4745
  if (typeof left !== "string" || typeof right !== "string") {
4765
4746
  throw new TypeError("|| requires string operands");
4766
4747
  }
4767
- return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
4748
+ return concatenatedSqlValue(left, right);
4768
4749
  }
4769
4750
  const exact = exactNumericBinary(operator, left, right);
4770
4751
  if (exact !== undefined)
@@ -4,7 +4,7 @@ export interface WriteColumnValues {
4
4
  readonly values: readonly unknown[];
5
5
  readonly stringByteLengths?: readonly number[];
6
6
  }
7
- export interface WriteBlockRange {
7
+ interface WriteBlockRange {
8
8
  readonly start: number;
9
9
  readonly end: number;
10
10
  }
@@ -16,3 +16,4 @@ export interface WriteBlockRange {
16
16
  export declare function planAlignedWriteBlockRanges(columns: readonly WriteColumnValues[], rowCount: number, maximumRows: number, targetBytes: number, measureString?: (value: string) => number): WriteBlockRange[];
17
17
  /** Conservative first estimate used by both physical compaction planners. */
18
18
  export declare function estimateCompactionRowsPerOutput(targetBlockBytes: number, maximumEncodedBytesPerRow: number): number;
19
+ export {};
@@ -40,6 +40,14 @@ export type Expression = {
40
40
  value: QueryValue;
41
41
  internalSqlValue?: true;
42
42
  sqlDomain?: SqlDomain;
43
+ /**
44
+ * A numeric constant's exact source digits, kept when the value survives the number
45
+ * boundary but the spelling carries more (`1.000000000000000000000000` is the number 1
46
+ * with display scale 24).
47
+ * Constant folding reads these so seeded arithmetic runs in exact decimal space, the way
48
+ * PostgreSQL types every decimal constant NUMERIC before evaluating it.
49
+ */
50
+ exactText?: string;
43
51
  }
44
52
  /** A `?` or `$n` placeholder; `index` is 0-based. Replaced by a literal at bind time. */
45
53
  | {
@@ -68,6 +76,13 @@ export type Expression = {
68
76
  direction: "asc" | "desc";
69
77
  nulls?: "first" | "last";
70
78
  }>;
79
+ /**
80
+ * AVG over a declared-scale NUMERIC column: the argument's declared scale, annotated by a
81
+ * schema-aware pass before execution. PostgreSQL floors its quotient scale at the summed
82
+ * operand's display scale; canonical NUMERIC encoding strips trailing zeros, so without
83
+ * this the internal division cannot know the digits the declared scale will render.
84
+ */
85
+ avgArgumentScale?: number;
71
86
  } | {
72
87
  kind: "list";
73
88
  items: Expression[];
@@ -175,6 +190,13 @@ export interface JoinPlan extends TableSource {
175
190
  full?: boolean;
176
191
  natural?: boolean;
177
192
  }
193
+ /**
194
+ * A cross join rides the nested-loop inner-join path with a condition every row pair satisfies:
195
+ * 1 = 1 over null literal key expressions. Producers build the shape with crossJoinPlan and
196
+ * consumers recognize it with isCrossJoinPlan, so the encoding lives in exactly one place.
197
+ */
198
+ export declare function crossJoinPlan(source: TableSource): JoinPlan;
199
+ export declare function isCrossJoinPlan(join: JoinPlan): boolean;
178
200
  export type SetOperator = "union" | "union all" | "intersect" | "intersect all" | "except" | "except all";
179
201
  export interface RecursiveCte {
180
202
  reference: string;