@minnowdb/core 0.5.0 → 0.6.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.
Files changed (40) hide show
  1. package/README.md +1 -1
  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 +516 -74
  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.js +540 -38
  17. package/dist/engine/query.d.ts +5 -278
  18. package/dist/engine/query.js +78 -32
  19. package/dist/engine/schema-wire.d.ts +7 -1
  20. package/dist/engine/schema-wire.js +4 -0
  21. package/dist/engine/schema.d.ts +67 -33
  22. package/dist/engine/schema.js +138 -7
  23. package/dist/engine/sql-domains.d.ts +8 -0
  24. package/dist/engine/sql-domains.js +25 -0
  25. package/dist/engine/sql-json.js +22 -3
  26. package/dist/engine/vector.d.ts +2 -2
  27. package/dist/engine/vector.js +301 -39
  28. package/dist/engine/worker-host.js +119 -44
  29. package/dist/plan/index.d.ts +5 -4
  30. package/dist/plan/index.js +3 -3
  31. package/dist/plan/model.d.ts +218 -0
  32. package/dist/plan/model.js +1 -0
  33. package/dist/storage/types.d.ts +7 -0
  34. package/dist/storage/types.js +16 -0
  35. package/dist/transactions/index.d.ts +5 -3
  36. package/dist/transactions/index.js +58 -8
  37. package/dist/worker-protocol/index.d.ts +6 -1
  38. package/dist/worker-protocol/index.js +5 -2
  39. package/package.json +1 -1
  40. package/sql-feature-matrix.json +69 -19
@@ -1,16 +1,9 @@
1
1
  import type { DatabaseRow } from "./database.js";
2
- import type { ColumnDefault, SqlDomain } from "../storage/types.js";
3
- import { type FtsStats } from "./fts.js";
2
+ import type { ColumnDefault, ColumnGenerated, SqlDomain } from "../storage/types.js";
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
+ 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";
4
5
  import { QueryMemoryContext, type QueryMemoryUsage } from "./memory.js";
5
6
  import { type ColumnarTable, type AsyncQueryExecutionOptions, type QueryBatchExecutionOptions } from "./vector.js";
6
- export type QueryValue = boolean | number | string | Date | null;
7
- export type QueryRow = Record<string, QueryValue>;
8
- export interface QueryResult {
9
- columns: string[];
10
- /** Logical SQL domain for each output column, positionally aligned with `columns`. */
11
- columnDomains: Array<SqlDomain | null>;
12
- rows: QueryRow[];
13
- }
14
7
  /** Domain metadata for an execution path that has no catalog-backed type information. */
15
8
  export declare function unknownColumnDomains(columns: readonly string[]): null[];
16
9
  export interface PreparedQuery {
@@ -35,14 +28,6 @@ export interface QueryExecutionOptions {
35
28
  */
36
29
  readonly executionMemoryBudgetBytes?: number;
37
30
  }
38
- export type BinaryOperator = "+" | "-" | "*" | "/" | "%" | "||";
39
- export type ComparisonOperator = "=" | "!=" | "<>" | ">" | ">=" | "<" | "<=";
40
- export type AggregateName = "COUNT" | "SUM" | "AVG" | "MIN" | "MAX" | "JSON_ARRAYAGG" | "STRING_AGG";
41
- export type ScalarFunctionName = "ROUND" | "COALESCE" | "DATE_TRUNC" | "DATE_ADD" | "UPPER" | "LOWER" | "LENGTH" | "ABS" | "TRIM" | "LTRIM" | "RTRIM" | "SUBSTR" | "REPLACE" | "INSTR" | "NULLIF" | "GREATEST" | "LEAST" | "FLOOR" | "CEIL" | "MOD" | "POWER" | "SQRT" | "EXTRACT" | "CAST" | "OCTET_LENGTH" | "LPAD" | "RPAD" | "OVERLAY" | "CURRENT_DATE" | "CURRENT_TIMESTAMP" | "LOCALTIME" | "GROUPING" | "JSON_VALUE" | "JSON_QUERY" | "JSON_EXISTS" | "JSON_OBJECT" | "JSON_ARRAY" | "IS_JSON" | "ARRAY"
42
- /** Optimizer-only, prefix-free equality key for hashable multi-column decorrelation. */
43
- | "MINNOW_TUPLE_KEY"
44
- /** Parser-produced wrapper carrying one explicit collation through ordering/comparison. */
45
- | "MINNOW_COLLATE" | "NEXTVAL" | "CURRVAL" | "RANDOM" | "GEN_RANDOM_UUID";
46
31
  export declare const scalarFunctionNames: ReadonlySet<string>;
47
32
  /** Functions whose answer can change without any catalog or input-row change. */
48
33
  export declare const volatileScalarFunctionNames: ReadonlySet<string>;
@@ -78,269 +63,10 @@ export declare function intervalLiteral(text: string): {
78
63
  */
79
64
  export declare function dateAddValue(value: unknown, months: unknown, milliseconds: unknown): Date | string | null;
80
65
  export declare function dateTruncValue(unit: unknown, value: unknown): Date | null;
81
- export type Expression = {
82
- kind: "literal";
83
- value: QueryValue;
84
- internalSqlValue?: true;
85
- sqlDomain?: SqlDomain;
86
- }
87
- /** A `?` or `$n` placeholder; `index` is 0-based. Replaced by a literal at bind time. */
88
- | {
89
- kind: "parameter";
90
- index: number;
91
- } | {
92
- kind: "column";
93
- reference: string;
94
- }
95
- /**
96
- * `*`, or `alias.*` when `table` is set (E051-07). A qualified wildcard names one source and
97
- * may sit beside other select items; every executor entry expands it into that source's
98
- * columns, so past the entry only a bare wildcard — the whole-row projection — survives.
99
- */
100
- | {
101
- kind: "wildcard";
102
- table?: string;
103
- } | {
104
- kind: "binary";
105
- operator: BinaryOperator;
106
- left: Expression;
107
- right: Expression;
108
- } | {
109
- kind: "call";
110
- name: AggregateName | ScalarFunctionName;
111
- arguments: Expression[];
112
- distinct?: boolean;
113
- aggregateOrderBy?: Array<{
114
- expression: Expression;
115
- direction: "asc" | "desc";
116
- nulls?: "first" | "last";
117
- }>;
118
- } | {
119
- kind: "list";
120
- items: Expression[];
121
- } | {
122
- kind: "subquery";
123
- block: CompiledQuery;
124
- } | {
125
- kind: "condition";
126
- operator: PredicateOperator;
127
- left: Expression;
128
- right: Expression;
129
- /** LIKE/ILIKE escape character, from LIKE ... ESCAPE 'c'. */
130
- escape?: string;
131
- } | {
132
- kind: "logical";
133
- operator: "and" | "or";
134
- left: Expression;
135
- right: Expression;
136
- } | {
137
- kind: "not";
138
- operand: Expression;
139
- } | {
140
- kind: "exists";
141
- block: CompiledQuery;
142
- negated: boolean;
143
- } | {
144
- kind: "case";
145
- branches: Array<{
146
- when: Expression;
147
- then: Expression;
148
- }>;
149
- otherwise?: Expression;
150
- } | {
151
- kind: "window";
152
- name: WindowFunctionName;
153
- partitionBy: Expression[];
154
- orderBy: Array<{
155
- expression: Expression;
156
- direction: "asc" | "desc";
157
- nulls?: "first" | "last";
158
- }>;
159
- argument?: Expression;
160
- /** LAG/LEAD row distance; parsed as a literal non-negative integer, defaulting to 1. */
161
- offset?: number;
162
- /** LAG/LEAD default when the offset row falls outside the partition; NULL when absent. */
163
- fallback?: QueryValue;
164
- frame?: WindowFrame;
165
- } | {
166
- kind: "fts";
167
- op: "match" | "bm25";
168
- /**
169
- * Column references forming the document, or "*" for every searchable column of the
170
- * single scan source. "*" stays unexpanded through compilation (both front ends emit the
171
- * identical node — plan parity), and the engine expands it against the catalog at
172
- * prepare time via expandFtsColumns.
173
- */
174
- columns: Expression[] | "*";
175
- query: string;
176
- /** Placeholder used by parameterized SQL; replaced with `query` before execution. */
177
- queryParameter?: number;
178
- /**
179
- * Corpus statistics for BM25, annotated by the executor onto its cloned plan before
180
- * evaluation; never set by compilation.
181
- */
182
- stats?: FtsStats;
183
- };
184
- export type WindowFunctionName = "ROW_NUMBER" | "RANK" | "DENSE_RANK" | "PERCENT_RANK" | "CUME_DIST" | "NTILE" | "LAG" | "LEAD" | "FIRST_VALUE" | "LAST_VALUE" | "NTH_VALUE" | AggregateName;
185
- export interface WindowFrameBound {
186
- kind: "unbounded-preceding" | "preceding" | "current-row" | "following" | "unbounded-following";
187
- /** Row distance for preceding/following bounds (ROWS unit only). */
188
- offset?: number;
189
- }
190
- /** How a frame's rows are excluded around the current row (T612). */
191
- export type WindowFrameExclusion = "no-others" | "current-row" | "group" | "ties";
192
- /** An explicit frame clause; absent means the SQL default for the window's ordering. */
193
- export interface WindowFrame {
194
- unit: "rows" | "range" | "groups";
195
- start: WindowFrameBound;
196
- end: WindowFrameBound;
197
- /** Rows excluded around the current row; absent means EXCLUDE NO OTHERS. */
198
- exclude?: WindowFrameExclusion;
199
- }
200
- export interface WindowSpec {
201
- alias: string;
202
- name: WindowFunctionName;
203
- partitionAliases: string[];
204
- orderAliases: Array<{
205
- alias: string;
206
- direction: "asc" | "desc";
207
- nulls?: "first" | "last";
208
- }>;
209
- /** Hidden inner alias of an aggregate window's argument; absent for COUNT(*) and rankings. */
210
- argumentAlias?: string;
211
- /** LAG/LEAD row distance. */
212
- offset?: number;
213
- /** LAG/LEAD default when the offset row falls outside the partition. */
214
- fallback?: QueryValue;
215
- frame?: WindowFrame;
216
- }
217
66
  /** The output column type of one window: rankings and most aggregates count, MIN/MAX carry. */
218
67
  export declare function windowOutputType(window: WindowSpec, innerSchema: readonly SqlColumnSchema[]): SqlColumnType;
219
68
  /** Logical domain carried or produced by a window result, when its physical type is not enough. */
220
69
  export declare function windowOutputDomain(window: WindowSpec, innerSchema: readonly SqlColumnSchema[]): SqlDomain | undefined;
221
- export interface SelectItem {
222
- expression: Expression;
223
- alias: string;
224
- }
225
- export interface TableSource {
226
- table: string;
227
- alias: string;
228
- /** A parenthesized SELECT or expanded CTE body; `table` is then a unique synthetic name. */
229
- derived?: CompiledQuery;
230
- /** A top-level set operation; members combine positionally under the first member's schema. */
231
- union?: {
232
- blocks: CompiledQuery[];
233
- ops: SetOperator[];
234
- };
235
- recursive?: RecursiveCte;
236
- /** A window-function desugar: the inner block executes, then window columns append. */
237
- windowed?: {
238
- block: CompiledQuery;
239
- windows: WindowSpec[];
240
- };
241
- /**
242
- * `FROM t AS y(a, b)`: positional new names for a base table's columns (E051-09). Every
243
- * executor entry turns the source into a derived projection, so past that point the rename
244
- * is an ordinary select list and nothing else has to know about it.
245
- */
246
- columnAliases?: string[];
247
- /** Parser marker for a derived source allowed to reference sources to its left. */
248
- lateral?: true;
249
- }
250
- export interface JoinPlan extends TableSource {
251
- kind: "inner" | "left" | "semi" | "anti";
252
- left: Expression;
253
- right: Expression;
254
- /** General ON condition for non-equi or multi-key joins; left/right are inert placeholders. */
255
- on?: Expression;
256
- /** Parser marker: FULL OUTER JOIN. Assembly desugars it into a union of two left joins. */
257
- full?: boolean;
258
- /**
259
- * Parser marker: NATURAL JOIN. Every execution entry replaces it with the equality
260
- * conjunction over the columns this source shares with the ones before it, so no executor
261
- * ever sees the marker (F401-01).
262
- */
263
- natural?: boolean;
264
- }
265
- export type SetOperator = "union" | "union all" | "intersect" | "intersect all" | "except" | "except all";
266
- /**
267
- * A WITH RECURSIVE source: the base block seeds the working set, then the step block re-executes
268
- * with `reference` bound to the previous iteration's new rows (linear delta recursion) until no
269
- * new rows appear. UNION deduplicates against everything seen; UNION ALL appends raw.
270
- */
271
- export interface RecursiveCte {
272
- reference: string;
273
- base: CompiledQuery;
274
- step: CompiledQuery;
275
- all: boolean;
276
- }
277
- export type PredicateOperator = ComparisonOperator | `${ComparisonOperator} ANY` | `${ComparisonOperator} ALL` | "IN" | "NOT IN" | "IS NULL" | "IS NOT NULL" | "LIKE" | "NOT LIKE" | "ILIKE" | "NOT ILIKE" | "SIMILAR TO" | "NOT SIMILAR TO" | "IS DISTINCT FROM" | "IS NOT DISTINCT FROM" | "IS TRUE";
278
- export interface Predicate {
279
- left: Expression;
280
- operator: PredicateOperator;
281
- right: Expression;
282
- /** LIKE/ILIKE escape character, carried from the parsed condition. */
283
- escape?: string;
284
- }
285
- export interface CompiledQuery {
286
- sql: string;
287
- base: TableSource;
288
- joins: JoinPlan[];
289
- select: SelectItem[];
290
- predicates: Predicate[];
291
- groupBy: Expression[];
292
- having: Predicate[];
293
- orderBy: Array<{
294
- expression: Expression;
295
- direction: "asc" | "desc";
296
- /**
297
- * Explicit NULL placement, absolute regardless of direction. Absent keeps PostgreSQL's
298
- * default: NULLS LAST for ASC and NULLS FIRST for DESC.
299
- */
300
- nulls?: "first" | "last";
301
- }>;
302
- limit?: number;
303
- offset?: number;
304
- /**
305
- * SELECT DISTINCT * awaiting expansion: the wildcard's columns are unknown until input
306
- * schemas exist, so every executor entry expands this into a concrete select list plus a
307
- * matching GROUP BY exactly once (see expandDistinctWildcard), like MATCH(*).
308
- */
309
- distinctWildcard?: boolean;
310
- /** Parameter slots for LIMIT ? / OFFSET ?; binding resolves them into limit/offset. */
311
- limitParameter?: number;
312
- offsetParameter?: number;
313
- /**
314
- * FETCH FIRST n ROWS WITH TIES (F866): rows tying with the last retained row under the
315
- * ORDER BY are kept too. The limit cannot be pushed into a scan then, so every execution
316
- * entry runs the query unlimited and trims the ordered result.
317
- */
318
- limitWithTies?: boolean;
319
- /**
320
- * Number of `?`/`$n` placeholders in the whole statement; set only on the top-level plan.
321
- * A plan with placeholders must pass through bindPlanParameters before it prepares.
322
- */
323
- parameterCount?: number;
324
- /**
325
- * CURRENT_DATE / CURRENT_TIMESTAMP / LOCALTIME appear somewhere in the statement. Every
326
- * executor entry replaces them with one instant per execution, and results never memoize,
327
- * because the answer depends on the clock rather than on the data.
328
- */
329
- usesStatementDatetime?: boolean;
330
- /** NEXTVAL/CURRVAL appear in this parsed statement and need connection-local resolution. */
331
- usesSequenceCalls?: boolean;
332
- /** RANDOM/GEN_RANDOM_UUID appear and make result memoization unsafe. */
333
- usesVolatileFunctions?: boolean;
334
- }
335
- /** ORDER BY / LIMIT / OFFSET tail of a select or set operation. */
336
- export interface SelectTail {
337
- orderBy: CompiledQuery["orderBy"];
338
- limit?: number;
339
- offset?: number;
340
- limitParameter?: number;
341
- offsetParameter?: number;
342
- limitWithTies?: boolean;
343
- }
344
70
  /**
345
71
  * The virtual one-row source behind a FROM-less SELECT. The name cannot collide with a real
346
72
  * table (parentheses never survive identifier parsing unquoted), and its single hidden column
@@ -473,6 +199,7 @@ export type CompiledStatement = {
473
199
  sqlDomain?: SqlDomain;
474
200
  nullable?: boolean;
475
201
  defaultValue?: ColumnDefault;
202
+ generatedValue?: ColumnGenerated;
476
203
  enumValues?: readonly string[];
477
204
  backfill?: Exclude<QueryValue, null>;
478
205
  }>;
@@ -597,6 +324,7 @@ export type CompiledStatement = {
597
324
  sqlDomain?: SqlDomain;
598
325
  nullable?: boolean;
599
326
  defaultValue?: ColumnDefault;
327
+ generatedValue?: ColumnGenerated;
600
328
  enumValues?: readonly string[];
601
329
  backfill?: Exclude<QueryValue, null>;
602
330
  };
@@ -969,4 +697,3 @@ export declare function validateOffset(offset: number): number;
969
697
  * boolean expression becomes a single IS TRUE predicate evaluated with three-valued logic.
970
698
  */
971
699
  export declare function splitCondition(expression: Expression): Predicate[];
972
- export {};
@@ -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": {
@@ -1765,7 +1765,10 @@ export function bindStatementParameters(statement, params) {
1765
1765
  */
1766
1766
  export function inferBlockSchema(plan, schemas) {
1767
1767
  const sources = [plan.base, ...plan.joins];
1768
- const multipleSources = sources.length > 1;
1768
+ const multipleSources = sources.filter((source) => {
1769
+ const schema = schemas.get(source.table);
1770
+ return schema === undefined || schema.some((column) => !column.name.startsWith("\0"));
1771
+ }).length > 1;
1769
1772
  const wildcardSchema = (source) => (schemas.get(source.table) ?? [])
1770
1773
  .filter((column) => !column.name.startsWith("\0"))
1771
1774
  .map((column) => ({
@@ -3514,10 +3517,10 @@ function executeRowQueryInternal(plan, tables, memory) {
3514
3517
  // Only a wildcard select needs the source shapes: every other select resolves against its
3515
3518
  // own output aliases.
3516
3519
  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
- }))
3520
+ ? [plan.base, ...plan.joins].flatMap((source) => {
3521
+ const columns = rowTableColumnNames(tables.get(source.table) ?? []).filter((name) => !name.startsWith("\0"));
3522
+ return columns.length === 0 ? [] : [{ alias: source.alias, columns }];
3523
+ })
3521
3524
  : [];
3522
3525
  const sortColumns = plan.orderBy.map(({ expression, direction, nulls }) => ({
3523
3526
  outputName: orderOutputName(expression, plan.select, orderSources),
@@ -3700,11 +3703,10 @@ function isSqlJoinKey(value) {
3700
3703
  }
3701
3704
  function project(select, context, group) {
3702
3705
  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
- ])));
3706
+ const aliases = Object.keys(context).filter((alias) => Object.keys(context[alias] ?? {}).some((name) => !name.startsWith("\0")));
3707
+ return Object.fromEntries(aliases.flatMap((alias) => Object.entries(context[alias] ?? {})
3708
+ .filter(([name]) => !name.startsWith("\0"))
3709
+ .map(([name, value]) => [aliases.length === 1 ? name : `${alias}.${name}`, value])));
3708
3710
  }
3709
3711
  return Object.fromEntries(select.map((item) => [item.alias, asQueryValue(evaluate(item.expression, context, group))]));
3710
3712
  }
@@ -3875,7 +3877,7 @@ function evaluate(expression, context, group) {
3875
3877
  if (expression.name === "JSON_ARRAYAGG") {
3876
3878
  return values.length === 0
3877
3879
  ? null
3878
- : JSON.stringify(values.map((value) => jsonValueOf(value ?? null)));
3880
+ : preservedJsonDomainValue(jsonConstructor("JSON_ARRAY", values));
3879
3881
  }
3880
3882
  if (expression.name === "MIN")
3881
3883
  return values.reduce((best, value) => (best === undefined || compareValues(value, best) < 0 ? value : best), undefined);
@@ -4861,6 +4863,7 @@ class Parser {
4861
4863
  const columnType = this.#columnType();
4862
4864
  let nullable = true;
4863
4865
  let defaultValue;
4866
+ let generatedValue;
4864
4867
  for (;;) {
4865
4868
  if (this.#isKeyword("DEFAULT")) {
4866
4869
  // PostgreSQL-compatible variable-free scalar expression, retained in the catalog.
@@ -4868,6 +4871,10 @@ class Parser {
4868
4871
  defaultValue = this.#columnDefault();
4869
4872
  continue;
4870
4873
  }
4874
+ if (this.#isKeyword("GENERATED")) {
4875
+ generatedValue = this.#generatedColumn();
4876
+ continue;
4877
+ }
4871
4878
  if (this.#isKeyword("CHECK")) {
4872
4879
  checks.push(this.#checkConstraint(`${table}_${name}_check`));
4873
4880
  continue;
@@ -4913,6 +4920,7 @@ class Parser {
4913
4920
  ...columnType,
4914
4921
  ...(nullable ? { nullable: true } : {}),
4915
4922
  ...(defaultValue === undefined ? {} : { defaultValue }),
4923
+ ...(generatedValue === undefined ? {} : { generatedValue }),
4916
4924
  });
4917
4925
  if (!this.#punctuation(","))
4918
4926
  break;
@@ -4938,6 +4946,23 @@ class Parser {
4938
4946
  }
4939
4947
  }
4940
4948
  }
4949
+ for (const column of columns) {
4950
+ if (column.defaultValue !== undefined && column.generatedValue !== undefined) {
4951
+ throw new TypeError(`Generated column ${column.name} cannot also have a DEFAULT`);
4952
+ }
4953
+ if (column.generatedValue === undefined)
4954
+ continue;
4955
+ for (const reference of expressionColumnNames(compileCheckExpression(column.generatedValue.sql, `generated ${table}.${column.name}`))) {
4956
+ const referencedName = reference.split(".").at(-1) ?? reference;
4957
+ const referenced = columns.find(({ name }) => name === referencedName);
4958
+ if (referenced === undefined) {
4959
+ throw new TypeError(`Generated column ${column.name} refers to a column this table has no: ${referencedName}`);
4960
+ }
4961
+ if (referenced === column || referenced.generatedValue !== undefined) {
4962
+ throw new TypeError(`Generated column ${column.name} cannot reference a generated column: ${referencedName}`);
4963
+ }
4964
+ }
4965
+ }
4941
4966
  // Preserve the released single-UNIQUE row-addressing behavior when no PRIMARY KEY was
4942
4967
  // declared. Additional UNIQUE constraints remain independently enforced secondary keys.
4943
4968
  const promotedUnique = primaryKey === undefined && uniqueConstraints[0]?.columns.length === 1
@@ -5060,6 +5085,23 @@ class Parser {
5060
5085
  throw new TypeError("DEFAULT requires an expression");
5061
5086
  return columnDefaultFor(expression, sql);
5062
5087
  }
5088
+ /** GENERATED [ALWAYS] AS (expression) STORED. */
5089
+ #generatedColumn() {
5090
+ this.#keyword("GENERATED");
5091
+ if (this.#isKeyword("ALWAYS"))
5092
+ this.#keyword("ALWAYS");
5093
+ this.#keyword("AS");
5094
+ const open = this.#peek();
5095
+ this.#expectPunctuation("(");
5096
+ const expression = this.#expression();
5097
+ const close = this.#peek();
5098
+ this.#expectPunctuation(")");
5099
+ this.#keyword("STORED");
5100
+ if (hasAggregate(expression) || containsWindow(expression) || containsParameter(expression)) {
5101
+ throw new TypeError("Generated columns take an immutable row expression");
5102
+ }
5103
+ return { kind: "stored", sql: this.text.slice(open.start + 1, close.start).trim() };
5104
+ }
5063
5105
  /** DROP VIEW [IF EXISTS] name (F031-16). */
5064
5106
  parseDropView() {
5065
5107
  this.#keyword("DROP");
@@ -7737,8 +7779,8 @@ class Parser {
7737
7779
  /**
7738
7780
  * GROUPING SETS desugar: one grouped block per set, combined with UNION ALL. A grouped column
7739
7781
  * 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.
7782
+ * expression's type through schema inference. GROUPING() becomes one constant bitmask per member
7783
+ * block, distinguishing columns aggregated away by the set from data NULLs.
7742
7784
  */
7743
7785
  function desugarGroupingSets(parts, nextSequence) {
7744
7786
  const { groupingSets, limit, offset, limitParameter, offsetParameter, ...blockParts } = parts;
@@ -8008,16 +8050,17 @@ export function assembleSelectBlock(parts, nextSequence) {
8008
8050
  export function expandDistinctWildcard(plan, columnsOf) {
8009
8051
  if (plan.distinctWildcard !== true)
8010
8052
  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) {
8053
+ const shaped = [plan.base, ...plan.joins].map((source) => ({
8054
+ source,
8055
+ columns: sourceWildcardColumns(source, columnsOf),
8056
+ }));
8057
+ const visible = shaped.filter(({ columns }) => (columns?.length ?? 0) > 0);
8058
+ const multiple = visible.length > 1;
8059
+ const select = visible.flatMap(({ source, columns }) => {
8060
+ if (columns === undefined) {
8016
8061
  throw new TypeError(`SELECT DISTINCT * requires known columns for: ${source.table}`);
8017
8062
  }
8018
- return columns
8019
- .filter((name) => !name.startsWith("\0"))
8020
- .map((name) => {
8063
+ return columns.map((name) => {
8021
8064
  const output = multiple ? `${source.alias}.${name}` : name;
8022
8065
  return { expression: { kind: "column", reference: output }, alias: output };
8023
8066
  });
@@ -8113,10 +8156,12 @@ export function withTiesPlan(plan) {
8113
8156
  return { plan, trim: (result) => result };
8114
8157
  if (plan.orderBy.length === 0)
8115
8158
  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
- }));
8159
+ const sources = [plan.base, ...plan.joins].flatMap((source) => {
8160
+ const columns = source.derived?.select
8161
+ .map((item) => item.alias)
8162
+ .filter((name) => !name.startsWith("\0"));
8163
+ return columns?.length === 0 ? [] : [{ alias: source.alias, columns: columns ?? [] }];
8164
+ });
8120
8165
  // orderOutputName throws when a sort key has no output column, which is the same failure the
8121
8166
  // executors report; nothing here has to re-check it.
8122
8167
  const keys = plan.orderBy.map(({ expression }) => orderOutputName(expression, plan.select, sources));
@@ -8296,7 +8341,8 @@ export function expandQualifiedWildcards(plan, columnsOf) {
8296
8341
  const expandBlock = (block) => {
8297
8342
  forEachNestedBlock(block, expandBlock);
8298
8343
  const sources = [block.base, ...block.joins];
8299
- const multiple = sources.length > 1;
8344
+ const multiple = sources.filter((source) => (sourceWildcardColumns(source, columnsOf)?.length ?? 0) > 0)
8345
+ .length > 1;
8300
8346
  block.select = block.select.flatMap((item) => {
8301
8347
  if (item.expression.kind !== "wildcard" || item.expression.table === undefined)
8302
8348
  return [item];
@@ -8386,9 +8432,9 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
8386
8432
  throw new TypeError("Window functions are only allowed in the select list");
8387
8433
  }
8388
8434
  // 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.
8435
+ // silently do nothing. Valid ordinals have already resolved; a remaining literal is invalid.
8390
8436
  if (order.expression.kind === "literal") {
8391
- throw new TypeError("ORDER BY ordinals are not supported; name the column or alias");
8437
+ throw new TypeError("ORDER BY position is outside the select list");
8392
8438
  }
8393
8439
  }
8394
8440
  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
  ? {}