@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 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, upserts, `RETURNING`, triggers, exact decimals,
12
- nested JSON/JSONB, stored generated columns, zoneless DATE, arrays, enums, sequences, and savepoints.
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.
@@ -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