@minnowdb/core 0.6.4 → 0.6.6

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 (43) hide show
  1. package/dist/engine/artifact-cache.d.ts +2 -1
  2. package/dist/engine/batch.d.ts +0 -1
  3. package/dist/engine/batch.js +1 -1
  4. package/dist/engine/buffered-writer.js +1 -12
  5. package/dist/engine/byte-estimates.d.ts +11 -0
  6. package/dist/engine/byte-estimates.js +25 -0
  7. package/dist/engine/database.js +78 -38
  8. package/dist/engine/fts.d.ts +0 -1
  9. package/dist/engine/fts.js +1 -1
  10. package/dist/engine/join-index.d.ts +0 -1
  11. package/dist/engine/optimizer.js +11 -3
  12. package/dist/engine/point-read.d.ts +1 -1
  13. package/dist/engine/point-read.js +3 -2
  14. package/dist/engine/query-cache.js +1 -12
  15. package/dist/engine/query.d.ts +20 -59
  16. package/dist/engine/query.js +374 -71
  17. package/dist/engine/result-wire.d.ts +2 -1
  18. package/dist/engine/schema.d.ts +18 -2
  19. package/dist/engine/schema.js +11 -2
  20. package/dist/engine/sort-keys.d.ts +2 -3
  21. package/dist/engine/sort-keys.js +1 -1
  22. package/dist/engine/sql-domains.d.ts +36 -2
  23. package/dist/engine/sql-domains.js +143 -6
  24. package/dist/engine/sql-json.d.ts +12 -2
  25. package/dist/engine/sql-json.js +41 -1
  26. package/dist/engine/sql-semantics.d.ts +2 -1
  27. package/dist/engine/vector.d.ts +7 -7
  28. package/dist/engine/vector.js +8 -4
  29. package/dist/engine/write-block-planner.d.ts +2 -1
  30. package/dist/plan/model.d.ts +19 -0
  31. package/dist/storage/opfs/leader.d.ts +0 -4
  32. package/dist/storage/opfs/leader.js +2 -2
  33. package/dist/storage/opfs/snapshot-ledger.d.ts +3 -2
  34. package/dist/storage/toolkit/index.d.ts +1 -1
  35. package/dist/storage/toolkit/record-core.d.ts +0 -1
  36. package/dist/storage/toolkit/record-core.js +0 -7
  37. package/dist/testing/opfs-shim.d.ts +2 -1
  38. package/dist/testing/simulator.js +3 -0
  39. package/dist/worker-protocol/index.d.ts +1 -1
  40. package/dist/worker-protocol/index.js +0 -1
  41. package/package.json +2 -1
  42. package/postgres-feature-profile.json +18 -3
  43. package/sql-feature-matrix.json +95 -11
@@ -1,5 +1,5 @@
1
1
  /** Snapshot of the byte-bounded artifact cache's lifetime counters. */
2
- export interface ArtifactCacheStats {
2
+ interface ArtifactCacheStats {
3
3
  limitBytes: number;
4
4
  usedBytes: number;
5
5
  entries: number;
@@ -26,3 +26,4 @@ export declare class ArtifactCache {
26
26
  clear(): void;
27
27
  stats(): ArtifactCacheStats;
28
28
  }
29
+ export {};
@@ -31,6 +31,5 @@ export interface ColumnarBatch {
31
31
  }
32
32
  /** What `insertBatch` and `upsertBatch` take: rows, or columns for a bulk load. */
33
33
  export type InsertBatchInput = readonly BatchRow[] | ColumnarBatch;
34
- export declare function isColumnarBatch(input: InsertBatchInput): input is ColumnarBatch;
35
34
  /** Pivots rows into the engine's columnar form; a columnar batch passes straight through. */
36
35
  export declare function toColumnarBatch(input: InsertBatchInput): ColumnarBatch;
@@ -6,7 +6,7 @@
6
6
  * This module deliberately has no imports: the main-thread client normalizes here too, and must
7
7
  * not pull the engine in behind it.
8
8
  */
9
- export function isColumnarBatch(input) {
9
+ function isColumnarBatch(input) {
10
10
  return !Array.isArray(input);
11
11
  }
12
12
  /** Pivots rows into the engine's columnar form; a columnar batch passes straight through. */
@@ -1,4 +1,5 @@
1
1
  import { copyDate } from "../date-value.js";
2
+ import { estimateRowBytes } from "./byte-estimates.js";
2
3
  /** Maximum accepted `add()` calls that have not completed. Callers must await for backpressure. */
3
4
  export const MAX_BUFFERED_WRITER_PENDING_ADDS = 64;
4
5
  /** Batches row-oriented writes by row count, estimated bytes, or age. */
@@ -169,15 +170,3 @@ function cloneRow(row) {
169
170
  value instanceof Date ? copyDate(value) : value,
170
171
  ]));
171
172
  }
172
- function estimateRowBytes(row) {
173
- let bytes = 0;
174
- for (const value of Object.values(row)) {
175
- if (typeof value === "string")
176
- bytes += 4 + value.length;
177
- else if (typeof value === "number" || value instanceof Date)
178
- bytes += 8;
179
- else
180
- bytes += 1;
181
- }
182
- return bytes;
183
- }
@@ -0,0 +1,11 @@
1
+ import type { ColumnarBatch } from "./batch.js";
2
+ /**
3
+ * The one modeled per-value size used for flush thresholds, cache accounting, and write
4
+ * metrics. One byte per UTF-16 code unit approximates the UTF-8 payload (exact for ASCII)
5
+ * without encoding the string just to measure it — this estimate feeds metrics and flush
6
+ * thresholds, not the physical format. Keeping a single implementation keeps those
7
+ * accountings from drifting apart.
8
+ */
9
+ export declare function estimateValuesBytes(values: readonly unknown[]): number;
10
+ export declare function estimateRowBytes(row: Readonly<Record<string, unknown>>): number;
11
+ export declare function estimateBatchBytes(input: ColumnarBatch): number;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The one modeled per-value size used for flush thresholds, cache accounting, and write
3
+ * metrics. One byte per UTF-16 code unit approximates the UTF-8 payload (exact for ASCII)
4
+ * without encoding the string just to measure it — this estimate feeds metrics and flush
5
+ * thresholds, not the physical format. Keeping a single implementation keeps those
6
+ * accountings from drifting apart.
7
+ */
8
+ export function estimateValuesBytes(values) {
9
+ let bytes = 0;
10
+ for (const value of values) {
11
+ if (typeof value === "string")
12
+ bytes += 4 + value.length;
13
+ else if (typeof value === "number" || value instanceof Date)
14
+ bytes += 8;
15
+ else
16
+ bytes += 1;
17
+ }
18
+ return bytes;
19
+ }
20
+ export function estimateRowBytes(row) {
21
+ return estimateValuesBytes(Object.values(row));
22
+ }
23
+ export function estimateBatchBytes(input) {
24
+ return Object.values(input.columns).reduce((total, values) => total + estimateValuesBytes(values), 0);
25
+ }
@@ -1,5 +1,6 @@
1
1
  import { toColumnarBatch, } from "./batch.js";
2
2
  import { ArtifactCache } from "./artifact-cache.js";
3
+ import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byte-estimates.js";
3
4
  import { throwIfAborted } from "./cancellation.js";
4
5
  import { BufferedTableWriter } from "./buffered-writer.js";
5
6
  export { attachLifecycleFlush, BufferedTableWriter, MAX_BUFFERED_WRITER_PENDING_ADDS, } from "./buffered-writer.js";
@@ -14,13 +15,13 @@ import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize
14
15
  import { boundedMaintenanceBatchItems, simpleDataTypes, floorWholeNumberProduct, BlockReadBatchTooLargeError, validateColumnDefault, validateEnumValues, secondaryIndexColumnIds, secondaryIndexDirections, secondaryUniqueKeyNamespace, CompactionJobConflictError, CompactionBacklogError, GarbageCollectionJobConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_INDEXED_STRING_CHARACTERS, MAX_CATALOG_NAME_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MAINTENANCE_BATCH_ITEMS, MAX_STORAGE_BULK_READ_ITEMS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_TEMP_OWNER_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, validateStorageId, SnapshotManifestMissingError, SchemaConflictError, TableInUseError, TableRecordConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, } from "../storage/types.js";
15
16
  import { decodeSnapshotFrameStream, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, extendSnapshotFrameStreamChecksum, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity, } from "../storage/snapshot.js";
16
17
  import { Snapshot, TransactionManager, } from "../transactions/index.js";
17
- import { applyWindowFunctions, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDefaultInsertValue, referencedColumns, childExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, } from "./query.js";
18
+ import { applyWindowFunctions, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, annotateAvgArgumentScales, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDefaultInsertValue, referencedColumns, childExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, } from "./query.js";
18
19
  import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBytes, RESULT_MEMO_MAX_BYTES, } from "./query-cache.js";
19
20
  import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES, } from "./memory.js";
20
21
  import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE, } from "./live.js";
21
22
  import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan, } from "./optimizer.js";
22
23
  import { encodeSqlEqualityValue } from "./sql-semantics.js";
23
- import { externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
24
+ import { exactNumericAsNumber, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
24
25
  import { toCatalog } from "./catalog.js";
25
26
  import { applyColumnSteps, assertColumnDroppable, compileGeneratedColumnExpression, declaredForeignKeys, isDestructiveStep, planMigration, } from "./schema.js";
26
27
  import { columnarTableFromRows, createColumnarTable, vectorValue, } from "./vector.js";
@@ -2666,6 +2667,9 @@ export class MinnowDatabase {
2666
2667
  // columnar forms of derived and windowed sources too, not just the result memo.
2667
2668
  options.memoize !== false, options.spillToStorage !== false, options.spillToStorage === true, options.spillPageRows, options.signal);
2668
2669
  throwIfAborted(options.signal);
2670
+ // After input preparation, because executing the nested blocks registered their
2671
+ // synthetic source schemas in typedSchemas — same reasoning as the domain inference.
2672
+ resolvedPlan = annotateAvgArgumentScales(resolvedPlan, typedSchemas);
2669
2673
  outputNeedsExternalization = queryResultNeedsExternalization(resolvedPlan, typedSchemas);
2670
2674
  outputColumnDomains = inferResultColumnDomains(resolvedPlan, typedSchemas);
2671
2675
  };
@@ -3602,12 +3606,15 @@ export class MinnowDatabase {
3602
3606
  if (!keyComponents.every((component) => equalityColumns.has(component.name))) {
3603
3607
  return undefined;
3604
3608
  }
3609
+ // A logical-domain projection is served too: the stored scalar is the tagged internal
3610
+ // value the ordinary executor's pipeline carries for a bare column reference, and both
3611
+ // paths cross the result boundary through the same externalizeQueryResult call, so
3612
+ // reporting the column's domain below makes the answers identical by construction.
3605
3613
  const projected = [];
3606
3614
  for (const item of shape.select) {
3607
3615
  const column = columnByName.get(item.column);
3608
- if (column === undefined || column.hidden === true || column.sqlDomain !== undefined) {
3616
+ if (column === undefined || column.hidden === true)
3609
3617
  return undefined;
3610
- }
3611
3618
  projected.push({ column, alias: item.alias });
3612
3619
  }
3613
3620
  const segments = await this.#visibleSegmentRecords(table, snapshot, visibility);
@@ -3749,11 +3756,16 @@ export class MinnowDatabase {
3749
3756
  if (columnVector === undefined)
3750
3757
  return undefined;
3751
3758
  const value = vectorValue(columnVector, slot);
3752
- // A stored plain-text value in the protected NUL namespace crosses the result
3753
- // boundary through the ordinary executor's wrapping rules; reproducing them here
3754
- // is not worth the risk, so the whole statement falls back.
3755
- if (typeof value === "string" && value.charCodeAt(0) === 0)
3756
- return undefined;
3759
+ if (typeof value === "string" && value.charCodeAt(0) === 0) {
3760
+ // A domain column's stored scalar is expected here: it carries the internal
3761
+ // tag and is stripped by the shared externalization boundary. Anything else in
3762
+ // the NUL namespace plain text crossing through the ordinary executor's
3763
+ // wrapping rules, or an untagged value where a tag is required — is not
3764
+ // provably reproduced, so the whole statement falls back.
3765
+ if (item.column.sqlDomain === undefined || !isSqlDomainValue(value)) {
3766
+ return undefined;
3767
+ }
3768
+ }
3757
3769
  row[item.alias] = value;
3758
3770
  }
3759
3771
  rows.push(row);
@@ -3762,7 +3774,7 @@ export class MinnowDatabase {
3762
3774
  }
3763
3775
  return {
3764
3776
  columns: shape.select.map((item) => item.alias),
3765
- columnDomains: shape.select.map(() => null),
3777
+ columnDomains: projected.map((item) => item.column.sqlDomain ?? null),
3766
3778
  rows,
3767
3779
  };
3768
3780
  }
@@ -7791,6 +7803,8 @@ export class MinnowDatabase {
7791
7803
  ...(sqlDomain === undefined ? {} : { sqlDomain }),
7792
7804
  })),
7793
7805
  ]));
7806
+ // Streamed plans scan real tables only, so the catalog schemas resolve every AVG argument.
7807
+ plan = annotateAvgArgumentScales(plan, typedSchemas);
7794
7808
  const columns = referencedColumns(plan, schemas);
7795
7809
  // Streaming must choose the scan/build orientation before creating its sliding base view.
7796
7810
  // Plain append histories have exact row counts in segment metadata; mutation histories keep
@@ -8846,29 +8860,48 @@ export class MinnowDatabase {
8846
8860
  throwIfAborted(signal);
8847
8861
  const inputs = await this.#prepareBlockInputs(block, snapshot, visibility, memory, realTables, typedSchemas, extraInputs, cacheResults, allowSpill, forceSpill, spillPageRows, signal);
8848
8862
  throwIfAborted(signal);
8863
+ // After input preparation, because executing this block's nested sources registered their
8864
+ // synthetic schemas in typedSchemas, which is what resolves a derived column's domain.
8865
+ block = annotateAvgArgumentScales(block, typedSchemas);
8849
8866
  const prepared = createPreparedColumnarQuery(block, inputs, memory.createChild(), ftsStats === undefined ? {} : { ftsStats });
8867
+ // The columnar preparation only knows vector kinds, so a plain domain column projects with a
8868
+ // null domain. Catalog-backed inference fills those in — at miss time, because executing the
8869
+ // block registered its nested synthetic sources in typedSchemas — so a substituted scalar or
8870
+ // IN subquery literal carries its domain to the outer result (T694).
8871
+ const withCatalogColumnDomains = (result) => {
8872
+ if (!result.columnDomains.includes(null))
8873
+ return result;
8874
+ try {
8875
+ const inferred = inferResultColumnDomains(block, typedSchemas);
8876
+ result.columnDomains = result.columnDomains.map((domain, index) => domain ?? inferred[index] ?? null);
8877
+ }
8878
+ catch {
8879
+ // A shape this schema registry cannot type keeps its expression-level domains.
8880
+ }
8881
+ return result;
8882
+ };
8850
8883
  try {
8851
8884
  if (!allowSpill || memory.usage.budgetBytes === Number.MAX_SAFE_INTEGER) {
8852
8885
  const result = prepared.execute();
8853
8886
  throwIfAborted(signal);
8854
- return result;
8887
+ return withCatalogColumnDomains(result);
8855
8888
  }
8856
8889
  if (!forceSpill) {
8857
8890
  try {
8858
8891
  const result = prepared.execute();
8859
8892
  throwIfAborted(signal);
8860
- return result;
8893
+ return withCatalogColumnDomains(result);
8861
8894
  }
8862
8895
  catch (error) {
8863
8896
  if (!(error instanceof QueryMemoryBudgetError))
8864
8897
  throw error;
8865
8898
  }
8866
8899
  }
8867
- return await prepared.executeAsync({
8900
+ return withCatalogColumnDomains(await prepared.executeAsync({
8868
8901
  spillStore: this.#leasedSpillStore(),
8869
8902
  ...(spillPageRows === undefined ? {} : { spillPageRows }),
8870
8903
  ...(signal === undefined ? {} : { signal }),
8871
- });
8904
+ }));
8872
8905
  }
8873
8906
  finally {
8874
8907
  prepared.close();
@@ -15875,7 +15908,12 @@ function normalizeDomainBatch(table, input) {
15875
15908
  const values = input.columns[column.name];
15876
15909
  if (values === undefined)
15877
15910
  continue;
15878
- if (column.sqlDomain !== undefined || column.type === "datetime") {
15911
+ if (column.sqlDomain !== undefined ||
15912
+ column.type === "datetime" ||
15913
+ // A number column rewrites only when a tagged exact-NUMERIC constant actually reached it
15914
+ // (a SQL literal like 0.1/3 evaluated exactly), so plain bulk loads never copy here.
15915
+ (column.type === "number" &&
15916
+ values.some((value) => typeof value === "string" && isExactNumeric(value)))) {
15879
15917
  input.columns[column.name] = values.map((value) => normalizeColumnLogicalValue(column, value));
15880
15918
  }
15881
15919
  }
@@ -15889,6 +15927,18 @@ function normalizeColumnLogicalValue(column, value) {
15889
15927
  throw new TypeError("Invalid DATE value");
15890
15928
  return new Date(`${external}T00:00:00.000Z`);
15891
15929
  }
15930
+ if (column.type === "number" && typeof value === "string" && isExactNumeric(value)) {
15931
+ // PostgreSQL's assignment cast: an exact-NUMERIC value reaching a float column rounds to
15932
+ // the nearest Float64. An integer column keeps its exactness guarantee instead — a value
15933
+ // Float64 cannot hold exactly is rejected rather than silently rounded.
15934
+ const exact = exactNumericAsNumber(value);
15935
+ if (exact !== undefined)
15936
+ return exact;
15937
+ if (column.integer === true) {
15938
+ throw new TypeError(`${column.name} must be a safe integer`);
15939
+ }
15940
+ return Number(externalSqlDomainValue(value));
15941
+ }
15892
15942
  return value;
15893
15943
  }
15894
15944
  function selectBatchRows(input, indexes) {
@@ -15923,14 +15973,25 @@ function normalizeUpsertConflictWhere(table, predicate) {
15923
15973
  return { column, operator: predicate.operator, value };
15924
15974
  }
15925
15975
  function normalizeDomainUpdate(table, input) {
15926
- if (!table.columns.some((column) => column.sqlDomain !== undefined || column.type === "datetime"))
15976
+ const numberColumnNeedsCast = (column, values) => column.type === "number" &&
15977
+ values.some((value) => typeof value === "string" && isExactNumeric(value));
15978
+ if (!table.columns.some((column) => column.sqlDomain !== undefined || column.type === "datetime") &&
15979
+ !Object.entries(input.changes).some(([name, values]) => {
15980
+ const column = table.columns.find((candidate) => candidate.name === name);
15981
+ return column !== undefined && numberColumnNeedsCast(column, values);
15982
+ })) {
15927
15983
  return input;
15984
+ }
15928
15985
  let changed = false;
15929
15986
  const changes = { ...input.changes };
15930
15987
  for (const [name, values] of Object.entries(input.changes)) {
15931
15988
  const column = table.columns.find((candidate) => candidate.name === name);
15932
- if (column === undefined || (column.sqlDomain === undefined && column.type !== "datetime"))
15989
+ if (column === undefined ||
15990
+ (column.sqlDomain === undefined &&
15991
+ column.type !== "datetime" &&
15992
+ !numberColumnNeedsCast(column, values))) {
15933
15993
  continue;
15994
+ }
15934
15995
  changed = true;
15935
15996
  changes[name] = values.map((value) => normalizeColumnLogicalValue(column, value));
15936
15997
  }
@@ -18892,12 +18953,6 @@ function compactTableSkipped(tableName, skipReason, sourceSegments, sourceBlockI
18892
18953
  metrics: null,
18893
18954
  };
18894
18955
  }
18895
- function estimateRowBytes(row) {
18896
- return estimateValuesBytes(Object.values(row));
18897
- }
18898
- function estimateBatchBytes(input) {
18899
- return Object.values(input.columns).reduce((total, values) => total + estimateValuesBytes(values), 0);
18900
- }
18901
18956
  function writeColumnValues(type, values) {
18902
18957
  const cached = type === "string" ? validatedStringByteLengths.get(values) : undefined;
18903
18958
  return {
@@ -18925,21 +18980,6 @@ function maximumWriteBlockStoredBytes(column, start, end, compression) {
18925
18980
  : physicalBytes;
18926
18981
  return Math.min(MAX_STORED_BLOCK_BYTE_LENGTH, BLOCK_HEADER_LENGTH + MAX_BLOCK_METADATA_BYTE_LENGTH + storedPayloadBytes);
18927
18982
  }
18928
- function estimateValuesBytes(values) {
18929
- let bytes = 0;
18930
- for (const value of values) {
18931
- // One byte per UTF-16 code unit approximates the UTF-8 payload (exact for ASCII) without
18932
- // encoding the string just to measure it — this estimate feeds metrics and flush
18933
- // thresholds, not the physical format.
18934
- if (typeof value === "string")
18935
- bytes += 4 + value.length;
18936
- else if (typeof value === "number" || value instanceof Date)
18937
- bytes += 8;
18938
- else
18939
- bytes += 1;
18940
- }
18941
- return bytes;
18942
- }
18943
18983
  function cacheableQueryInput(sql, params) {
18944
18984
  let characters = sql.length;
18945
18985
  if (characters > MAX_CACHEABLE_TEXT_CHARACTERS)
@@ -27,7 +27,6 @@ export declare function tokenize(text: string): string[];
27
27
  * query matches nothing.
28
28
  */
29
29
  export declare function tokenizeQuery(query: string): FtsQueryTerm[];
30
- export declare function termMatches(token: string, term: FtsQueryTerm): boolean;
31
30
  /** Tokenized query terms with the same bounded caching scheme as the LIKE pattern cache. */
32
31
  export declare function cachedQueryTerms(query: string): FtsQueryTerm[];
33
32
  /** Compile-time validation shared by the SQL parser and the DSL expression builder. */
@@ -110,7 +110,7 @@ export function tokenizeQuery(query) {
110
110
  }
111
111
  return terms;
112
112
  }
113
- export function termMatches(token, term) {
113
+ function termMatches(token, term) {
114
114
  return term.prefix ? token.startsWith(term.term) : token === term.term;
115
115
  }
116
116
  /**
@@ -1,5 +1,4 @@
1
1
  import { QueryMemoryContext } from "./memory.js";
2
- export type JoinIndexKey = boolean | number | string | Date;
3
2
  /** Collision-checked scalar-key hash index with typed duplicate row chains. */
4
3
  export declare class ByteJoinIndex {
5
4
  #private;
@@ -1,6 +1,6 @@
1
1
  import { dateIsoString, dateMilliseconds } from "../date-value.js";
2
2
  import { blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, volatileScalarFunctionNames, } from "./query.js";
3
- import { isSqlDomainValue } from "./sql-domains.js";
3
+ import { concatenatedSqlValue, isSqlDomainValue } from "./sql-domains.js";
4
4
  /**
5
5
  * Deterministic plan-to-plan rewrites over the shared compiled representation. Every rule
6
6
  * preserves result semantics exactly; rules that cannot prove safety leave the plan unchanged.
@@ -2181,7 +2181,8 @@ function foldExpression(expression) {
2181
2181
  ? { kind: targetWord }
2182
2182
  : expression.name === "JSON_QUERY" ||
2183
2183
  expression.name === "JSON_OBJECT" ||
2184
- expression.name === "JSON_ARRAY"
2184
+ expression.name === "JSON_ARRAY" ||
2185
+ expression.name === "MINNOW_JSON_GET"
2185
2186
  ? { kind: "json" }
2186
2187
  : undefined;
2187
2188
  return {
@@ -2243,7 +2244,14 @@ function foldBinary(operator, leftValue, rightValue) {
2243
2244
  return null;
2244
2245
  if (operator === "||") {
2245
2246
  if (typeof leftValue === "string" && typeof rightValue === "string") {
2246
- return leftValue + rightValue;
2247
+ // Same helper the executors use, so folding cannot concatenate internal domain
2248
+ // encodings. A refused shape stays unfolded and raises the same error at execution.
2249
+ try {
2250
+ return concatenatedSqlValue(leftValue, rightValue);
2251
+ }
2252
+ catch {
2253
+ return undefined;
2254
+ }
2247
2255
  }
2248
2256
  return undefined;
2249
2257
  }
@@ -1,5 +1,5 @@
1
1
  import type { CompiledQuery, QueryValue } from "../plan/model.js";
2
- export type PointReadValue = boolean | number | string | Date;
2
+ type PointReadValue = boolean | number | string | Date;
3
3
  export interface PointReadEquality {
4
4
  column: string;
5
5
  value: PointReadValue;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Shape analysis for the keyed point-read fast path: a single-table conjunction of
3
- * column-equals-literal predicates that covers the table's unique key, projecting plain
4
- * columns. Such a statement addresses at most one row, so execution can skip parameter
3
+ * column-equals-literal predicates that covers the table's unique key, projecting bare
4
+ * columns plain or logical-domain typed, since domain scalars externalize through the
5
+ * same result boundary. Such a statement addresses at most one row, so execution can skip parameter
5
6
  * binding, plan cloning, streamed-view construction, and the vector pipeline entirely and
6
7
  * answer from cached decoded blocks. Anything this module cannot prove eligible falls back to
7
8
  * the ordinary executor, which stays the authority on errors and semantics.
@@ -1,4 +1,5 @@
1
1
  import { copyDate, dateMilliseconds } from "../date-value.js";
2
+ import { estimateValuesBytes } from "./byte-estimates.js";
2
3
  import { copyQueryResultExternalization } from "./query.js";
3
4
  import { defineSqlResultProperty } from "./sql-semantics.js";
4
5
  /** Modest per-entry cap so one giant result cannot thrash the shared artifact cache. */
@@ -60,18 +61,6 @@ export function queryResultRetainedBytes(result) {
60
61
  bytes += estimateValuesBytes(Object.values(row));
61
62
  return bytes;
62
63
  }
63
- function estimateValuesBytes(values) {
64
- let bytes = 0;
65
- for (const value of values) {
66
- if (typeof value === "string")
67
- bytes += 4 + value.length;
68
- else if (typeof value === "number" || value instanceof Date)
69
- bytes += 8;
70
- else
71
- bytes += 1;
72
- }
73
- return bytes;
74
- }
75
64
  function encodeParameter(value) {
76
65
  if (value === null)
77
66
  return [0];
@@ -31,12 +31,6 @@ export interface QueryExecutionOptions {
31
31
  export declare const scalarFunctionNames: ReadonlySet<string>;
32
32
  /** Functions whose answer can change without any catalog or input-row change. */
33
33
  export declare const volatileScalarFunctionNames: ReadonlySet<string>;
34
- /**
35
- * The niladic datetime functions (F051-06/07/08). Their value is the statement's own clock
36
- * reading, so they are resolved once per execution rather than evaluated per row: every row of
37
- * one statement sees one instant, both executors agree, and constant folding leaves them alone.
38
- */
39
- export declare const statementDatetimeNames: ReadonlySet<string>;
40
34
  export declare function isScalarFunctionName(name: AggregateName | ScalarFunctionName): name is ScalarFunctionName;
41
35
  /**
42
36
  * Evaluates one scalar function over already-evaluated argument values. Every executor calls
@@ -46,23 +40,6 @@ export declare function isScalarFunctionName(name: AggregateName | ScalarFunctio
46
40
  * SUBSTR count characters, not UTF-16 units, matching SQLite and PostgreSQL.
47
41
  */
48
42
  export declare function scalarFunctionValue(name: Exclude<ScalarFunctionName, "COALESCE">, values: readonly unknown[]): unknown;
49
- export declare const dateTruncUnits: ReadonlySet<string>;
50
- /**
51
- * `INTERVAL '1 month'`, `INTERVAL '2 years 3 days'`. Months are kept apart from milliseconds
52
- * rather than converted, because a month is not a fixed number of them: adding one to January 31
53
- * has to land on the end of February, which only calendar arithmetic can do.
54
- */
55
- export declare function intervalLiteral(text: string): {
56
- months: number;
57
- milliseconds: number;
58
- };
59
- /**
60
- * A datetime shifted by a calendar interval: whole months first, then milliseconds. A day that
61
- * does not exist in the target month clamps to that month's last day, which is what both SQLite
62
- * and PostgreSQL do with 31 January plus a month.
63
- */
64
- export declare function dateAddValue(value: unknown, months: unknown, milliseconds: unknown): Date | string | null;
65
- export declare function dateTruncValue(unit: unknown, value: unknown): Date | null;
66
43
  /** The output column type of one window: rankings and most aggregates count, MIN/MAX carry. */
67
44
  export declare function windowOutputType(window: WindowSpec, innerSchema: readonly SqlColumnSchema[]): SqlColumnType;
68
45
  /** Logical domain carried or produced by a window result, when its physical type is not enough. */
@@ -75,7 +52,7 @@ export declare function windowOutputDomain(window: WindowSpec, innerSchema: read
75
52
  export declare const DUAL_TABLE = "(dual)";
76
53
  /** The dual source's single row, shared by every resolution path. */
77
54
  export declare function dualTableRows(): DatabaseRow[];
78
- export interface CompileQueryOptions {
55
+ interface CompileQueryOptions {
79
56
  /** Set false to skip deterministic plan rewrites, for example to snapshot the raw plan. */
80
57
  readonly optimize?: boolean;
81
58
  }
@@ -84,7 +61,7 @@ export declare function compileQuery(sql: string, options?: CompileQueryOptions)
84
61
  * One WHEN clause of a MERGE (F312). Branches are tried in order for each source row, and the
85
62
  * first whose match state and optional condition hold decides what happens to that row.
86
63
  */
87
- export type MergeBranch = {
64
+ type MergeBranch = {
88
65
  when: "matched";
89
66
  condition?: Expression;
90
67
  action: {
@@ -125,7 +102,7 @@ export interface UniqueConstraintDefinition {
125
102
  columns: string[];
126
103
  }
127
104
  /** An INSERT value: a constant, SQL DEFAULT, or an unbound placeholder. */
128
- export interface DefaultInsertValue {
105
+ interface DefaultInsertValue {
129
106
  readonly default: true;
130
107
  }
131
108
  export type InsertValue = QueryValue | DefaultInsertValue | {
@@ -344,7 +321,7 @@ export type CompiledStatement = {
344
321
  * row condition over this table's columns, with no aggregate, window, subquery, or parameter.
345
322
  */
346
323
  export declare function compileCheckExpression(sql: string, name: string): Expression;
347
- export interface DefaultExpressionTarget {
324
+ interface DefaultExpressionTarget {
348
325
  readonly name: string;
349
326
  readonly type: SqlColumnType;
350
327
  readonly sqlDomain?: SqlDomain;
@@ -368,7 +345,7 @@ export declare function compileStatement(sql: string): CompiledStatement;
368
345
  export declare function evaluateJoinedRowExpression(expression: Expression, rows: Readonly<Record<string, DatabaseRow | undefined>>): QueryValue;
369
346
  /** Evaluates an expression against one row, for UPDATE SET assignment computation. */
370
347
  export declare function evaluateRowExpression(expression: Expression, alias: string, row: DatabaseRow): QueryValue;
371
- export interface SubqueryResolutionStep {
348
+ interface SubqueryResolutionStep {
372
349
  /** The uncorrelated block to execute; earlier steps have already substituted inside it. */
373
350
  readonly block: CompiledQuery;
374
351
  /** Replaces the subquery node with the executed result as literals. */
@@ -386,9 +363,6 @@ export declare function subqueryResolutionSteps(plan: CompiledQuery): {
386
363
  steps: SubqueryResolutionStep[];
387
364
  };
388
365
  export declare function blockHasSubqueries(plan: CompiledQuery): boolean;
389
- /** True when any `?`/`$n` placeholder remains anywhere in the expression tree. */
390
- export declare function containsParameter(expression: Expression): boolean;
391
- export declare function blockHasParameters(block: CompiledQuery): boolean;
392
366
  /**
393
367
  * A deep copy of a compiled plan, statement, or expression tree. Plans are plain data — objects,
394
368
  * arrays, primitives, and Date literals — so a direct recursive copy does what structuredClone
@@ -486,7 +460,7 @@ export declare function mapBlockExpressions(block: CompiledQuery, map: (expressi
486
460
  */
487
461
  export declare function resolveStatementDatetimes(plan: CompiledQuery, now?: Date): CompiledQuery;
488
462
  /** One top-level full-text MATCH conjunct of a plan, with its resolved column expressions. */
489
- export interface FtsMatchConjunct {
463
+ interface FtsMatchConjunct {
490
464
  columns: Expression[];
491
465
  query: string;
492
466
  }
@@ -520,6 +494,17 @@ export declare function planContainsFts(plan: CompiledQuery, op?: "match" | "bm2
520
494
  * later migrations.
521
495
  */
522
496
  export declare function expandFtsColumns(plan: CompiledQuery, searchableColumnsFor: (tableName: string) => readonly string[] | undefined): CompiledQuery;
497
+ /**
498
+ * Annotates every `AVG(column)` in this block's expression positions with the argument column's
499
+ * declared NUMERIC scale, resolved against the given schemas. PostgreSQL floors an AVG's
500
+ * internal division scale at the summed values' display scale; the canonical NUMERIC encoding
501
+ * strips trailing fractional zeros, so without the annotation the divide cannot know the digits
502
+ * a declared scale above its own selection would render, and the display padding would fabricate
503
+ * zeros where PostgreSQL computes real digits. Runs where the catalog is known, per block —
504
+ * nested blocks execute through their own schema-aware entry. Copy-on-write: plans without an
505
+ * AVG pass through untouched, and the input is often the compile cache's own object.
506
+ */
507
+ export declare function annotateAvgArgumentScales(plan: CompiledQuery, schemas: ReadonlyMap<string, readonly SqlColumnSchema[]>): CompiledQuery;
523
508
  /**
524
509
  * Appends window-function columns to an executed inner-block result. Rows sort stably by the
525
510
  * hidden partition and ordering aliases with the same comparison semantics as ORDER BY;
@@ -532,7 +517,7 @@ export declare function applyWindowFunctions(result: QueryResult, windows: reado
532
517
  /** Correctness reference retained while the vector executor matures. */
533
518
  export declare function executeRowQuery(plan: CompiledQuery, tables: ReadonlyMap<string, DatabaseRow[]>): QueryResult;
534
519
  /** One ORDER BY resolution source: an alias and the columns a wildcard select exposes from it. */
535
- export interface OrderSourceShape {
520
+ interface OrderSourceShape {
536
521
  readonly alias: string;
537
522
  readonly columns: readonly string[];
538
523
  }
@@ -543,7 +528,7 @@ export interface OrderSourceShape {
543
528
  * repeated rows.
544
529
  */
545
530
  export declare function orderOutputName(expression: Expression, select: readonly SelectItem[], sources: readonly OrderSourceShape[]): string;
546
- export interface ListMembership {
531
+ interface ListMembership {
547
532
  set: ReadonlySet<unknown>;
548
533
  hasNull: boolean;
549
534
  }
@@ -604,7 +589,7 @@ export declare function nullOrder(left: unknown, right: unknown, nulls: "first"
604
589
  export declare function copyQueryResultExternalization(source: QueryResult, copy: QueryResult): QueryResult;
605
590
  /** Removes internal domain tags only after every comparison, group, join, and sort is complete. */
606
591
  export declare function externalizeQueryResult(result: QueryResult): QueryResult;
607
- export interface SelectBlockParts {
592
+ interface SelectBlockParts {
608
593
  sql: string;
609
594
  base: TableSource;
610
595
  joins: JoinPlan[];
@@ -624,13 +609,6 @@ export interface SelectBlockParts {
624
609
  groupingSets?: Expression[][];
625
610
  }
626
611
  export declare function assembleSelectBlock(parts: SelectBlockParts, nextSequence: () => number): CompiledQuery;
627
- /**
628
- * Expands a pending SELECT DISTINCT * against known input columns: the select list becomes
629
- * every wildcard output (alias-qualified when more than one source contributes), and GROUP BY
630
- * over those same columns provides the deduplication through the grouped executor. Runs
631
- * exactly once per execution entry, like MATCH(*) expansion.
632
- */
633
- export declare function expandDistinctWildcard(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
634
612
  /**
635
613
  * Turns every source carrying a column alias list into a derived projection that renames the
636
614
  * table's columns positionally (E051-09). The table's own column order is only known here, so
@@ -648,15 +626,6 @@ export declare function planReadsViews(plan: CompiledQuery, isView: (name: strin
648
626
  */
649
627
  export declare function expandViewSources(plan: CompiledQuery, viewFor: (tableName: string) => CompiledQuery | undefined, maxDepth?: number): CompiledQuery;
650
628
  export declare function planHasSourceColumnAliases(plan: CompiledQuery): boolean;
651
- /**
652
- * FETCH FIRST n ROWS WITH TIES (F866). The plan runs without its limit — a limit pushed into a
653
- * scan cannot know whether the next row ties — and the ordered result is trimmed here: rows up
654
- * to the limit, plus every following row equal to the last one on all ORDER BY columns.
655
- */
656
- export declare function withTiesPlan(plan: CompiledQuery): {
657
- plan: CompiledQuery;
658
- trim: (result: QueryResult) => QueryResult;
659
- };
660
629
  /** Whether any block of the plan still carries an unresolved NATURAL join marker. */
661
630
  export declare function planHasNaturalJoins(plan: CompiledQuery): boolean;
662
631
  /**
@@ -666,13 +635,6 @@ export declare function planHasNaturalJoins(plan: CompiledQuery): boolean;
666
635
  */
667
636
  export declare function expandNaturalJoins(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
668
637
  export declare function expandSourceColumnAliases(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
669
- /**
670
- * Expands `alias.*` select items into that source's columns (E051-07), at every nesting depth.
671
- * Output names follow the same rule as a bare `*`: the column's own name when the block reads
672
- * one source, and `alias.column` when it reads several, so two sources cannot collide.
673
- * Runs once per execution entry, like MATCH(*) and DISTINCT * expansion.
674
- */
675
- export declare function expandQualifiedWildcards(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
676
638
  /** Wraps compound members into the set-operation source the executor folds left to right. */
677
639
  export declare function compoundSelectBlock(sql: string, blocks: CompiledQuery[], ops: SetOperator[], tail: SelectTail, nextSequence: () => number): CompiledQuery;
678
640
  /**
@@ -693,7 +655,6 @@ export declare function projectResultColumns(result: QueryResult, aliases: reado
693
655
  export declare function derivedTableSource(derived: CompiledQuery, alias: string, nextSequence: () => number): TableSource;
694
656
  /** The parser's LIMIT range contract, shared with the typed builder. */
695
657
  export declare function validateLimit(limit: number): number;
696
- export declare function timestampLiteral(text: string): Date;
697
658
  /** The parser's OFFSET range contract, shared with the typed builder. */
698
659
  export declare function validateOffset(offset: number): number;
699
660
  /**