@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.
- package/dist/block-format/codecs.d.ts +0 -8
- package/dist/block-format/codecs.js +1 -1
- 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/group-index.d.ts +2 -0
- package/dist/engine/group-index.js +4 -8
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/join-index.js +1 -15
- package/dist/engine/keyed-live.js +2 -36
- package/dist/engine/live-equal.d.ts +7 -0
- package/dist/engine/live-equal.js +42 -0
- package/dist/engine/optimizer.js +11 -16
- 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 +286 -54
- package/dist/engine/result-wire.d.ts +2 -1
- package/dist/engine/schema.d.ts +15 -11
- package/dist/engine/schema.js +11 -19
- 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/typed-live.js +3 -41
- package/dist/engine/vector.d.ts +7 -7
- package/dist/engine/vector.js +11 -30
- package/dist/engine/write-block-planner.d.ts +2 -1
- package/dist/plan/model.d.ts +22 -0
- package/dist/plan/model.js +29 -1
- package/dist/storage/indexeddb.js +13 -23
- package/dist/storage/opfs/leader.d.ts +0 -4
- package/dist/storage/opfs/leader.js +5 -14
- 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 +3 -19
- package/dist/storage/types.d.ts +4 -3
- package/dist/storage/types.js +13 -2
- package/dist/testing/block-store-conformance.js +28 -0
- package/dist/testing/opfs-shim.d.ts +2 -1
- package/dist/testing/simulator.js +3 -0
- package/dist/worker-protocol/index.d.ts +0 -32
- package/dist/worker-protocol/index.js +0 -40
- package/package.json +2 -1
- package/postgres-feature-profile.json +8 -3
- package/sql-feature-matrix.json +71 -11
|
@@ -16,14 +16,6 @@ export interface CompressionMemoryBound {
|
|
|
16
16
|
/** Additional JavaScript-owned byte storage retained while producing the result. */
|
|
17
17
|
readonly scratchBytes: number;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* The raw codec returns its input view unchanged in both directions: callers treat compressed
|
|
21
|
-
* and decompressed payloads as read-only (encode copies the payload into the block envelope,
|
|
22
|
-
* decode consumers only read or copy out), so a full-payload defensive copy per block would be
|
|
23
|
-
* pure overhead on the default compression path. The returned view may share the caller's
|
|
24
|
-
* buffer at a non-zero byte offset.
|
|
25
|
-
*/
|
|
26
|
-
export declare const rawCodec: CompressionCodec;
|
|
27
19
|
export declare function getCompressionMemoryBound(compression: Compression, inputLength: number): CompressionMemoryBound;
|
|
28
20
|
export declare const gzipCodec: CompressionCodec;
|
|
29
21
|
export declare function getCodec(id: Compression): CompressionCodec;
|
|
@@ -16,7 +16,7 @@ export class CompressionOutputLimitError extends RangeError {
|
|
|
16
16
|
* pure overhead on the default compression path. The returned view may share the caller's
|
|
17
17
|
* buffer at a non-zero byte offset.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
const rawCodec = {
|
|
20
20
|
id: "raw",
|
|
21
21
|
async compress(bytes, maximumOutputLength) {
|
|
22
22
|
if (maximumOutputLength !== undefined) {
|
|
@@ -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
|
@@ -29,3 +29,5 @@ export declare function hashScratch(length: number): number;
|
|
|
29
29
|
export declare function equalsScratch(arena: Uint8Array, offset: number, length: number): boolean;
|
|
30
30
|
/** Copies the scratch arena's first `length` bytes into owned storage. Exported for the join index. */
|
|
31
31
|
export declare function copyScratchKey(length: number): Uint8Array;
|
|
32
|
+
export declare function safeDouble(value: number, label: string): number;
|
|
33
|
+
export declare function safeProduct(left: number, right: number, label: string): number;
|
|
@@ -49,7 +49,7 @@ export class ByteGroupIndex {
|
|
|
49
49
|
return this.get([]);
|
|
50
50
|
}
|
|
51
51
|
getOne(key) {
|
|
52
|
-
const length =
|
|
52
|
+
const length = encodeSingleScalarKey(key);
|
|
53
53
|
const index = this.#find(length, hashScratch(length));
|
|
54
54
|
return index < 0 ? undefined : this.#values[index];
|
|
55
55
|
}
|
|
@@ -68,7 +68,7 @@ export class ByteGroupIndex {
|
|
|
68
68
|
return this.#getOrInsertScratch(length, create);
|
|
69
69
|
}
|
|
70
70
|
getOrInsertOne(key, create) {
|
|
71
|
-
const length =
|
|
71
|
+
const length = encodeSingleScalarKey(key);
|
|
72
72
|
return this.#getOrInsertScratch(length, create);
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
@@ -231,10 +231,6 @@ export function encodeSingleScalarKey(key) {
|
|
|
231
231
|
reclaimScratch();
|
|
232
232
|
return writeGroupKey(key, 0);
|
|
233
233
|
}
|
|
234
|
-
function encodeSingleGroupKey(key) {
|
|
235
|
-
reclaimScratch();
|
|
236
|
-
return writeGroupKey(key, 0);
|
|
237
|
-
}
|
|
238
234
|
function writeGroupKey(key, offset) {
|
|
239
235
|
if (key === null) {
|
|
240
236
|
ensureScratchCapacity(offset + 1);
|
|
@@ -341,13 +337,13 @@ export function equalsScratch(arena, offset, length) {
|
|
|
341
337
|
export function copyScratchKey(length) {
|
|
342
338
|
return scratch.slice(0, length);
|
|
343
339
|
}
|
|
344
|
-
function safeDouble(value, label) {
|
|
340
|
+
export function safeDouble(value, label) {
|
|
345
341
|
const doubled = value * 2;
|
|
346
342
|
if (!Number.isSafeInteger(doubled))
|
|
347
343
|
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
348
344
|
return doubled;
|
|
349
345
|
}
|
|
350
|
-
function safeProduct(left, right, label) {
|
|
346
|
+
export function safeProduct(left, right, label) {
|
|
351
347
|
const product = left * right;
|
|
352
348
|
if (!Number.isSafeInteger(product))
|
|
353
349
|
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
-
import { copyScratchKey, encodeSingleScalarKey, equalsScratch, hashScratch, } from "./group-index.js";
|
|
2
|
+
import { copyScratchKey, encodeSingleScalarKey, equalsScratch, hashScratch, safeDouble, safeProduct, } from "./group-index.js";
|
|
3
3
|
const INITIAL_ENTRY_CAPACITY = 4;
|
|
4
4
|
const INITIAL_BUCKET_CAPACITY = 8;
|
|
5
5
|
const INITIAL_KEY_CAPACITY = 32;
|
|
@@ -201,17 +201,3 @@ function encodeJoinKey(value) {
|
|
|
201
201
|
}
|
|
202
202
|
throw new TypeError("Join keys must be SQL scalar values");
|
|
203
203
|
}
|
|
204
|
-
function safeDouble(value, label) {
|
|
205
|
-
const doubled = value * 2;
|
|
206
|
-
if (!Number.isSafeInteger(doubled)) {
|
|
207
|
-
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
208
|
-
}
|
|
209
|
-
return doubled;
|
|
210
|
-
}
|
|
211
|
-
function safeProduct(left, right, label) {
|
|
212
|
-
const product = left * right;
|
|
213
|
-
if (!Number.isSafeInteger(product)) {
|
|
214
|
-
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
215
|
-
}
|
|
216
|
-
return product;
|
|
217
|
-
}
|
|
@@ -1,39 +1,5 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
-
|
|
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 (!sameValue(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 (!sameValue(leftRecord[key], rightRecord[key]))
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
2
|
+
import { sameLiveValue } from "./live-equal.js";
|
|
37
3
|
function keyToken(value, name) {
|
|
38
4
|
if (typeof value === "string")
|
|
39
5
|
return `s:${String(value.length)}:${value}`;
|
|
@@ -84,7 +50,7 @@ function diffRows(previousRows, rows, key) {
|
|
|
84
50
|
changes.push({ type: "insert", row: next.row, index: next.index });
|
|
85
51
|
continue;
|
|
86
52
|
}
|
|
87
|
-
if (!
|
|
53
|
+
if (!sameLiveValue(old.row, next.row)) {
|
|
88
54
|
changes.push({ type: "update", row: next.row, previous: old.row, index: next.index });
|
|
89
55
|
}
|
|
90
56
|
if (old.index !== next.index) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural equality over live result values: dates by instant, arrays by element, objects by
|
|
3
|
+
* own keys in insertion order. Shared by the typed live store's exact suppression and the keyed
|
|
4
|
+
* live window's diffing; internal on purpose — live-api re-exports its modules wholesale, and
|
|
5
|
+
* this helper is not part of the live API.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sameLiveValue(left: unknown, right: unknown): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
/**
|
|
3
|
+
* Structural equality over live result values: dates by instant, arrays by element, objects by
|
|
4
|
+
* own keys in insertion order. Shared by the typed live store's exact suppression and the keyed
|
|
5
|
+
* live window's diffing; internal on purpose — live-api re-exports its modules wholesale, and
|
|
6
|
+
* this helper is not part of the live API.
|
|
7
|
+
*/
|
|
8
|
+
export function sameLiveValue(left, right) {
|
|
9
|
+
if (Object.is(left, right))
|
|
10
|
+
return true;
|
|
11
|
+
if (left instanceof Date || right instanceof Date) {
|
|
12
|
+
return (left instanceof Date &&
|
|
13
|
+
right instanceof Date &&
|
|
14
|
+
Object.is(dateMilliseconds(left), dateMilliseconds(right)));
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
17
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
|
18
|
+
return false;
|
|
19
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
20
|
+
if (!sameLiveValue(left[index], right[index]))
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const leftRecord = left;
|
|
29
|
+
const rightRecord = right;
|
|
30
|
+
const leftKeys = Object.keys(leftRecord);
|
|
31
|
+
const rightKeys = Object.keys(rightRecord);
|
|
32
|
+
if (leftKeys.length !== rightKeys.length)
|
|
33
|
+
return false;
|
|
34
|
+
for (let index = 0; index < leftKeys.length; index += 1) {
|
|
35
|
+
const key = leftKeys[index];
|
|
36
|
+
if (key === undefined || key !== rightKeys[index])
|
|
37
|
+
return false;
|
|
38
|
+
if (!sameLiveValue(leftRecord[key], rightRecord[key]))
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
package/dist/engine/optimizer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { crossJoinPlan } from "../plan/model.js";
|
|
2
3
|
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";
|
|
4
|
+
import { concatenatedSqlValue, isSqlDomainValue } from "./sql-domains.js";
|
|
4
5
|
/**
|
|
5
6
|
* Deterministic plan-to-plan rewrites over the shared compiled representation. Every rule
|
|
6
7
|
* preserves result semantics exactly; rules that cannot prove safety leave the plan unchanged.
|
|
@@ -1776,20 +1777,7 @@ function decorrelateExistsWithProbes(block, exists, scope, nextAlias) {
|
|
|
1776
1777
|
reference: `${probesAlias}.${correlationProbeAlias(index)}`,
|
|
1777
1778
|
}));
|
|
1778
1779
|
replacePlanReferences(inner, new Map(references.map((reference, index) => [reference, probeReferences[index] ?? null])));
|
|
1779
|
-
inner.joins.push({
|
|
1780
|
-
table: probesAlias,
|
|
1781
|
-
alias: probesAlias,
|
|
1782
|
-
derived: probes,
|
|
1783
|
-
kind: "inner",
|
|
1784
|
-
left: { kind: "literal", value: null },
|
|
1785
|
-
right: { kind: "literal", value: null },
|
|
1786
|
-
on: {
|
|
1787
|
-
kind: "condition",
|
|
1788
|
-
operator: "=",
|
|
1789
|
-
left: { kind: "literal", value: 1 },
|
|
1790
|
-
right: { kind: "literal", value: 1 },
|
|
1791
|
-
},
|
|
1792
|
-
});
|
|
1780
|
+
inner.joins.push(crossJoinPlan({ table: probesAlias, alias: probesAlias, derived: probes }));
|
|
1793
1781
|
const flagsAlias = nextAlias();
|
|
1794
1782
|
const flags = {
|
|
1795
1783
|
sql: "(probe-lifted correlated exists flags)",
|
|
@@ -2244,7 +2232,14 @@ function foldBinary(operator, leftValue, rightValue) {
|
|
|
2244
2232
|
return null;
|
|
2245
2233
|
if (operator === "||") {
|
|
2246
2234
|
if (typeof leftValue === "string" && typeof rightValue === "string") {
|
|
2247
|
-
|
|
2235
|
+
// Same helper the executors use, so folding cannot concatenate internal domain
|
|
2236
|
+
// encodings. A refused shape stays unfolded and raises the same error at execution.
|
|
2237
|
+
try {
|
|
2238
|
+
return concatenatedSqlValue(leftValue, rightValue);
|
|
2239
|
+
}
|
|
2240
|
+
catch {
|
|
2241
|
+
return undefined;
|
|
2242
|
+
}
|
|
2248
2243
|
}
|
|
2249
2244
|
return undefined;
|
|
2250
2245
|
}
|
|
@@ -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];
|