@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.
- package/README.md +1 -1
- package/dist/engine/cancellation.d.ts +2 -0
- package/dist/engine/cancellation.js +4 -0
- package/dist/engine/catalog.d.ts +3 -1
- package/dist/engine/catalog.js +1 -0
- package/dist/engine/client.d.ts +32 -4
- package/dist/engine/client.js +82 -15
- package/dist/engine/database.d.ts +23 -14
- package/dist/engine/database.js +516 -74
- package/dist/engine/defaults.js +11 -0
- package/dist/engine/errors.d.ts +13 -0
- package/dist/engine/errors.js +22 -0
- package/dist/engine/fts.d.ts +2 -15
- package/dist/engine/live.d.ts +1 -7
- package/dist/engine/live.js +2 -12
- package/dist/engine/optimizer.js +540 -38
- package/dist/engine/query.d.ts +5 -278
- package/dist/engine/query.js +78 -32
- package/dist/engine/schema-wire.d.ts +7 -1
- package/dist/engine/schema-wire.js +4 -0
- package/dist/engine/schema.d.ts +67 -33
- package/dist/engine/schema.js +138 -7
- package/dist/engine/sql-domains.d.ts +8 -0
- package/dist/engine/sql-domains.js +25 -0
- package/dist/engine/sql-json.js +22 -3
- package/dist/engine/vector.d.ts +2 -2
- package/dist/engine/vector.js +301 -39
- package/dist/engine/worker-host.js +119 -44
- package/dist/plan/index.d.ts +5 -4
- package/dist/plan/index.js +3 -3
- package/dist/plan/model.d.ts +218 -0
- package/dist/plan/model.js +1 -0
- package/dist/storage/types.d.ts +7 -0
- package/dist/storage/types.js +16 -0
- package/dist/transactions/index.d.ts +5 -3
- package/dist/transactions/index.js +58 -8
- package/dist/worker-protocol/index.d.ts +6 -1
- package/dist/worker-protocol/index.js +5 -2
- package/package.json +1 -1
- package/sql-feature-matrix.json +69 -19
package/dist/engine/query.d.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import type { DatabaseRow } from "./database.js";
|
|
2
|
-
import type { ColumnDefault, SqlDomain } from "../storage/types.js";
|
|
3
|
-
import {
|
|
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 {};
|
package/dist/engine/query.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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].
|
|
3518
|
-
|
|
3519
|
-
columns:
|
|
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] ?? {})
|
|
3705
|
-
|
|
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
|
-
:
|
|
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.
|
|
7741
|
-
*
|
|
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
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
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].
|
|
8117
|
-
|
|
8118
|
-
|
|
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 >
|
|
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
|
|
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
|
|
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
|
? {}
|