@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.
- package/README.md +3 -2
- 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 +528 -79
- 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.d.ts +7 -0
- package/dist/engine/optimizer.js +1349 -76
- package/dist/engine/query.d.ts +11 -278
- package/dist/engine/query.js +178 -49
- 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 +369 -43
- 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 +224 -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/postgres-feature-profile.json +5 -0
- package/sql-feature-matrix.json +89 -21
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": {
|
|
@@ -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 ||
|
|
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.
|
|
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].
|
|
3518
|
-
|
|
3519
|
-
columns:
|
|
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] ?? {})
|
|
3705
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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" ||
|
|
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.
|
|
7741
|
-
*
|
|
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
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
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].
|
|
8117
|
-
|
|
8118
|
-
|
|
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 >
|
|
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
|
|
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
|
|
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
|
? {}
|