@minnowdb/core 0.7.8 → 0.8.0
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/client.d.ts +5 -2
- package/dist/engine/client.js +14 -4
- package/dist/engine/database.js +632 -39
- package/dist/engine/errors.d.ts +2 -2
- package/dist/engine/errors.js +1 -1
- package/dist/engine/keyed-live.js +55 -18
- package/dist/engine/live-aggregate.d.ts +12 -0
- package/dist/engine/live-aggregate.js +226 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +107 -8
- package/dist/engine/live.js +353 -187
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +2 -13
- package/dist/engine/query-generations.d.ts +8 -0
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.d.ts +3 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +6 -11
- package/dist/engine/query.js +294 -428
- package/dist/engine/sql-domains.d.ts +4 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.d.ts +12 -1
- package/dist/engine/typed-live.js +110 -25
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +386 -0
- package/dist/engine/worker-server.js +12 -5
- package/dist/plan/model.d.ts +1 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
package/dist/engine/optimizer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeQueryIdentity } from "./query-identity.js";
|
|
1
2
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
2
3
|
import { crossJoinPlan } from "../plan/model.js";
|
|
3
4
|
import { blockHasRowWindow, blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, statementDatetimeNames, volatileScalarFunctionNames, transparentProjectionSource, dateTruncValue, integerQuotient } from "./query.js";
|
|
@@ -557,7 +558,7 @@ function propagateJoinKeyConstants(block) {
|
|
|
557
558
|
}
|
|
558
559
|
if (pairs.length === 0)
|
|
559
560
|
return;
|
|
560
|
-
const signature = (predicate) =>
|
|
561
|
+
const signature = (predicate) => encodeQueryIdentity(predicate);
|
|
561
562
|
const present = new Set(block.predicates.map(signature));
|
|
562
563
|
const implied = [];
|
|
563
564
|
const constantSide = (predicate) => {
|
|
@@ -785,9 +786,9 @@ function decorrelateBlock(block, nextAlias) {
|
|
|
785
786
|
if (!expressionHasCorrelatedSubquery(item.expression))
|
|
786
787
|
continue;
|
|
787
788
|
if (grouped) {
|
|
788
|
-
const groupedExpressions = new Set(block.groupBy.map((group) =>
|
|
789
|
+
const groupedExpressions = new Set(block.groupBy.map((group) => encodeQueryIdentity(group)));
|
|
789
790
|
const outerReferences = correlatedOuterReferences(item.expression);
|
|
790
|
-
if (containsAggregateCall(item.expression) || outerReferences.some((reference) => !groupedExpressions.has(
|
|
791
|
+
if (containsAggregateCall(item.expression) || outerReferences.some((reference) => !groupedExpressions.has(encodeQueryIdentity({ kind: "column", reference })))) {
|
|
791
792
|
throw new TypeError("A grouped correlated select-list subquery must reference only GROUP BY columns and cannot be nested inside an aggregate");
|
|
792
793
|
}
|
|
793
794
|
}
|
|
@@ -2428,7 +2429,11 @@ function foldExpression(expression) {
|
|
|
2428
2429
|
if (folded === null || typeof folded === "string" || typeof folded === "boolean" || folded instanceof Date || typeof folded === "number" && Number.isFinite(folded)) {
|
|
2429
2430
|
const target = foldedArguments[1];
|
|
2430
2431
|
const targetWord = expression.name === "CAST" && target?.kind === "literal" && typeof target.value === "string" ? target.value : void 0;
|
|
2431
|
-
const
|
|
2432
|
+
const first = literalValues[0];
|
|
2433
|
+
const sqlDomain = expression.name === "ARRAY" ? {
|
|
2434
|
+
kind: "array",
|
|
2435
|
+
element: typeof first === "number" ? "DOUBLE" : typeof first === "boolean" ? "BOOLEAN" : first instanceof Date ? "TIMESTAMP" : "TEXT"
|
|
2436
|
+
} : targetWord?.startsWith("numeric:") === true ? (() => {
|
|
2432
2437
|
const [, precisionWord = "", scaleWord = ""] = targetWord.split(":");
|
|
2433
2438
|
return {
|
|
2434
2439
|
kind: "numeric",
|
|
@@ -2565,8 +2570,8 @@ function rewriteForInner(expression, source, derived, singleSource) {
|
|
|
2565
2570
|
if (containsAggregateOrWindow(item.expression))
|
|
2566
2571
|
return void 0;
|
|
2567
2572
|
if (derived.groupBy.length > 0) {
|
|
2568
|
-
const signature =
|
|
2569
|
-
if (!derived.groupBy.some((group) =>
|
|
2573
|
+
const signature = encodeQueryIdentity(item.expression);
|
|
2574
|
+
if (!derived.groupBy.some((group) => encodeQueryIdentity(group) === signature)) {
|
|
2570
2575
|
return void 0;
|
|
2571
2576
|
}
|
|
2572
2577
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeQueryIdentity } from "./query-identity.js";
|
|
1
2
|
import { copyDate, dateMilliseconds } from "../date-value.js";
|
|
2
3
|
import { estimateValuesBytes } from "./byte-estimates.js";
|
|
3
4
|
import { copyQueryResultExternalization } from "./query.js";
|
|
@@ -7,19 +8,7 @@ function queryResultMemoKey(sql, params) {
|
|
|
7
8
|
return JSON.stringify([sql, params.map(encodeParameter)]);
|
|
8
9
|
}
|
|
9
10
|
function planMemoKey(plan) {
|
|
10
|
-
return
|
|
11
|
-
if (value instanceof Date)
|
|
12
|
-
return { $date: dateMilliseconds(value) };
|
|
13
|
-
if (typeof value === "bigint")
|
|
14
|
-
return { $bigint: value.toString() };
|
|
15
|
-
if (typeof value === "number") {
|
|
16
|
-
if (Object.is(value, -0))
|
|
17
|
-
return { $number: "-0" };
|
|
18
|
-
if (!Number.isFinite(value))
|
|
19
|
-
return { $number: String(value) };
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
});
|
|
11
|
+
return encodeQueryIdentity(plan);
|
|
23
12
|
}
|
|
24
13
|
function copyQueryResult(result) {
|
|
25
14
|
const columns = result.columns;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Manifest, StoragePage } from "../storage/types.js";
|
|
2
|
+
/** Bounded, process-local table generations proved by a contiguous durable commit history. */
|
|
3
|
+
export declare class QueryGenerations {
|
|
4
|
+
#private;
|
|
5
|
+
readonly page: (after: number | null, limit: number) => Promise<StoragePage<Manifest, number>>;
|
|
6
|
+
constructor(page: (after: number | null, limit: number) => Promise<StoragePage<Manifest, number>>);
|
|
7
|
+
key(tableIds: readonly string[], version: number | null): Promise<string>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
class QueryGenerations {
|
|
2
|
+
page;
|
|
3
|
+
#version;
|
|
4
|
+
#base = null;
|
|
5
|
+
#tables = /* @__PURE__ */ new Map();
|
|
6
|
+
#pending = Promise.resolve();
|
|
7
|
+
constructor(page) {
|
|
8
|
+
this.page = page;
|
|
9
|
+
}
|
|
10
|
+
key(tableIds, version) {
|
|
11
|
+
const task = this.#pending.then(async () => {
|
|
12
|
+
if (this.#version !== void 0 && (version ?? -1) < (this.#version ?? -1))
|
|
13
|
+
return JSON.stringify(["snapshot", version, tableIds]);
|
|
14
|
+
if (this.#version === void 0)
|
|
15
|
+
this.#reset(version);
|
|
16
|
+
if (version !== this.#version) {
|
|
17
|
+
const tables = new Map(this.#tables);
|
|
18
|
+
let cursor = this.#version ?? null;
|
|
19
|
+
let complete = false;
|
|
20
|
+
for (let pageIndex = 0; pageIndex < 8 && !complete; pageIndex += 1) {
|
|
21
|
+
const page = await this.page(cursor, 64);
|
|
22
|
+
for (const manifest of page.records) {
|
|
23
|
+
if (manifest.previousVersion !== cursor || manifest.version > (version ?? -1))
|
|
24
|
+
break;
|
|
25
|
+
for (const id of manifest.changedTableIds)
|
|
26
|
+
tables.set(id, manifest.version);
|
|
27
|
+
cursor = manifest.version;
|
|
28
|
+
if (cursor === version) {
|
|
29
|
+
complete = true;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (page.nextCursor === null || tables.size > 4096)
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (!complete || tables.size > 4096)
|
|
37
|
+
this.#reset(version);
|
|
38
|
+
else {
|
|
39
|
+
this.#tables.clear();
|
|
40
|
+
for (const [id, generation] of tables)
|
|
41
|
+
this.#tables.set(id, generation);
|
|
42
|
+
this.#version = version;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return JSON.stringify([
|
|
46
|
+
this.#base,
|
|
47
|
+
tableIds.map((id) => [id, this.#tables.get(id) ?? this.#base])
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
this.#pending = task.then(() => void 0, () => void 0);
|
|
51
|
+
return task;
|
|
52
|
+
}
|
|
53
|
+
#reset(version) {
|
|
54
|
+
this.#version = version;
|
|
55
|
+
this.#base = version;
|
|
56
|
+
this.#tables.clear();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
QueryGenerations
|
|
61
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Type-tagged, length-delimited structural identity. Unlike JSON, it preserves Date vs string,
|
|
2
|
+
* -0, non-finite numbers, and undefined fields, so deduplication cannot merge distinct plans. */
|
|
3
|
+
export declare function encodeQueryIdentity(value: unknown, ancestors?: Set<object>): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
function encodeQueryIdentity(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
3
|
+
if (value === null)
|
|
4
|
+
return "z";
|
|
5
|
+
if (typeof value === "undefined")
|
|
6
|
+
return "u";
|
|
7
|
+
if (typeof value === "boolean")
|
|
8
|
+
return value ? "b1" : "b0";
|
|
9
|
+
if (typeof value === "number") {
|
|
10
|
+
if (Number.isNaN(value))
|
|
11
|
+
return "nNaN;";
|
|
12
|
+
if (Object.is(value, -0))
|
|
13
|
+
return "n-0;";
|
|
14
|
+
return `n${String(value)};`;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === "bigint")
|
|
17
|
+
return `i${String(value)};`;
|
|
18
|
+
if (typeof value === "string")
|
|
19
|
+
return `s${String(value.length)}:${value}`;
|
|
20
|
+
if (value instanceof Date)
|
|
21
|
+
return `d${String(dateMilliseconds(value))};`;
|
|
22
|
+
if (typeof value !== "object") {
|
|
23
|
+
throw new TypeError(`Unsupported query identity value: ${typeof value}`);
|
|
24
|
+
}
|
|
25
|
+
if (ancestors.has(value))
|
|
26
|
+
throw new TypeError("Query identity contains a cycle");
|
|
27
|
+
ancestors.add(value);
|
|
28
|
+
let encoded;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
encoded = `a${String(value.length)}[${Array.from(value, (item) => encodeQueryIdentity(item, ancestors)).join("")}]`;
|
|
31
|
+
} else {
|
|
32
|
+
const record = value;
|
|
33
|
+
const keys = Object.keys(record).sort();
|
|
34
|
+
encoded = `o${String(keys.length)}{${keys.map((key) => `${encodeQueryIdentity(key, ancestors)}${encodeQueryIdentity(record[key], ancestors)}`).join("")}}`;
|
|
35
|
+
}
|
|
36
|
+
ancestors.delete(value);
|
|
37
|
+
return encoded;
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
encodeQueryIdentity
|
|
41
|
+
};
|
package/dist/engine/query.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ColumnDefault, ColumnGenerated, SqlDomain } from "../storage/types
|
|
|
3
3
|
import type { AggregateName, ComparisonOperator, CompiledQuery, Expression, FtsStats, JoinPlan, Predicate, PredicateOperator, QueryResult, QueryRow, QueryValue, ScalarFunctionName, SelectItem, SelectTail, SetOperator, TableSource, WindowSpec } from "../plan/model.js";
|
|
4
4
|
export type { AggregateName, BinaryOperator, ComparisonOperator, CompiledQuery, Expression, JoinPlan, Predicate, PredicateOperator, QueryResult, QueryRow, QueryValue, RecursiveCte, ScalarFunctionName, SelectItem, SelectTail, SetOperator, TableSource, WindowFrame, WindowFrameBound, WindowFrameExclusion, WindowFunctionName, WindowSpec, } from "../plan/model.js";
|
|
5
5
|
import { QueryMemoryContext, type QueryMemoryUsage } from "./memory.js";
|
|
6
|
+
export { applyWindowFunctions } from "./windows.js";
|
|
6
7
|
import { type ColumnarTable, type AsyncQueryExecutionOptions, type QueryBatchExecutionOptions } from "./vector.js";
|
|
7
8
|
/** Domain metadata for an execution path that has no catalog-backed type information. */
|
|
8
9
|
export declare function unknownColumnDomains(columns: readonly string[]): null[];
|
|
@@ -451,6 +452,8 @@ export declare function bindStatementParameters(statement: CompiledStatement, pa
|
|
|
451
452
|
export type SqlColumnType = "boolean" | "number" | "string" | "datetime";
|
|
452
453
|
export interface SqlColumnSchema {
|
|
453
454
|
name: string;
|
|
455
|
+
/** An all-NULL expression awaiting a surrounding common SQL type. */
|
|
456
|
+
unknown?: true;
|
|
454
457
|
type: SqlColumnType;
|
|
455
458
|
/** Exact SQL whole-number domain, physically stored as a safe JavaScript number. */
|
|
456
459
|
integer?: true;
|
|
@@ -459,8 +462,8 @@ export interface SqlColumnSchema {
|
|
|
459
462
|
}
|
|
460
463
|
/**
|
|
461
464
|
* Infers the typed output schema of one select block from typed source schemas. A column whose
|
|
462
|
-
* type
|
|
463
|
-
*
|
|
465
|
+
* type is still unknown (a bare NULL literal) carries that fact until a surrounding UNION or
|
|
466
|
+
* expression provides context. Its all-null physical vector uses string storage meanwhile.
|
|
464
467
|
*/
|
|
465
468
|
export declare function inferBlockSchema(plan: CompiledQuery, schemas: ReadonlyMap<string, readonly SqlColumnSchema[]>): SqlColumnSchema[];
|
|
466
469
|
/** Best-effort logical domains for a public result without making execution a new typecheck. */
|
|
@@ -631,17 +634,9 @@ export declare function expandFtsColumns(plan: CompiledQuery, searchableColumnsF
|
|
|
631
634
|
* AVG pass through untouched, and the input is often the compile cache's own object.
|
|
632
635
|
*/
|
|
633
636
|
export declare function annotateAvgArgumentScales(plan: CompiledQuery, schemas: ReadonlyMap<string, readonly SqlColumnSchema[]>): CompiledQuery;
|
|
634
|
-
/**
|
|
635
|
-
* Appends window-function columns to an executed inner-block result. Rows sort stably by the
|
|
636
|
-
* hidden partition and ordering aliases with the same comparison semantics as ORDER BY;
|
|
637
|
-
* ROW_NUMBER numbers rows per partition, RANK shares ranks across ordering ties with gaps, and
|
|
638
|
-
* DENSE_RANK shares without gaps. Without OVER ordering every partition row is a peer.
|
|
639
|
-
*/
|
|
640
|
-
export declare function applyWindowFunctions(result: QueryResult, windows: readonly WindowSpec[], options?: {
|
|
641
|
-
copyRows?: boolean;
|
|
642
|
-
}): QueryResult;
|
|
643
637
|
/** Correctness reference retained while the vector executor matures. */
|
|
644
638
|
export declare function executeRowQuery(plan: CompiledQuery, tables: ReadonlyMap<string, DatabaseRow[]>): QueryResult;
|
|
639
|
+
export declare function executeRowQueryInternal(plan: CompiledQuery, tables: ReadonlyMap<string, DatabaseRow[]>, memory?: QueryMemoryContext): QueryResult;
|
|
645
640
|
/** One ORDER BY resolution source: an alias and the columns a wildcard select exposes from it. */
|
|
646
641
|
interface OrderSourceShape {
|
|
647
642
|
readonly alias: string;
|