@minnowdb/core 0.5.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.
Files changed (42) hide show
  1. package/README.md +3 -2
  2. package/dist/engine/cancellation.d.ts +2 -0
  3. package/dist/engine/cancellation.js +4 -0
  4. package/dist/engine/catalog.d.ts +3 -1
  5. package/dist/engine/catalog.js +1 -0
  6. package/dist/engine/client.d.ts +32 -4
  7. package/dist/engine/client.js +82 -15
  8. package/dist/engine/database.d.ts +23 -14
  9. package/dist/engine/database.js +528 -79
  10. package/dist/engine/defaults.js +11 -0
  11. package/dist/engine/errors.d.ts +13 -0
  12. package/dist/engine/errors.js +22 -0
  13. package/dist/engine/fts.d.ts +2 -15
  14. package/dist/engine/live.d.ts +1 -7
  15. package/dist/engine/live.js +2 -12
  16. package/dist/engine/optimizer.d.ts +7 -0
  17. package/dist/engine/optimizer.js +1349 -76
  18. package/dist/engine/query.d.ts +11 -278
  19. package/dist/engine/query.js +178 -49
  20. package/dist/engine/schema-wire.d.ts +7 -1
  21. package/dist/engine/schema-wire.js +4 -0
  22. package/dist/engine/schema.d.ts +67 -33
  23. package/dist/engine/schema.js +138 -7
  24. package/dist/engine/sql-domains.d.ts +8 -0
  25. package/dist/engine/sql-domains.js +25 -0
  26. package/dist/engine/sql-json.js +22 -3
  27. package/dist/engine/vector.d.ts +2 -2
  28. package/dist/engine/vector.js +369 -43
  29. package/dist/engine/worker-host.js +119 -44
  30. package/dist/plan/index.d.ts +5 -4
  31. package/dist/plan/index.js +3 -3
  32. package/dist/plan/model.d.ts +224 -0
  33. package/dist/plan/model.js +1 -0
  34. package/dist/storage/types.d.ts +7 -0
  35. package/dist/storage/types.js +16 -0
  36. package/dist/transactions/index.d.ts +5 -3
  37. package/dist/transactions/index.js +58 -8
  38. package/dist/worker-protocol/index.d.ts +6 -1
  39. package/dist/worker-protocol/index.js +5 -2
  40. package/package.json +1 -1
  41. package/postgres-feature-profile.json +5 -0
  42. package/sql-feature-matrix.json +89 -21
@@ -6,10 +6,10 @@ import { cachedQueryTerms, ftsBm25Row, FtsStatsAccumulator, ftsMatchTruth, rende
6
6
  import { QueryMemoryContext } from "./memory.js";
7
7
  import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
8
8
  import { stringArgument } from "./sql-semantics.js";
9
- import { jsonAtPath, jsonConstructor, jsonIsValid, jsonValueOf, parseJsonPath, } from "./sql-json.js";
9
+ import { jsonAtPath, jsonConstructor, jsonIsValid, parseJsonPath } from "./sql-json.js";
10
10
  import { optimizePlan } from "./optimizer.js";
11
11
  import { compareSqlValues as compareValues, compileLikePattern, compileSimilarPattern, encodeSqlEqualityValue, roundSqlNumber, } from "./sql-semantics.js";
12
- import { arrayDomainValue, boundedJsonText, collatedDomainValue, dateDomainValue, exactNumericBinary, exactNumericValue, externalSqlDomainValue, intervalDomainValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue, } from "./sql-domains.js";
12
+ import { arrayDomainValue, boundedJsonText, collatedDomainValue, dateDomainValue, exactNumericBinary, exactNumericValue, externalSqlDomainValue, intervalDomainValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, preservedJsonDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue, } from "./sql-domains.js";
13
13
  import { columnarTableFromRows, prepareVectorQuery, } from "./vector.js";
14
14
  /** Domain metadata for an execution path that has no catalog-backed type information. */
15
15
  export function unknownColumnDomains(columns) {
@@ -264,7 +264,7 @@ export function scalarFunctionValue(name, values) {
264
264
  }
265
265
  if (name === "JSON_ARRAY" || name === "JSON_OBJECT") {
266
266
  // These build from every argument, so a NULL first one is data, not an early exit.
267
- return jsonConstructor(name, values);
267
+ return preservedJsonDomainValue(jsonConstructor(name, values));
268
268
  }
269
269
  if (name === "ARRAY")
270
270
  return arrayDomainValue(values);
@@ -394,7 +394,7 @@ export function scalarFunctionValue(name, values) {
394
394
  if (!found.found || found.value === undefined)
395
395
  return null;
396
396
  // JSON_QUERY returns JSON text, so a selected string keeps its quotes.
397
- return JSON.stringify(found.value);
397
+ return preservedJsonDomainValue(JSON.stringify(found.value));
398
398
  }
399
399
  case "LPAD":
400
400
  case "RPAD": {
@@ -800,6 +800,7 @@ const aggregateNames = new Set([
800
800
  "MAX",
801
801
  "JSON_ARRAYAGG",
802
802
  "STRING_AGG",
803
+ "MINNOW_SINGLE_VALUE",
803
804
  ]);
804
805
  /** Set functions the parser builds from COUNT/SUM rather than from their own accumulator. */
805
806
  const statisticalAggregates = new Set([
@@ -1502,8 +1503,12 @@ export function containsParameter(expression) {
1502
1503
  return childExpressions(expression).some(containsParameter);
1503
1504
  }
1504
1505
  export function blockHasParameters(block) {
1505
- if (block.limitParameter !== undefined || block.offsetParameter !== undefined)
1506
+ if (block.limitParameter !== undefined ||
1507
+ block.offsetParameter !== undefined ||
1508
+ (block.limitValidationParameters?.length ?? 0) > 0 ||
1509
+ (block.offsetValidationParameters?.length ?? 0) > 0) {
1506
1510
  return true;
1511
+ }
1507
1512
  const expressions = [];
1508
1513
  forEachBlockExpression(block, (expression) => expressions.push(expression));
1509
1514
  return (expressions.some(containsParameter) ||
@@ -1608,6 +1613,14 @@ function bindBlock(block, values) {
1608
1613
  block.offset = validateOffset(numeric(values[block.offsetParameter] ?? null));
1609
1614
  delete block.offsetParameter;
1610
1615
  }
1616
+ for (const index of block.limitValidationParameters ?? []) {
1617
+ validateLimit(numeric(values[index] ?? null));
1618
+ }
1619
+ for (const index of block.offsetValidationParameters ?? []) {
1620
+ validateOffset(numeric(values[index] ?? null));
1621
+ }
1622
+ delete block.limitValidationParameters;
1623
+ delete block.offsetValidationParameters;
1611
1624
  for (const item of block.select)
1612
1625
  item.expression = bindExpression(item.expression, values);
1613
1626
  for (const predicate of [...block.predicates, ...block.having]) {
@@ -1765,7 +1778,10 @@ export function bindStatementParameters(statement, params) {
1765
1778
  */
1766
1779
  export function inferBlockSchema(plan, schemas) {
1767
1780
  const sources = [plan.base, ...plan.joins];
1768
- const multipleSources = sources.length > 1;
1781
+ const multipleSources = sources.filter((source) => {
1782
+ const schema = schemas.get(source.table);
1783
+ return schema === undefined || schema.some((column) => !column.name.startsWith("\0"));
1784
+ }).length > 1;
1769
1785
  const wildcardSchema = (source) => (schemas.get(source.table) ?? [])
1770
1786
  .filter((column) => !column.name.startsWith("\0"))
1771
1787
  .map((column) => ({
@@ -1889,6 +1905,7 @@ export function inferBlockSchema(plan, schemas) {
1889
1905
  expression.name === "AVG" ||
1890
1906
  expression.name === "MIN" ||
1891
1907
  expression.name === "MAX" ||
1908
+ expression.name === "MINNOW_SINGLE_VALUE" ||
1892
1909
  expression.name === "COALESCE" ||
1893
1910
  expression.name === "NULLIF" ||
1894
1911
  expression.name === "GREATEST" ||
@@ -3514,10 +3531,10 @@ function executeRowQueryInternal(plan, tables, memory) {
3514
3531
  // Only a wildcard select needs the source shapes: every other select resolves against its
3515
3532
  // own output aliases.
3516
3533
  const orderSources = plan.select[0]?.expression.kind === "wildcard"
3517
- ? [plan.base, ...plan.joins].map((source) => ({
3518
- alias: source.alias,
3519
- columns: rowTableColumnNames(tables.get(source.table) ?? []),
3520
- }))
3534
+ ? [plan.base, ...plan.joins].flatMap((source) => {
3535
+ const columns = rowTableColumnNames(tables.get(source.table) ?? []).filter((name) => !name.startsWith("\0"));
3536
+ return columns.length === 0 ? [] : [{ alias: source.alias, columns }];
3537
+ })
3521
3538
  : [];
3522
3539
  const sortColumns = plan.orderBy.map(({ expression, direction, nulls }) => ({
3523
3540
  outputName: orderOutputName(expression, plan.select, orderSources),
@@ -3700,11 +3717,10 @@ function isSqlJoinKey(value) {
3700
3717
  }
3701
3718
  function project(select, context, group) {
3702
3719
  if (select[0]?.expression.kind === "wildcard") {
3703
- const aliases = Object.keys(context);
3704
- return Object.fromEntries(aliases.flatMap((alias) => Object.entries(context[alias] ?? {}).map(([name, value]) => [
3705
- aliases.length === 1 ? name : `${alias}.${name}`,
3706
- value,
3707
- ])));
3720
+ const aliases = Object.keys(context).filter((alias) => Object.keys(context[alias] ?? {}).some((name) => !name.startsWith("\0")));
3721
+ return Object.fromEntries(aliases.flatMap((alias) => Object.entries(context[alias] ?? {})
3722
+ .filter(([name]) => !name.startsWith("\0"))
3723
+ .map(([name, value]) => [aliases.length === 1 ? name : `${alias}.${name}`, value])));
3708
3724
  }
3709
3725
  return Object.fromEntries(select.map((item) => [item.alias, asQueryValue(evaluate(item.expression, context, group))]));
3710
3726
  }
@@ -3788,6 +3804,13 @@ function evaluate(expression, context, group) {
3788
3804
  if (group === undefined)
3789
3805
  throw new TypeError(`${expression.name} requires grouped execution`);
3790
3806
  const argument = expression.arguments[0] ?? { kind: "wildcard" };
3807
+ if (expression.name === "MINNOW_SINGLE_VALUE") {
3808
+ if (group.length > 1) {
3809
+ throw new TypeError(`A scalar subquery returned ${String(group.length)} rows`);
3810
+ }
3811
+ const row = group[0];
3812
+ return row === undefined ? null : evaluate(argument, row);
3813
+ }
3791
3814
  if (expression.name === "STRING_AGG") {
3792
3815
  const delimiter = expression.arguments[1];
3793
3816
  if (delimiter === undefined)
@@ -3845,12 +3868,44 @@ function evaluate(expression, context, group) {
3845
3868
  String(externalSqlDomainValue(member.value)))
3846
3869
  .join(""));
3847
3870
  }
3871
+ if (expression.name === "JSON_ARRAYAGG") {
3872
+ let members = group.map((row) => ({
3873
+ value: argument.kind === "wildcard" ? 1 : evaluate(argument, row),
3874
+ order: (expression.aggregateOrderBy ?? []).map((item) => evaluate(item.expression, row)),
3875
+ }));
3876
+ if (expression.distinct === true) {
3877
+ const seen = new Set();
3878
+ members = members.filter(({ value }) => {
3879
+ const key = value instanceof Date ? ` d${String(dateMilliseconds(value))}` : value;
3880
+ if (seen.has(key))
3881
+ return false;
3882
+ seen.add(key);
3883
+ return true;
3884
+ });
3885
+ }
3886
+ if ((expression.aggregateOrderBy?.length ?? 0) > 0) {
3887
+ members.sort((left, right) => {
3888
+ for (const [index, order] of (expression.aggregateOrderBy ?? []).entries()) {
3889
+ const a = left.order[index];
3890
+ const b = right.order[index];
3891
+ const placed = nullOrder(a, b, order.nulls, order.direction);
3892
+ if (placed !== undefined && placed !== 0)
3893
+ return placed;
3894
+ const compared = compareValues(a, b);
3895
+ if (compared !== 0)
3896
+ return order.direction === "desc" ? -compared : compared;
3897
+ }
3898
+ return 0;
3899
+ });
3900
+ }
3901
+ return members.length === 0
3902
+ ? null
3903
+ : preservedJsonDomainValue(jsonConstructor("JSON_ARRAY", members.map(({ value }) => value)));
3904
+ }
3848
3905
  let values = argument.kind === "wildcard"
3849
3906
  ? group.map(() => 1)
3850
3907
  : group.map((row) => evaluate(argument, row));
3851
- if (expression.name !== "JSON_ARRAYAGG") {
3852
- values = values.filter((value) => value !== null && value !== undefined);
3853
- }
3908
+ values = values.filter((value) => value !== null && value !== undefined);
3854
3909
  if (expression.distinct === true) {
3855
3910
  const seen = new Set();
3856
3911
  values = values.filter((value) => {
@@ -3872,11 +3927,6 @@ function evaluate(expression, context, group) {
3872
3927
  const sum = sumNumericValues(values);
3873
3928
  return exactNumericBinary("/", sum, values.length) ?? numeric(sum) / values.length;
3874
3929
  })();
3875
- if (expression.name === "JSON_ARRAYAGG") {
3876
- return values.length === 0
3877
- ? null
3878
- : JSON.stringify(values.map((value) => jsonValueOf(value ?? null)));
3879
- }
3880
3930
  if (expression.name === "MIN")
3881
3931
  return values.reduce((best, value) => (best === undefined || compareValues(value, best) < 0 ? value : best), undefined);
3882
3932
  return values.reduce((best, value) => (best === undefined || compareValues(value, best) > 0 ? value : best), undefined);
@@ -4328,6 +4378,10 @@ export function hasAggregate(expression) {
4328
4378
  }
4329
4379
  return childExpressions(expression).some(hasAggregate);
4330
4380
  }
4381
+ /** One root aggregate call, using the same canonical set as parsing and execution. */
4382
+ export function isAggregateCall(expression) {
4383
+ return expression.kind === "call" && aggregateNames.has(expression.name);
4384
+ }
4331
4385
  function validateGrouping(plan) {
4332
4386
  const grouped = plan.groupBy.length > 0 || plan.select.some((item) => hasAggregate(item.expression));
4333
4387
  if (!grouped)
@@ -4861,6 +4915,7 @@ class Parser {
4861
4915
  const columnType = this.#columnType();
4862
4916
  let nullable = true;
4863
4917
  let defaultValue;
4918
+ let generatedValue;
4864
4919
  for (;;) {
4865
4920
  if (this.#isKeyword("DEFAULT")) {
4866
4921
  // PostgreSQL-compatible variable-free scalar expression, retained in the catalog.
@@ -4868,6 +4923,10 @@ class Parser {
4868
4923
  defaultValue = this.#columnDefault();
4869
4924
  continue;
4870
4925
  }
4926
+ if (this.#isKeyword("GENERATED")) {
4927
+ generatedValue = this.#generatedColumn();
4928
+ continue;
4929
+ }
4871
4930
  if (this.#isKeyword("CHECK")) {
4872
4931
  checks.push(this.#checkConstraint(`${table}_${name}_check`));
4873
4932
  continue;
@@ -4913,6 +4972,7 @@ class Parser {
4913
4972
  ...columnType,
4914
4973
  ...(nullable ? { nullable: true } : {}),
4915
4974
  ...(defaultValue === undefined ? {} : { defaultValue }),
4975
+ ...(generatedValue === undefined ? {} : { generatedValue }),
4916
4976
  });
4917
4977
  if (!this.#punctuation(","))
4918
4978
  break;
@@ -4938,6 +4998,23 @@ class Parser {
4938
4998
  }
4939
4999
  }
4940
5000
  }
5001
+ for (const column of columns) {
5002
+ if (column.defaultValue !== undefined && column.generatedValue !== undefined) {
5003
+ throw new TypeError(`Generated column ${column.name} cannot also have a DEFAULT`);
5004
+ }
5005
+ if (column.generatedValue === undefined)
5006
+ continue;
5007
+ for (const reference of expressionColumnNames(compileCheckExpression(column.generatedValue.sql, `generated ${table}.${column.name}`))) {
5008
+ const referencedName = reference.split(".").at(-1) ?? reference;
5009
+ const referenced = columns.find(({ name }) => name === referencedName);
5010
+ if (referenced === undefined) {
5011
+ throw new TypeError(`Generated column ${column.name} refers to a column this table has no: ${referencedName}`);
5012
+ }
5013
+ if (referenced === column || referenced.generatedValue !== undefined) {
5014
+ throw new TypeError(`Generated column ${column.name} cannot reference a generated column: ${referencedName}`);
5015
+ }
5016
+ }
5017
+ }
4941
5018
  // Preserve the released single-UNIQUE row-addressing behavior when no PRIMARY KEY was
4942
5019
  // declared. Additional UNIQUE constraints remain independently enforced secondary keys.
4943
5020
  const promotedUnique = primaryKey === undefined && uniqueConstraints[0]?.columns.length === 1
@@ -5060,6 +5137,23 @@ class Parser {
5060
5137
  throw new TypeError("DEFAULT requires an expression");
5061
5138
  return columnDefaultFor(expression, sql);
5062
5139
  }
5140
+ /** GENERATED [ALWAYS] AS (expression) STORED. */
5141
+ #generatedColumn() {
5142
+ this.#keyword("GENERATED");
5143
+ if (this.#isKeyword("ALWAYS"))
5144
+ this.#keyword("ALWAYS");
5145
+ this.#keyword("AS");
5146
+ const open = this.#peek();
5147
+ this.#expectPunctuation("(");
5148
+ const expression = this.#expression();
5149
+ const close = this.#peek();
5150
+ this.#expectPunctuation(")");
5151
+ this.#keyword("STORED");
5152
+ if (hasAggregate(expression) || containsWindow(expression) || containsParameter(expression)) {
5153
+ throw new TypeError("Generated columns take an immutable row expression");
5154
+ }
5155
+ return { kind: "stored", sql: this.text.slice(open.start + 1, close.start).trim() };
5156
+ }
5063
5157
  /** DROP VIEW [IF EXISTS] name (F031-16). */
5064
5158
  parseDropView() {
5065
5159
  this.#keyword("DROP");
@@ -5294,7 +5388,7 @@ class Parser {
5294
5388
  rows: [[]],
5295
5389
  defaultValues: true,
5296
5390
  ...this.#onConflictClause(table),
5297
- ...this.#returningClause(),
5391
+ ...this.#returningClause(table),
5298
5392
  };
5299
5393
  }
5300
5394
  if (this.#isKeyword("SELECT")) {
@@ -5311,7 +5405,7 @@ class Parser {
5311
5405
  columns,
5312
5406
  rows: [],
5313
5407
  query,
5314
- ...this.#returningClause(),
5408
+ ...this.#returningClause(table),
5315
5409
  };
5316
5410
  }
5317
5411
  this.#keyword("VALUES");
@@ -5338,7 +5432,7 @@ class Parser {
5338
5432
  columns,
5339
5433
  rows,
5340
5434
  ...this.#onConflictClause(table),
5341
- ...this.#returningClause(),
5435
+ ...this.#returningClause(table),
5342
5436
  };
5343
5437
  }
5344
5438
  #onConflictClause(table) {
@@ -5405,8 +5499,8 @@ class Parser {
5405
5499
  },
5406
5500
  };
5407
5501
  }
5408
- /** RETURNING * or RETURNING col, ... — the engine's runStatement implements the semantics. */
5409
- #returningClause() {
5502
+ /** RETURNING *, target.*, or [target.]col, ... — execution owns the row semantics. */
5503
+ #returningClause(table) {
5410
5504
  if (!this.#isKeyword("RETURNING"))
5411
5505
  return {};
5412
5506
  this.#keyword("RETURNING");
@@ -5416,7 +5510,23 @@ class Parser {
5416
5510
  }
5417
5511
  const columns = [];
5418
5512
  for (;;) {
5419
- columns.push(this.#identifier());
5513
+ const first = this.#identifier();
5514
+ if (this.#punctuation(".")) {
5515
+ if (first !== table) {
5516
+ throw new TypeError(`RETURNING qualifier must name the target table: ${table}`);
5517
+ }
5518
+ if (this.#peek().text === "*") {
5519
+ this.#index += 1;
5520
+ if (columns.length > 0 || this.#peek().text === ",") {
5521
+ throw new TypeError("RETURNING target.* must be the only returned item");
5522
+ }
5523
+ return { returning: "*" };
5524
+ }
5525
+ columns.push(this.#identifier());
5526
+ }
5527
+ else {
5528
+ columns.push(first);
5529
+ }
5420
5530
  if (!this.#punctuation(","))
5421
5531
  break;
5422
5532
  }
@@ -5567,14 +5677,14 @@ class Parser {
5567
5677
  throw new TypeError("UPDATE assignments must set each column once");
5568
5678
  }
5569
5679
  const predicates = this.#mutationPredicates();
5570
- return { kind: "update", table, assignments, predicates, ...this.#returningClause() };
5680
+ return { kind: "update", table, assignments, predicates, ...this.#returningClause(table) };
5571
5681
  }
5572
5682
  #deleteStatement() {
5573
5683
  this.#keyword("DELETE");
5574
5684
  this.#keyword("FROM");
5575
5685
  const table = this.#identifier();
5576
5686
  const predicates = this.#mutationPredicates();
5577
- return { kind: "delete", table, predicates, ...this.#returningClause() };
5687
+ return { kind: "delete", table, predicates, ...this.#returningClause(table) };
5578
5688
  }
5579
5689
  #mutationPredicates() {
5580
5690
  const predicates = [];
@@ -6749,10 +6859,18 @@ class Parser {
6749
6859
  const all = this.#isKeyword("ALL");
6750
6860
  this.#keyword(this.#isKeyword("ANY") ? "ANY" : all ? "ALL" : "SOME");
6751
6861
  this.#expectPunctuation("(");
6862
+ // Kysely's fn.any(subquery) emits PostgreSQL's valid ANY((SELECT ...)) spelling.
6863
+ let wrapped = 0;
6864
+ while (this.#punctuation("("))
6865
+ wrapped += 1;
6752
6866
  if (!this.#isKeyword("SELECT")) {
6753
6867
  throw new TypeError("ANY/ALL take a subquery");
6754
6868
  }
6755
6869
  const block = this.#selectBlock("(quantified subquery)");
6870
+ while (wrapped > 0) {
6871
+ this.#expectPunctuation(")");
6872
+ wrapped -= 1;
6873
+ }
6756
6874
  this.#expectPunctuation(")");
6757
6875
  return {
6758
6876
  kind: "condition",
@@ -7254,7 +7372,9 @@ class Parser {
7254
7372
  // canonical plan names. ANY_VALUE picks an implementation-dependent row of the group
7255
7373
  // (T626); MIN is one such choice and reuses its accumulator exactly.
7256
7374
  const name = (upper === "ANY_VALUE" ? "MIN" : (functionSpellings.get(upper) ?? upper));
7257
- if (name === "MINNOW_TUPLE_KEY" || name === "MINNOW_COLLATE") {
7375
+ if (name === "MINNOW_TUPLE_KEY" ||
7376
+ name === "MINNOW_COLLATE" ||
7377
+ name === "MINNOW_SINGLE_VALUE") {
7258
7378
  throw new TypeError(`Unsupported function: ${identifier}`);
7259
7379
  }
7260
7380
  if (!aggregateNames.has(name) && !scalarFunctionNames.has(name))
@@ -7285,6 +7405,11 @@ class Parser {
7285
7405
  if (this.#isKeyword("ORDER"))
7286
7406
  aggregateOrderBy = this.#orderByClause();
7287
7407
  }
7408
+ else if (name === "JSON_ARRAYAGG") {
7409
+ args.push(this.#expression());
7410
+ if (this.#isKeyword("ORDER"))
7411
+ aggregateOrderBy = this.#orderByClause();
7412
+ }
7288
7413
  else {
7289
7414
  args.push(...this.#expressionList());
7290
7415
  }
@@ -7737,8 +7862,8 @@ class Parser {
7737
7862
  /**
7738
7863
  * GROUPING SETS desugar: one grouped block per set, combined with UNION ALL. A grouped column
7739
7864
  * absent from a member's set projects as NULLIF(expr, expr) — always NULL, but carrying the
7740
- * expression's type through schema inference. The GROUPING() marker function is deliberately
7741
- * unsupported, so rollup NULLs and data NULLs are indistinguishable; the docs call this out.
7865
+ * expression's type through schema inference. GROUPING() becomes one constant bitmask per member
7866
+ * block, distinguishing columns aggregated away by the set from data NULLs.
7742
7867
  */
7743
7868
  function desugarGroupingSets(parts, nextSequence) {
7744
7869
  const { groupingSets, limit, offset, limitParameter, offsetParameter, ...blockParts } = parts;
@@ -8008,16 +8133,17 @@ export function assembleSelectBlock(parts, nextSequence) {
8008
8133
  export function expandDistinctWildcard(plan, columnsOf) {
8009
8134
  if (plan.distinctWildcard !== true)
8010
8135
  return plan;
8011
- const sources = [plan.base, ...plan.joins];
8012
- const multiple = sources.length > 1;
8013
- const select = sources.flatMap((source) => {
8014
- const columns = columnsOf(source.table);
8015
- if (columns === undefined || columns.length === 0) {
8136
+ const shaped = [plan.base, ...plan.joins].map((source) => ({
8137
+ source,
8138
+ columns: sourceWildcardColumns(source, columnsOf),
8139
+ }));
8140
+ const visible = shaped.filter(({ columns }) => (columns?.length ?? 0) > 0);
8141
+ const multiple = visible.length > 1;
8142
+ const select = visible.flatMap(({ source, columns }) => {
8143
+ if (columns === undefined) {
8016
8144
  throw new TypeError(`SELECT DISTINCT * requires known columns for: ${source.table}`);
8017
8145
  }
8018
- return columns
8019
- .filter((name) => !name.startsWith("\0"))
8020
- .map((name) => {
8146
+ return columns.map((name) => {
8021
8147
  const output = multiple ? `${source.alias}.${name}` : name;
8022
8148
  return { expression: { kind: "column", reference: output }, alias: output };
8023
8149
  });
@@ -8113,10 +8239,12 @@ export function withTiesPlan(plan) {
8113
8239
  return { plan, trim: (result) => result };
8114
8240
  if (plan.orderBy.length === 0)
8115
8241
  throw new TypeError("FETCH ... WITH TIES requires ORDER BY");
8116
- const sources = [plan.base, ...plan.joins].map((source) => ({
8117
- alias: source.alias,
8118
- columns: source.derived?.select.map((item) => item.alias) ?? [],
8119
- }));
8242
+ const sources = [plan.base, ...plan.joins].flatMap((source) => {
8243
+ const columns = source.derived?.select
8244
+ .map((item) => item.alias)
8245
+ .filter((name) => !name.startsWith("\0"));
8246
+ return columns?.length === 0 ? [] : [{ alias: source.alias, columns: columns ?? [] }];
8247
+ });
8120
8248
  // orderOutputName throws when a sort key has no output column, which is the same failure the
8121
8249
  // executors report; nothing here has to re-check it.
8122
8250
  const keys = plan.orderBy.map(({ expression }) => orderOutputName(expression, plan.select, sources));
@@ -8296,7 +8424,8 @@ export function expandQualifiedWildcards(plan, columnsOf) {
8296
8424
  const expandBlock = (block) => {
8297
8425
  forEachNestedBlock(block, expandBlock);
8298
8426
  const sources = [block.base, ...block.joins];
8299
- const multiple = sources.length > 1;
8427
+ const multiple = sources.filter((source) => (sourceWildcardColumns(source, columnsOf)?.length ?? 0) > 0)
8428
+ .length > 1;
8300
8429
  block.select = block.select.flatMap((item) => {
8301
8430
  if (item.expression.kind !== "wildcard" || item.expression.table === undefined)
8302
8431
  return [item];
@@ -8386,9 +8515,9 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
8386
8515
  throw new TypeError("Window functions are only allowed in the select list");
8387
8516
  }
8388
8517
  // A bare literal is almost always a SQL ordinal (ORDER BY 2); sorting by a constant would
8389
- // silently do nothing, so reject it the way the engine always has.
8518
+ // silently do nothing. Valid ordinals have already resolved; a remaining literal is invalid.
8390
8519
  if (order.expression.kind === "literal") {
8391
- throw new TypeError("ORDER BY ordinals are not supported; name the column or alias");
8520
+ throw new TypeError("ORDER BY position is outside the select list");
8392
8521
  }
8393
8522
  }
8394
8523
  const selectSignatures = new Map(parts.select.map((item) => [JSON.stringify(item.expression), item.alias]));
@@ -1,4 +1,4 @@
1
- import type { ColumnDefault, SqlDomain } from "../storage/types.js";
1
+ import type { ColumnDefault, ColumnGenerated, SqlDomain } from "../storage/types.js";
2
2
  import { type AnyTable, type MigrationStep, type ReferentialAction, type SchemaColumnType, type SchemaDefinition, type TableCheck, type TableForeignKey } from "./schema.js";
3
3
  /**
4
4
  * Structured-clone-safe mirror of the schema DSL. Column builders and table validators carry
@@ -12,6 +12,7 @@ export interface WireColumn {
12
12
  readonly integer?: true;
13
13
  readonly sqlDomain?: SqlDomain;
14
14
  readonly defaultSpec?: ColumnDefault;
15
+ readonly generatedSpec?: ColumnGenerated;
15
16
  readonly renamedFromName?: string;
16
17
  readonly reference?: {
17
18
  table: string;
@@ -84,6 +85,11 @@ export type WireMigrationStep = {
84
85
  tableName: string;
85
86
  columnName: string;
86
87
  defaultValue: ColumnDefault | null;
88
+ } | {
89
+ kind: "alter-generated";
90
+ tableName: string;
91
+ columnName: string;
92
+ generatedValue: ColumnGenerated | null;
87
93
  } | {
88
94
  kind: "alter-foreign-keys";
89
95
  tableName: string;
@@ -14,6 +14,9 @@ function serializeColumn(definition, frozen) {
14
14
  ? {}
15
15
  : { sqlDomain: structuredClone(definition.sqlDomain) }),
16
16
  ...(definition.defaultSpec === undefined ? {} : { defaultSpec: { ...definition.defaultSpec } }),
17
+ ...(definition.generatedSpec === undefined
18
+ ? {}
19
+ : { generatedSpec: { ...definition.generatedSpec } }),
17
20
  ...(definition.renamedFromName === undefined
18
21
  ? {}
19
22
  : { renamedFromName: definition.renamedFromName }),
@@ -95,6 +98,7 @@ function deserializeColumn(wire) {
95
98
  integer: wire.integer === true,
96
99
  ...(wire.sqlDomain === undefined ? {} : { sqlDomain: structuredClone(wire.sqlDomain) }),
97
100
  ...(wire.defaultSpec === undefined ? {} : { defaultSpec: wire.defaultSpec }),
101
+ ...(wire.generatedSpec === undefined ? {} : { generatedSpec: wire.generatedSpec }),
98
102
  ...(wire.renamedFromName === undefined ? {} : { renamedFromName: wire.renamedFromName }),
99
103
  ...(wire.reference === undefined
100
104
  ? {}