@minnowdb/core 0.6.0 → 0.6.1
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/README.md +3 -2
- package/dist/engine/database.js +12 -5
- package/dist/engine/optimizer.d.ts +7 -0
- package/dist/engine/optimizer.js +855 -84
- package/dist/engine/query.d.ts +6 -0
- package/dist/engine/query.js +101 -18
- package/dist/engine/vector.js +69 -5
- package/dist/plan/model.d.ts +7 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +5 -0
- package/sql-feature-matrix.json +26 -8
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@ npm install @minnowdb/core
|
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
- Direct SQL through `MinnowDatabase.query()` and `execute()`.
|
|
11
|
-
- Joins, CTEs, window functions, grouping sets,
|
|
12
|
-
|
|
11
|
+
- Joins, CTEs, window functions, grouping sets, nested correlated subqueries, subquery-backed
|
|
12
|
+
mutations, upserts, `RETURNING`, triggers, exact decimals, nested JSON/JSONB, stored generated
|
|
13
|
+
columns, zoneless DATE, arrays, enums, sequences, and savepoints.
|
|
13
14
|
- Compressed column storage, secondary indexes, full-text search, and snapshot reads.
|
|
14
15
|
- Atomic writes across tabs through IndexedDB or OPFS, strict durability by default, and explicit
|
|
15
16
|
origin-eviction persistence policy.
|
package/dist/engine/database.js
CHANGED
|
@@ -18,7 +18,7 @@ import { applyWindowFunctions, bindPlanParameters, bindStatementParameters, DUAL
|
|
|
18
18
|
import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBytes, RESULT_MEMO_MAX_BYTES, } from "./query-cache.js";
|
|
19
19
|
import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES, } from "./memory.js";
|
|
20
20
|
import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE, } from "./live.js";
|
|
21
|
-
import { chooseJoinOrder, renderPlan } from "./optimizer.js";
|
|
21
|
+
import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan, } from "./optimizer.js";
|
|
22
22
|
import { encodeSqlEqualityValue } from "./sql-semantics.js";
|
|
23
23
|
import { externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
|
|
24
24
|
import { toCatalog } from "./catalog.js";
|
|
@@ -3300,7 +3300,7 @@ export class MinnowDatabase {
|
|
|
3300
3300
|
// read has to ask the catalog. It asks by epoch — an O(1) probe the store already serves for
|
|
3301
3301
|
// result memoization — and only re-reads the view set when the catalog has actually moved.
|
|
3302
3302
|
// A database with no views therefore pays one probe, not a catalog scan per query.
|
|
3303
|
-
const { views, domains } = await this.#catalogFacts(probe);
|
|
3303
|
+
const { views, domains, columns: catalogColumns } = await this.#catalogFacts(probe);
|
|
3304
3304
|
let rewritten = plan.usesSequenceCalls === true ? await this.#resolveSequenceCalls(plan) : plan;
|
|
3305
3305
|
if (views.size > 0 && planReadsViews(plan, (name) => views.has(name))) {
|
|
3306
3306
|
const bodies = new Map();
|
|
@@ -3319,6 +3319,9 @@ export class MinnowDatabase {
|
|
|
3319
3319
|
if (domains.size > 0 && planReadsTable(rewritten, (name) => domains.has(name))) {
|
|
3320
3320
|
rewritten = normalizePlanDomainLiterals(rewritten, domains);
|
|
3321
3321
|
}
|
|
3322
|
+
const qualified = qualifyCorrelatedReferences(rewritten, catalogColumns);
|
|
3323
|
+
if (qualified !== rewritten)
|
|
3324
|
+
rewritten = optimizePlan(qualified);
|
|
3322
3325
|
if (!aliased && !natural)
|
|
3323
3326
|
return rewritten;
|
|
3324
3327
|
// These two need column *order*, which only the records carry; both are rare enough that
|
|
@@ -3431,7 +3434,9 @@ export class MinnowDatabase {
|
|
|
3431
3434
|
const views = new Map();
|
|
3432
3435
|
const childKeys = new Map();
|
|
3433
3436
|
const domains = new Map();
|
|
3437
|
+
const columns = new Map();
|
|
3434
3438
|
for (const table of await this.store.listTables()) {
|
|
3439
|
+
columns.set(table.name, table.columns.filter(({ hidden }) => hidden !== true).map(({ name }) => name));
|
|
3435
3440
|
const tableDomains = new Map(table.columns.flatMap((column) => column.sqlDomain === undefined ? [] : [[column.name, column.sqlDomain]]));
|
|
3436
3441
|
if (tableDomains.size > 0)
|
|
3437
3442
|
domains.set(table.name, tableDomains);
|
|
@@ -3445,7 +3450,7 @@ export class MinnowDatabase {
|
|
|
3445
3450
|
existing.push({ table, key });
|
|
3446
3451
|
}
|
|
3447
3452
|
}
|
|
3448
|
-
const facts = { views, childKeys, domains };
|
|
3453
|
+
const facts = { views, childKeys, domains, columns };
|
|
3449
3454
|
this.#catalogCache = { epoch, facts };
|
|
3450
3455
|
return facts;
|
|
3451
3456
|
}
|
|
@@ -3530,6 +3535,8 @@ export class MinnowDatabase {
|
|
|
3530
3535
|
async #queryCompiledFirstColumn(plan, probe) {
|
|
3531
3536
|
const options = this.#effectiveQueryOptions({ memoize: false });
|
|
3532
3537
|
plan = await this.#applyCatalogRewrites(plan, probe);
|
|
3538
|
+
if (!this.#canStreamPlanShape(plan, options))
|
|
3539
|
+
return undefined;
|
|
3533
3540
|
const values = [];
|
|
3534
3541
|
const streamed = await this.#queryStreamed(plan, options, undefined, probe, {
|
|
3535
3542
|
batchRows: 2_048,
|
|
@@ -7291,7 +7298,7 @@ export class MinnowDatabase {
|
|
|
7291
7298
|
}
|
|
7292
7299
|
}
|
|
7293
7300
|
}
|
|
7294
|
-
const plan = {
|
|
7301
|
+
const plan = optimizePlan({
|
|
7295
7302
|
sql: `(${statement.kind})`,
|
|
7296
7303
|
base: { table: table.name, alias: table.name },
|
|
7297
7304
|
joins: [],
|
|
@@ -7303,7 +7310,7 @@ export class MinnowDatabase {
|
|
|
7303
7310
|
groupBy: [],
|
|
7304
7311
|
having: [],
|
|
7305
7312
|
orderBy: [],
|
|
7306
|
-
};
|
|
7313
|
+
});
|
|
7307
7314
|
if (statement.kind === "delete" && returningColumns === undefined) {
|
|
7308
7315
|
const internalWriter = options.writer;
|
|
7309
7316
|
if (internalWriter.queryFirstColumn !== undefined) {
|
|
@@ -6,6 +6,13 @@ import { type CompiledQuery } from "./query.js";
|
|
|
6
6
|
* predicates containing subqueries stay self-contained when they move.
|
|
7
7
|
*/
|
|
8
8
|
export declare function optimizePlan(plan: CompiledQuery): CompiledQuery;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves an unqualified column in a nested block against the nearest enclosing query scope
|
|
11
|
+
* when that name does not exist in the nested block itself. Compilation has no catalog, so this
|
|
12
|
+
* SQL name-resolution step runs later for database-backed plans and returns the original plan
|
|
13
|
+
* unchanged when it finds nothing to qualify.
|
|
14
|
+
*/
|
|
15
|
+
export declare function qualifyCorrelatedReferences(plan: CompiledQuery, tableColumns: ReadonlyMap<string, readonly string[]>): CompiledQuery;
|
|
9
16
|
/**
|
|
10
17
|
* Cost-based build-side selection using prepared or catalog-derived row counts. A single inner
|
|
11
18
|
* equi-join probes from the base into an index over the joined table, so a joined input more than
|