@minnowdb/core 0.6.5 → 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.
- package/dist/engine/artifact-cache.d.ts +2 -1
- package/dist/engine/batch.d.ts +0 -1
- package/dist/engine/batch.js +1 -1
- package/dist/engine/buffered-writer.js +1 -12
- package/dist/engine/byte-estimates.d.ts +11 -0
- package/dist/engine/byte-estimates.js +25 -0
- package/dist/engine/database.js +58 -34
- package/dist/engine/fts.d.ts +0 -1
- package/dist/engine/fts.js +1 -1
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/optimizer.js +9 -2
- package/dist/engine/point-read.d.ts +1 -1
- package/dist/engine/point-read.js +3 -2
- package/dist/engine/query-cache.js +1 -12
- package/dist/engine/query.d.ts +20 -59
- package/dist/engine/query.js +280 -39
- package/dist/engine/result-wire.d.ts +2 -1
- package/dist/engine/schema.d.ts +18 -2
- package/dist/engine/schema.js +11 -2
- package/dist/engine/sort-keys.d.ts +2 -3
- package/dist/engine/sort-keys.js +1 -1
- package/dist/engine/sql-domains.d.ts +27 -2
- package/dist/engine/sql-domains.js +120 -6
- package/dist/engine/sql-json.d.ts +0 -2
- package/dist/engine/sql-json.js +1 -1
- package/dist/engine/sql-semantics.d.ts +2 -1
- package/dist/engine/vector.d.ts +7 -7
- package/dist/engine/vector.js +8 -4
- package/dist/engine/write-block-planner.d.ts +2 -1
- package/dist/plan/model.d.ts +15 -0
- package/dist/storage/opfs/leader.d.ts +0 -4
- package/dist/storage/opfs/leader.js +2 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +3 -2
- package/dist/storage/toolkit/index.d.ts +1 -1
- package/dist/storage/toolkit/record-core.d.ts +0 -1
- package/dist/storage/toolkit/record-core.js +0 -7
- package/dist/testing/opfs-shim.d.ts +2 -1
- package/dist/testing/simulator.js +3 -0
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +0 -1
- package/package.json +2 -1
- package/postgres-feature-profile.json +8 -3
- package/sql-feature-matrix.json +71 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Snapshot of the byte-bounded artifact cache's lifetime counters. */
|
|
2
|
-
|
|
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 {};
|
package/dist/engine/batch.d.ts
CHANGED
|
@@ -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;
|
package/dist/engine/batch.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/dist/engine/database.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
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:
|
|
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,6 +8860,9 @@ 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 });
|
|
8850
8867
|
// The columnar preparation only knows vector kinds, so a plain domain column projects with a
|
|
8851
8868
|
// null domain. Catalog-backed inference fills those in — at miss time, because executing the
|
|
@@ -15891,7 +15908,12 @@ function normalizeDomainBatch(table, input) {
|
|
|
15891
15908
|
const values = input.columns[column.name];
|
|
15892
15909
|
if (values === undefined)
|
|
15893
15910
|
continue;
|
|
15894
|
-
if (column.sqlDomain !== undefined ||
|
|
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)))) {
|
|
15895
15917
|
input.columns[column.name] = values.map((value) => normalizeColumnLogicalValue(column, value));
|
|
15896
15918
|
}
|
|
15897
15919
|
}
|
|
@@ -15905,6 +15927,18 @@ function normalizeColumnLogicalValue(column, value) {
|
|
|
15905
15927
|
throw new TypeError("Invalid DATE value");
|
|
15906
15928
|
return new Date(`${external}T00:00:00.000Z`);
|
|
15907
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
|
+
}
|
|
15908
15942
|
return value;
|
|
15909
15943
|
}
|
|
15910
15944
|
function selectBatchRows(input, indexes) {
|
|
@@ -15939,14 +15973,25 @@ function normalizeUpsertConflictWhere(table, predicate) {
|
|
|
15939
15973
|
return { column, operator: predicate.operator, value };
|
|
15940
15974
|
}
|
|
15941
15975
|
function normalizeDomainUpdate(table, input) {
|
|
15942
|
-
|
|
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
|
+
})) {
|
|
15943
15983
|
return input;
|
|
15984
|
+
}
|
|
15944
15985
|
let changed = false;
|
|
15945
15986
|
const changes = { ...input.changes };
|
|
15946
15987
|
for (const [name, values] of Object.entries(input.changes)) {
|
|
15947
15988
|
const column = table.columns.find((candidate) => candidate.name === name);
|
|
15948
|
-
if (column === undefined ||
|
|
15989
|
+
if (column === undefined ||
|
|
15990
|
+
(column.sqlDomain === undefined &&
|
|
15991
|
+
column.type !== "datetime" &&
|
|
15992
|
+
!numberColumnNeedsCast(column, values))) {
|
|
15949
15993
|
continue;
|
|
15994
|
+
}
|
|
15950
15995
|
changed = true;
|
|
15951
15996
|
changes[name] = values.map((value) => normalizeColumnLogicalValue(column, value));
|
|
15952
15997
|
}
|
|
@@ -18908,12 +18953,6 @@ function compactTableSkipped(tableName, skipReason, sourceSegments, sourceBlockI
|
|
|
18908
18953
|
metrics: null,
|
|
18909
18954
|
};
|
|
18910
18955
|
}
|
|
18911
|
-
function estimateRowBytes(row) {
|
|
18912
|
-
return estimateValuesBytes(Object.values(row));
|
|
18913
|
-
}
|
|
18914
|
-
function estimateBatchBytes(input) {
|
|
18915
|
-
return Object.values(input.columns).reduce((total, values) => total + estimateValuesBytes(values), 0);
|
|
18916
|
-
}
|
|
18917
18956
|
function writeColumnValues(type, values) {
|
|
18918
18957
|
const cached = type === "string" ? validatedStringByteLengths.get(values) : undefined;
|
|
18919
18958
|
return {
|
|
@@ -18941,21 +18980,6 @@ function maximumWriteBlockStoredBytes(column, start, end, compression) {
|
|
|
18941
18980
|
: physicalBytes;
|
|
18942
18981
|
return Math.min(MAX_STORED_BLOCK_BYTE_LENGTH, BLOCK_HEADER_LENGTH + MAX_BLOCK_METADATA_BYTE_LENGTH + storedPayloadBytes);
|
|
18943
18982
|
}
|
|
18944
|
-
function estimateValuesBytes(values) {
|
|
18945
|
-
let bytes = 0;
|
|
18946
|
-
for (const value of values) {
|
|
18947
|
-
// One byte per UTF-16 code unit approximates the UTF-8 payload (exact for ASCII) without
|
|
18948
|
-
// encoding the string just to measure it — this estimate feeds metrics and flush
|
|
18949
|
-
// thresholds, not the physical format.
|
|
18950
|
-
if (typeof value === "string")
|
|
18951
|
-
bytes += 4 + value.length;
|
|
18952
|
-
else if (typeof value === "number" || value instanceof Date)
|
|
18953
|
-
bytes += 8;
|
|
18954
|
-
else
|
|
18955
|
-
bytes += 1;
|
|
18956
|
-
}
|
|
18957
|
-
return bytes;
|
|
18958
|
-
}
|
|
18959
18983
|
function cacheableQueryInput(sql, params) {
|
|
18960
18984
|
let characters = sql.length;
|
|
18961
18985
|
if (characters > MAX_CACHEABLE_TEXT_CHARACTERS)
|
package/dist/engine/fts.d.ts
CHANGED
|
@@ -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. */
|
package/dist/engine/fts.js
CHANGED
package/dist/engine/optimizer.js
CHANGED
|
@@ -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.
|
|
@@ -2244,7 +2244,14 @@ function foldBinary(operator, leftValue, rightValue) {
|
|
|
2244
2244
|
return null;
|
|
2245
2245
|
if (operator === "||") {
|
|
2246
2246
|
if (typeof leftValue === "string" && typeof rightValue === "string") {
|
|
2247
|
-
|
|
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
|
+
}
|
|
2248
2255
|
}
|
|
2249
2256
|
return undefined;
|
|
2250
2257
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CompiledQuery, QueryValue } from "../plan/model.js";
|
|
2
|
-
|
|
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
|
|
4
|
-
* columns
|
|
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];
|
package/dist/engine/query.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
/**
|