@minnowdb/core 0.7.2 → 0.7.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.
@@ -1,6 +1,6 @@
1
1
  import { dateIsoString, dateMilliseconds } from "../date-value.js";
2
2
  import { crossJoinPlan } from "../plan/model.js";
3
- import { blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, statementDatetimeNames, volatileScalarFunctionNames, transparentProjectionSource, dateTruncValue } from "./query.js";
3
+ 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";
4
4
  import { concatenatedSqlValue, isSqlDomainValue } from "./sql-domains.js";
5
5
  import { simpleScalarFunctions } from "./sql-functions.js";
6
6
  function optimizePlan(plan, options = {}) {
@@ -199,7 +199,7 @@ function decorrelateLateralSources(block) {
199
199
  const keyAliases = keys.map((_, index) => `\0lateral_key_${String(index + 1)}`);
200
200
  const grouped = inner.groupBy.length > 0;
201
201
  const globalAggregate = !grouped && inner.select.some((item) => containsAggregateCall(item.expression)) && inner.having.length === 0;
202
- const ranked = inner.limit !== void 0 || inner.limitParameter !== void 0 || inner.offset !== void 0 || inner.offsetParameter !== void 0;
202
+ const ranked = blockHasRowWindow(inner);
203
203
  const cross = join.on?.kind === "condition" && join.on.operator === "=" && join.on.left.kind === "literal" && join.on.left.value === 1 && join.on.right.kind === "literal" && join.on.right.value === 1 || join.on?.kind === "literal" && join.on.value === true;
204
204
  if (grouped || globalAggregate || ranked || inner.having.length > 0) {
205
205
  if (!keys.every((key) => key.operator === "=")) {
@@ -1409,7 +1409,7 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
1409
1409
  if (passthrough !== void 0)
1410
1410
  return passthrough;
1411
1411
  const item = inner.select[0];
1412
- if (inner.select.length !== 1 || item === void 0 || !isAggregateCall(item.expression) || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || inner.limit !== void 0 || inner.offset !== void 0 || !canExtractCorrelation(inner, scope, "scalar", true)) {
1412
+ if (inner.select.length !== 1 || item === void 0 || !isAggregateCall(item.expression) || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || blockHasRowWindow(inner) || !canExtractCorrelation(inner, scope, "scalar", true)) {
1413
1413
  return decorrelateGeneralScalar(block, inner, scope, nextAlias);
1414
1414
  }
1415
1415
  const previewKeys = extractCorrelation(structuredClone(inner), scope, "scalar", true);
@@ -1432,7 +1432,10 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
1432
1432
  })),
1433
1433
  { expression: item.expression, alias: correlationValueAlias }
1434
1434
  ],
1435
- predicates: inner.predicates,
1435
+ predicates: [
1436
+ ...inner.predicates,
1437
+ ...mirrorPredicatesThroughKeys(outerProbePredicates(block, keys.map((key) => key.outer), nextAlias.resolvedNames), keys)
1438
+ ],
1436
1439
  groupBy: keys.map((key) => key.inner),
1437
1440
  having: [],
1438
1441
  orderBy: []
@@ -1449,7 +1452,7 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
1449
1452
  }
1450
1453
  function scalarPassthrough(inner) {
1451
1454
  const item = inner.select[0];
1452
- if (inner.base.table !== DUAL_TABLE || inner.base.derived !== void 0 || inner.joins.length > 0 || inner.select.length !== 1 || item === void 0 || inner.predicates.length > 0 || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || inner.offset !== void 0 || inner.limit !== void 0 && inner.limit < 1) {
1455
+ if (inner.base.table !== DUAL_TABLE || inner.base.derived !== void 0 || inner.joins.length > 0 || inner.select.length !== 1 || item === void 0 || inner.predicates.length > 0 || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || inner.offset !== void 0 || inner.offsetParameter !== void 0 || inner.limitParameter !== void 0 || inner.limit !== void 0 && inner.limit < 1) {
1453
1456
  return void 0;
1454
1457
  }
1455
1458
  return structuredClone(item.expression);
@@ -1463,7 +1466,7 @@ function scalarShape(inner) {
1463
1466
  let expression = structuredClone(item.expression);
1464
1467
  for (; ; ) {
1465
1468
  const derived = rows.base.derived;
1466
- if (derived === void 0 || rows.joins.length > 0 || rows.predicates.length > 0 || rows.groupBy.length > 0 || rows.having.length > 0 || rows.orderBy.length > 0 || rows.limit !== void 0 || rows.limitParameter !== void 0 || rows.offset !== void 0 || rows.offsetParameter !== void 0 || rows.distinctWildcard === true) {
1469
+ if (derived === void 0 || rows.joins.length > 0 || rows.predicates.length > 0 || rows.groupBy.length > 0 || rows.having.length > 0 || rows.orderBy.length > 0 || blockHasRowWindow(rows) || rows.distinctWildcard === true) {
1467
1470
  break;
1468
1471
  }
1469
1472
  expression = inlineDerivedProjection(expression, rows.base.alias, derived);
@@ -1656,7 +1659,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1656
1659
  orderBy: []
1657
1660
  };
1658
1661
  let finalRows = matched;
1659
- if (rows.limit !== void 0 || rows.limitParameter !== void 0 || rows.offset !== void 0 || rows.offsetParameter !== void 0) {
1662
+ if (blockHasRowWindow(rows)) {
1660
1663
  const rankedAlias = nextAlias();
1661
1664
  const rankedTable = nextAlias();
1662
1665
  const ranked = {
@@ -2022,6 +2025,34 @@ function outerProbeBlock(block, keys, resolvedNames) {
2022
2025
  return outerProbeBlockForExpressions(block, keys.map((key) => key.outer), resolvedNames);
2023
2026
  }
2024
2027
  function outerProbeBlockForExpressions(block, outerExpressions, resolvedNames) {
2028
+ const prefix = outerProbePrefix(block, outerExpressions, resolvedNames);
2029
+ const expressions = outerExpressions.map((expression) => structuredClone(expression));
2030
+ return {
2031
+ sql: "(correlation probes)",
2032
+ base: structuredClone(block.base),
2033
+ joins: structuredClone(block.joins.slice(0, prefix.lastSource)),
2034
+ select: expressions.map((expression, index) => ({
2035
+ expression,
2036
+ alias: correlationProbeAlias(index)
2037
+ })),
2038
+ predicates: probePredicates(block, prefix.retained, prefix.retainsAll),
2039
+ groupBy: expressions,
2040
+ having: [],
2041
+ orderBy: []
2042
+ };
2043
+ }
2044
+ function outerProbePredicates(block, outerExpressions, resolvedNames) {
2045
+ const sources = /* @__PURE__ */ new Set([block.base.alias, ...block.joins.map((join) => join.alias)]);
2046
+ for (const expression of outerExpressions) {
2047
+ for (const alias of expressionAliases(expression)) {
2048
+ if (!sources.has(alias))
2049
+ return [];
2050
+ }
2051
+ }
2052
+ const prefix = outerProbePrefix(block, outerExpressions, resolvedNames);
2053
+ return probePredicates(block, prefix.retained, prefix.retainsAll);
2054
+ }
2055
+ function outerProbePrefix(block, outerExpressions, resolvedNames) {
2025
2056
  const sources = [block.base, ...block.joins];
2026
2057
  const sourceIndexes = outerExpressions.flatMap((expression) => {
2027
2058
  const aliases = [...expressionAliases(expression)];
@@ -2035,22 +2066,9 @@ function outerProbeBlockForExpressions(block, outerExpressions, resolvedNames) {
2035
2066
  });
2036
2067
  });
2037
2068
  const lastSource = Math.max(...sourceIndexes);
2038
- const expressions = outerExpressions.map((expression) => structuredClone(expression));
2039
2069
  const retained = new Set(sources.slice(0, lastSource + 1).map((source) => source.alias));
2040
2070
  const retainsAll = resolvedNames && lastSource === sources.length - 1;
2041
- return {
2042
- sql: "(correlation probes)",
2043
- base: structuredClone(block.base),
2044
- joins: structuredClone(block.joins.slice(0, lastSource)),
2045
- select: expressions.map((expression, index) => ({
2046
- expression,
2047
- alias: correlationProbeAlias(index)
2048
- })),
2049
- predicates: probePredicates(block, retained, retainsAll),
2050
- groupBy: expressions,
2051
- having: [],
2052
- orderBy: []
2053
- };
2071
+ return { lastSource, retained, retainsAll };
2054
2072
  }
2055
2073
  function probePredicates(block, retained, retainsAll) {
2056
2074
  return block.predicates.filter((predicate) => probeSafeExpression(predicate.left, retained, retainsAll) && probeSafeExpression(predicate.right, retained, retainsAll)).map((predicate) => structuredClone(predicate));
@@ -2387,14 +2405,15 @@ function foldExpression(expression) {
2387
2405
  const left = foldExpression(expression.left);
2388
2406
  const right = foldExpression(expression.right);
2389
2407
  if (left.kind === "literal" && right.kind === "literal") {
2390
- const folded = foldBinary(expression.operator, left.value, right.value);
2408
+ const folded = foldBinary(expression.operator, left.value, right.value, expression.integer === true);
2391
2409
  if (folded !== void 0) {
2392
2410
  const domain = left.sqlDomain?.kind === "numeric" || right.sqlDomain?.kind === "numeric" ? { kind: "numeric" } : void 0;
2393
2411
  return {
2394
2412
  kind: "literal",
2395
2413
  value: folded,
2396
2414
  ...isSqlDomainValue(folded) ? { internalSqlValue: true } : {},
2397
- ...domain === void 0 ? {} : { sqlDomain: domain }
2415
+ ...domain === void 0 ? {} : { sqlDomain: domain },
2416
+ ...left.decimal === true || right.decimal === true ? { decimal: true } : {}
2398
2417
  };
2399
2418
  }
2400
2419
  }
@@ -2466,7 +2485,7 @@ function foldExpression(expression) {
2466
2485
  }
2467
2486
  return expression;
2468
2487
  }
2469
- function foldBinary(operator, leftValue, rightValue) {
2488
+ function foldBinary(operator, leftValue, rightValue, integer = false) {
2470
2489
  if (leftValue === null || rightValue === null)
2471
2490
  return null;
2472
2491
  if (operator === "||") {
@@ -2487,6 +2506,9 @@ function foldBinary(operator, leftValue, rightValue) {
2487
2506
  const right = rightValue instanceof Date ? dateMilliseconds(rightValue) : rightValue;
2488
2507
  if ((operator === "/" || operator === "%") && right === 0)
2489
2508
  return null;
2509
+ if (operator === "/" && integer && typeof left === "number" && typeof right === "number") {
2510
+ return integerQuotient(left, right);
2511
+ }
2490
2512
  const result = operator === "+" ? left + right : operator === "-" ? left - right : operator === "*" ? left * right : operator === "%" ? left % right : left / right;
2491
2513
  return Number.isFinite(result) ? result : void 0;
2492
2514
  }
@@ -2508,7 +2530,7 @@ function pushPredicatesIntoDerived(block) {
2508
2530
  function pushdownTarget(predicate, sources, singleSource) {
2509
2531
  for (const source of sources) {
2510
2532
  const derived = source.derived;
2511
- if (derived === void 0 || derived.limit !== void 0)
2533
+ if (derived === void 0 || blockHasRowWindow(derived))
2512
2534
  continue;
2513
2535
  const left = rewriteForInner(predicate.left, source, derived, singleSource);
2514
2536
  if (left === void 0)
@@ -2740,7 +2762,7 @@ function referencedOutputAliases(block, source, singleSource) {
2740
2762
  }
2741
2763
  function combineDerivedLimit(block) {
2742
2764
  const derived = block.base.derived;
2743
- if (derived === void 0 || block.joins.length > 0 || block.predicates.length > 0 || block.groupBy.length > 0 || block.having.length > 0 || block.orderBy.length > 0 || block.limit === void 0 || block.offset !== void 0 || derived.offset !== void 0) {
2765
+ if (derived === void 0 || block.joins.length > 0 || block.predicates.length > 0 || block.groupBy.length > 0 || block.having.length > 0 || block.orderBy.length > 0 || block.limit === void 0 || block.offset !== void 0 || block.offsetParameter !== void 0 || derived.offset !== void 0 || derived.offsetParameter !== void 0 || derived.limitParameter !== void 0) {
2744
2766
  return;
2745
2767
  }
2746
2768
  derived.limit = Math.min(derived.limit ?? Number.MAX_SAFE_INTEGER, block.limit);
@@ -135,6 +135,13 @@ export type InsertValue = QueryValue | DefaultInsertValue | DeferredInsertExpres
135
135
  };
136
136
  export declare function isDefaultInsertValue(value: InsertValue): value is DefaultInsertValue;
137
137
  export declare function isDeferredInsertExpression(value: InsertValue): value is DeferredInsertExpression;
138
+ /** The FROM / USING sources of an UPDATE or DELETE, joined to the target as a cross product. */
139
+ export interface MutationSources {
140
+ base: TableSource;
141
+ joins: JoinPlan[];
142
+ /** ON conditions of parenthesized join groups, which filter the product like WHERE. */
143
+ predicates: Predicate[];
144
+ }
138
145
  export type CompiledStatement = {
139
146
  kind: "select";
140
147
  sql: string;
@@ -189,6 +196,12 @@ export type CompiledStatement = {
189
196
  operator: PredicateOperator;
190
197
  right: Expression;
191
198
  }>;
199
+ /**
200
+ * `UPDATE t SET … FROM sources WHERE …`: the extra sources the assignments and predicates
201
+ * may read, joined to the target as PostgreSQL joins them. A target row matched by several
202
+ * source rows is updated once, from the first match.
203
+ */
204
+ from?: MutationSources;
192
205
  returning?: string[] | "*";
193
206
  /** RETURNING items that are not plain columns, evaluated over the affected rows. */
194
207
  returningItems?: ReturningItem[];
@@ -202,6 +215,8 @@ export type CompiledStatement = {
202
215
  operator: PredicateOperator;
203
216
  right: Expression;
204
217
  }>;
218
+ /** `DELETE FROM t USING sources WHERE …`: the extra sources the predicates may read. */
219
+ from?: MutationSources;
205
220
  returning?: string[] | "*";
206
221
  /** RETURNING items that are not plain columns, evaluated over the affected rows. */
207
222
  returningItems?: ReturningItem[];
@@ -303,6 +318,19 @@ export type CompiledStatement = {
303
318
  view: string;
304
319
  ifExists?: boolean;
305
320
  parameterCount?: number;
321
+ }
322
+ /** A session setting, accepted and ignored: `SET name TO value`, `RESET name`. */
323
+ | {
324
+ kind: "set";
325
+ action: "set" | "reset";
326
+ name: string;
327
+ parameterCount?: number;
328
+ }
329
+ /** `SHOW name`: the engine's value for a PostgreSQL setting, as one row. */
330
+ | {
331
+ kind: "show";
332
+ name: string;
333
+ parameterCount?: number;
306
334
  } | {
307
335
  kind: "transaction";
308
336
  action: "begin" | "commit" | "rollback" | "savepoint" | "rollback-to" | "release";
@@ -487,6 +515,64 @@ export declare function mapChildExpressions(expression: Expression, map: (child:
487
515
  export declare function forEachBlockExpression(block: CompiledQuery, visit: (expression: Expression) => void): void;
488
516
  /** Visits every nested block of one block's sources (derived, union, windowed, recursive). */
489
517
  export declare function forEachNestedBlock(block: CompiledQuery, visit: (nested: CompiledQuery) => void): void;
518
+ /**
519
+ * Whether an expression is integer-typed the way PostgreSQL types it: an INTEGER, BIGINT, or
520
+ * SMALLINT column, an integer constant, COUNT, an integer CAST, SUM/MIN/MAX/ABS over an
521
+ * integer, MOD over integers, a CASE/COALESCE/NULLIF/GREATEST/LEAST whose every branch is an
522
+ * integer, or `+ - * / %` over two integers. A bound parameter takes the type of its integer
523
+ * partner, as PostgreSQL infers `qty / $1`. A column the caller cannot type is not an integer,
524
+ * so the compiler's static answer only ever widens once the catalog is known.
525
+ */
526
+ export declare function integerTypedExpression(expression: Expression, columnInteger?: (reference: string) => boolean, tableIntegerColumns?: (table: string) => IntegerColumns | undefined): boolean;
527
+ /** A table's integer columns, or `true` when every column may be one (the bind-time probe). */
528
+ export type IntegerColumns = ReadonlySet<string> | true;
529
+ /** PostgreSQL's integer `/`: truncation toward zero, NULL for a zero divisor. */
530
+ export declare function integerQuotient(left: number, right: number): number | null;
531
+ /**
532
+ * Marks every `/` in one expression whose operands are integers, subquery blocks included, so
533
+ * that every evaluator truncates it the way PostgreSQL does. Children are marked first: the
534
+ * parent's typing reads the children, and `(a / b) / c` is integer only once `a / b` is.
535
+ */
536
+ export declare function annotateIntegerDivision(expression: Expression, columnInteger?: (reference: string) => boolean, tableIntegerColumns?: (table: string) => IntegerColumns | undefined): void;
537
+ /**
538
+ * Marks integer division throughout a plan, nested blocks first. Without a catalog only
539
+ * constants, COUNT, and integer CASTs type as integers; with one, INTEGER columns do too, and
540
+ * a derived source's outputs follow from its own select list.
541
+ */
542
+ export declare function annotatePlanIntegerDivision(plan: CompiledQuery, tableIntegerColumns?: (table: string) => IntegerColumns | undefined): void;
543
+ /**
544
+ * Whether catalog binding could still mark a division in this plan: a `/` not yet marked whose
545
+ * operands would be integers if every column were one. Plans without such a node — the common
546
+ * case — need no clone at bind time.
547
+ */
548
+ export declare function planMayHaveIntegerDivision(plan: CompiledQuery): boolean;
549
+ /**
550
+ * PostgreSQL folds an unquoted identifier to lower case, so `SELECT ID FROM USERS` reads the
551
+ * table `users` and its column `id`. Minnow keeps the written spelling and resolves it exactly,
552
+ * which is what a catalog created through the typed API (where `Orders` is a legal name)
553
+ * relies on. This pass gives both: a table or column name that resolves exactly is left alone,
554
+ * and one that does not is read as the unique catalog name that matches it case-insensitively.
555
+ * Only plain tables and their columns are folded; derived sources expose their aliases as
556
+ * written. Returns the same plan when nothing needed folding, so cached plans stay untouched.
557
+ */
558
+ export declare function foldIdentifierCase(plan: CompiledQuery, tables: ReadonlyMap<string, readonly string[]>): CompiledQuery;
559
+ /**
560
+ * A table alias used as a value — `json_agg(agg)`, `to_json(obj)`, `row_to_json(u)` — is the
561
+ * row as a JSON object in PostgreSQL. The reference is expanded here, once the catalog is
562
+ * known, into JSON_OBJECT over the source's columns in declaration order, so the aggregate and
563
+ * constructor paths see a scalar document. Only an alias of the same block that names no column
564
+ * expands; a bare name that is also a column stays that column.
565
+ */
566
+ export declare function expandRowReferences(plan: CompiledQuery, tableColumns: (table: string) => readonly string[] | undefined): CompiledQuery;
567
+ /**
568
+ * PostgreSQL's functional-dependency rule (SQL:2003 T301): once a table's whole primary key is
569
+ * in GROUP BY, any other column of that table may appear ungrouped, because the key determines
570
+ * the row. Minnow's executors group by the listed expressions only, so such a column is added
571
+ * to GROUP BY here — the groups are unchanged, since the key already separates every row — and
572
+ * the containment check then accepts it. Only plain table sources with a declared key
573
+ * qualify; a derived table exposes no key. Returns the same plan when nothing needed adding.
574
+ */
575
+ export declare function extendGroupByWithKeyDependents(plan: CompiledQuery, keyColumns: (table: string) => readonly string[] | undefined, tableColumns?: (table: string) => readonly string[] | undefined): CompiledQuery;
490
576
  /**
491
577
  * Rewrites every expression slot of one block in place, the writing counterpart to
492
578
  * `forEachBlockExpression`. The mapper receives each root expression and returns its
@@ -650,6 +736,11 @@ interface SelectBlockParts {
650
736
  /** GROUP BY GROUPING SETS/ROLLUP/CUBE: the grouping lists to union; groupBy is then unused. */
651
737
  groupingSets?: Expression[][];
652
738
  }
739
+ /**
740
+ * The execution-time half of unifyGroupedReferences for a block over several sources: an
741
+ * unqualified name is the column of the one source that has it, which only the schemas know.
742
+ */
743
+ export declare function unifyPlanGroupedReferences(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
653
744
  export declare function assembleSelectBlock(parts: SelectBlockParts, nextSequence: () => number): CompiledQuery;
654
745
  /**
655
746
  * Turns every source carrying a column alias list into a derived projection that renames the
@@ -688,6 +779,13 @@ export declare function expandNaturalJoins(plan: CompiledQuery, columnsOf: (tabl
688
779
  export declare function expandSourceColumnAliases(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
689
780
  /** Wraps compound members into the set-operation source the executor folds left to right. */
690
781
  export declare function compoundSelectBlock(sql: string, blocks: CompiledQuery[], ops: SetOperator[], tail: SelectTail, nextSequence: () => number): CompiledQuery;
782
+ /**
783
+ * True when a block trims its rows with LIMIT, OFFSET, or a placeholder for either. A rewrite
784
+ * that moves a filter or a limit across such a block changes which rows the window sees, so every
785
+ * "does this block page its rows" guard goes through here: reading only the literal fields lets
786
+ * `LIMIT ?`, `OFFSET ?`, and a bare `OFFSET n` slip past and the predicate run before the window.
787
+ */
788
+ export declare function blockHasRowWindow(block: CompiledQuery): boolean;
691
789
  /**
692
790
  * Detects a pure projection wrapper over one derived block — the shape the ORDER-BY-expression
693
791
  * desugar emits: no joins, filters, grouping, ordering, or paging of its own, and every select