@typed-sql/sqlite 2.0.0-rc.1 → 2.1.0-rc.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/CHANGELOG.md +75 -0
- package/README.md +3 -3
- package/dist/packages/sqlite/src/capabilities.d.ts +18 -0
- package/dist/packages/sqlite/src/capabilities.d.ts.map +1 -0
- package/dist/packages/sqlite/src/capabilities.js +155 -0
- package/dist/packages/sqlite/src/capabilities.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/coercions.d.ts +6 -0
- package/dist/packages/sqlite/src/catalog/coercions.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/coercions.js +41 -0
- package/dist/packages/sqlite/src/catalog/coercions.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/date-time.d.ts +5 -0
- package/dist/packages/sqlite/src/catalog/date-time.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/date-time.js +20 -0
- package/dist/packages/sqlite/src/catalog/date-time.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/extensions.d.ts +5 -0
- package/dist/packages/sqlite/src/catalog/extensions.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/extensions.js +65 -0
- package/dist/packages/sqlite/src/catalog/extensions.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/functions.d.ts +57 -0
- package/dist/packages/sqlite/src/catalog/functions.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/functions.js +165 -0
- package/dist/packages/sqlite/src/catalog/functions.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/index.d.ts +8 -0
- package/dist/packages/sqlite/src/catalog/index.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/index.js +8 -0
- package/dist/packages/sqlite/src/catalog/index.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/json.d.ts +5 -0
- package/dist/packages/sqlite/src/catalog/json.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/json.js +146 -0
- package/dist/packages/sqlite/src/catalog/json.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/math.d.ts +4 -0
- package/dist/packages/sqlite/src/catalog/math.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/math.js +44 -0
- package/dist/packages/sqlite/src/catalog/math.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/operators.d.ts +10 -0
- package/dist/packages/sqlite/src/catalog/operators.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/operators.js +52 -0
- package/dist/packages/sqlite/src/catalog/operators.js.map +1 -0
- package/dist/packages/sqlite/src/index.d.ts +5 -2
- package/dist/packages/sqlite/src/index.d.ts.map +1 -1
- package/dist/packages/sqlite/src/index.js +15 -16
- package/dist/packages/sqlite/src/index.js.map +1 -1
- package/dist/packages/sqlite/src/node-sqlite.d.ts +18 -3
- package/dist/packages/sqlite/src/node-sqlite.d.ts.map +1 -1
- package/dist/packages/sqlite/src/node-sqlite.js +136 -25
- package/dist/packages/sqlite/src/node-sqlite.js.map +1 -1
- package/dist/packages/sqlite/src/parser/index.d.ts +8 -0
- package/dist/packages/sqlite/src/parser/index.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/index.js +4 -0
- package/dist/packages/sqlite/src/parser/index.js.map +1 -0
- package/dist/packages/sqlite/src/parser/parser.d.ts +14 -0
- package/dist/packages/sqlite/src/parser/parser.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/parser.js +1122 -0
- package/dist/packages/sqlite/src/parser/parser.js.map +1 -0
- package/dist/packages/sqlite/src/parser/tokenizer.d.ts +14 -0
- package/dist/packages/sqlite/src/parser/tokenizer.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/tokenizer.js +375 -0
- package/dist/packages/sqlite/src/parser/tokenizer.js.map +1 -0
- package/dist/packages/sqlite/src/parser/types.d.ts +313 -0
- package/dist/packages/sqlite/src/parser/types.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/types.js +9 -0
- package/dist/packages/sqlite/src/parser/types.js.map +1 -0
- package/dist/packages/sqlite/src/parser/walk.d.ts +13 -0
- package/dist/packages/sqlite/src/parser/walk.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/walk.js +180 -0
- package/dist/packages/sqlite/src/parser/walk.js.map +1 -0
- package/dist/packages/sqlite/src/prepared.d.ts +4 -3
- package/dist/packages/sqlite/src/prepared.d.ts.map +1 -1
- package/dist/packages/sqlite/src/prepared.js +7 -14
- package/dist/packages/sqlite/src/prepared.js.map +1 -1
- package/dist/packages/sqlite/src/provider.d.ts +27 -5
- package/dist/packages/sqlite/src/provider.d.ts.map +1 -1
- package/dist/packages/sqlite/src/provider.js +345 -14
- package/dist/packages/sqlite/src/provider.js.map +1 -1
- package/dist/packages/sqlite/src/resolver.d.ts +1 -1
- package/dist/packages/sqlite/src/resolver.d.ts.map +1 -1
- package/dist/packages/sqlite/src/resolver.js +989 -124
- package/dist/packages/sqlite/src/resolver.js.map +1 -1
- package/dist/packages/sqlite/src/runtime.d.ts +2 -0
- package/dist/packages/sqlite/src/runtime.d.ts.map +1 -1
- package/dist/packages/sqlite/src/runtime.js +1 -1
- package/dist/packages/sqlite/src/runtime.js.map +1 -1
- package/dist/packages/sqlite/src/schema-sql.d.ts +8 -0
- package/dist/packages/sqlite/src/schema-sql.d.ts.map +1 -0
- package/dist/packages/sqlite/src/schema-sql.js +157 -0
- package/dist/packages/sqlite/src/schema-sql.js.map +1 -0
- package/dist/packages/sqlite/src/semantics.d.ts +1 -1
- package/dist/packages/sqlite/src/semantics.d.ts.map +1 -1
- package/dist/packages/sqlite/src/semantics.js +44 -6
- package/dist/packages/sqlite/src/semantics.js.map +1 -1
- package/dist/packages/sqlite/src/snapshot.d.ts +10 -4
- package/dist/packages/sqlite/src/snapshot.d.ts.map +1 -1
- package/dist/packages/sqlite/src/snapshot.js +136 -3
- package/dist/packages/sqlite/src/snapshot.js.map +1 -1
- package/dist/packages/sqlite/src/support.d.ts +19 -0
- package/dist/packages/sqlite/src/support.d.ts.map +1 -0
- package/dist/packages/sqlite/src/support.js +59 -0
- package/dist/packages/sqlite/src/support.js.map +1 -0
- package/dist/packages/sqlite/src/type-policy.d.ts +3 -2
- package/dist/packages/sqlite/src/type-policy.d.ts.map +1 -1
- package/dist/packages/sqlite/src/type-policy.js +2 -12
- package/dist/packages/sqlite/src/type-policy.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,46 +1,75 @@
|
|
|
1
|
-
|
|
1
|
+
var _a;
|
|
2
|
+
import { ParameterCollector, ResolverSchemaIndex, unionTypeLiterals, } from "@typed-sql/core";
|
|
3
|
+
import { isSqliteAggregateFunction, isSqliteWindowFunction, normalizeSqliteDatabaseType, resolveSqliteBuiltin, SQLITE_CURRENT_TIME_KEYWORDS, SQLITE_EXTENSION_TABLE_FUNCTIONS, SQLITE_JSON_TABLE_FUNCTIONS, SQLITE_MATH_FUNCTIONS, SQLITE_UNSUPPORTED_OPERATORS, sqliteNumericOperands, sqliteOperator, } from "./catalog/index.js";
|
|
4
|
+
import { walkStatement } from "./parser/index.js";
|
|
5
|
+
import { compareSqliteVersions, parseSqliteVersion, sqliteVersionSupport } from "./support.js";
|
|
2
6
|
import { defaultSqliteTypePolicy, isKnownSqliteType, mapSqliteCastType, sqliteFlexibleType, } from "./type-policy.js";
|
|
3
|
-
const numericTypes = new Set([
|
|
4
|
-
"tinyint",
|
|
5
|
-
"smallint",
|
|
6
|
-
"mediumint",
|
|
7
|
-
"int",
|
|
8
|
-
"integer",
|
|
9
|
-
"bigint",
|
|
10
|
-
"decimal",
|
|
11
|
-
"numeric",
|
|
12
|
-
"float",
|
|
13
|
-
"double",
|
|
14
|
-
"real",
|
|
15
|
-
"bit",
|
|
16
|
-
"year",
|
|
17
|
-
]);
|
|
18
|
-
const comparisonOperators = new Set([
|
|
19
|
-
"=",
|
|
20
|
-
"!=",
|
|
21
|
-
"<>",
|
|
22
|
-
"<",
|
|
23
|
-
"<=",
|
|
24
|
-
">",
|
|
25
|
-
">=",
|
|
26
|
-
"IS",
|
|
27
|
-
"IS NOT",
|
|
28
|
-
"IS DISTINCT FROM",
|
|
29
|
-
"IS NOT DISTINCT FROM",
|
|
30
|
-
"LIKE",
|
|
31
|
-
"NOT LIKE",
|
|
32
|
-
"AND",
|
|
33
|
-
"OR",
|
|
34
|
-
]);
|
|
35
7
|
function name(identifier) {
|
|
36
8
|
return identifier.quoted ? identifier.name : identifier.name.toLowerCase();
|
|
37
9
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
10
|
+
function unionSqliteTypes(types) {
|
|
11
|
+
const members = types.flatMap((type) => {
|
|
12
|
+
const parts = [];
|
|
13
|
+
let depth = 0;
|
|
14
|
+
let quote;
|
|
15
|
+
let start = 0;
|
|
16
|
+
for (let index = 0; index < type.length; index += 1) {
|
|
17
|
+
const character = type[index];
|
|
18
|
+
if (quote !== undefined) {
|
|
19
|
+
if (character === quote && type[index - 1] !== "\\")
|
|
20
|
+
quote = undefined;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (character === "'" || character === '"' || character === "`")
|
|
24
|
+
quote = character;
|
|
25
|
+
else if (character === "(" || character === "[" || character === "{")
|
|
26
|
+
depth += 1;
|
|
27
|
+
else if (character === ")" || character === "]" || character === "}")
|
|
28
|
+
depth -= 1;
|
|
29
|
+
else if (depth === 0 && type.slice(index, index + 3) === " | ") {
|
|
30
|
+
parts.push(type.slice(start, index));
|
|
31
|
+
start = index + 3;
|
|
32
|
+
index += 2;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
parts.push(type.slice(start));
|
|
36
|
+
return parts;
|
|
37
|
+
});
|
|
38
|
+
return unionTypeLiterals(members);
|
|
39
|
+
}
|
|
40
|
+
function containsWindowFunction(expression) {
|
|
41
|
+
if (expression.kind === "call") {
|
|
42
|
+
return (expression.over !== undefined ||
|
|
43
|
+
expression.arguments.some(containsWindowFunction) ||
|
|
44
|
+
(expression.filter !== undefined && containsWindowFunction(expression.filter)));
|
|
45
|
+
}
|
|
46
|
+
if (expression.kind === "array" || expression.kind === "row")
|
|
47
|
+
return expression.elements.some(containsWindowFunction);
|
|
48
|
+
if (expression.kind === "cast" || expression.kind === "unary")
|
|
49
|
+
return containsWindowFunction(expression.expression);
|
|
50
|
+
if (expression.kind === "binary")
|
|
51
|
+
return containsWindowFunction(expression.left) || containsWindowFunction(expression.right);
|
|
52
|
+
if (expression.kind === "case")
|
|
53
|
+
return ((expression.operand !== undefined && containsWindowFunction(expression.operand)) ||
|
|
54
|
+
expression.branches.some((branch) => containsWindowFunction(branch.when) || containsWindowFunction(branch.then)) ||
|
|
55
|
+
(expression.elseExpression !== undefined && containsWindowFunction(expression.elseExpression)));
|
|
56
|
+
if (expression.kind === "in")
|
|
57
|
+
return (containsWindowFunction(expression.expression) ||
|
|
58
|
+
(!("kind" in expression.values) && expression.values.some(containsWindowFunction)));
|
|
59
|
+
if (expression.kind === "between")
|
|
60
|
+
return (containsWindowFunction(expression.expression) ||
|
|
61
|
+
containsWindowFunction(expression.lower) ||
|
|
62
|
+
containsWindowFunction(expression.upper));
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function isConstantFrameExpression(expression) {
|
|
66
|
+
if (expression.kind === "literal" || expression.kind === "parameter")
|
|
67
|
+
return true;
|
|
68
|
+
if (expression.kind === "unary")
|
|
69
|
+
return isConstantFrameExpression(expression.expression);
|
|
70
|
+
if (expression.kind === "binary")
|
|
71
|
+
return isConstantFrameExpression(expression.left) && isConstantFrameExpression(expression.right);
|
|
72
|
+
return false;
|
|
44
73
|
}
|
|
45
74
|
class Resolver {
|
|
46
75
|
#schema;
|
|
@@ -49,6 +78,7 @@ class Resolver {
|
|
|
49
78
|
#diagnostics = [];
|
|
50
79
|
#parameters = new ParameterCollector();
|
|
51
80
|
#index;
|
|
81
|
+
#activeWindows = new Map();
|
|
52
82
|
constructor(schema, options) {
|
|
53
83
|
this.#schema = schema;
|
|
54
84
|
this.#index = ResolverSchemaIndex.for(schema);
|
|
@@ -71,13 +101,30 @@ class Resolver {
|
|
|
71
101
|
return { columns: this.#select(statement, outer, ctes), resultKind: "rows" };
|
|
72
102
|
const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
|
|
73
103
|
const target = this.#relation(statement.table, false, scope, ctes);
|
|
104
|
+
const targetScope = {
|
|
105
|
+
relations: target === undefined ? [] : [target],
|
|
106
|
+
usingColumns: new Map(),
|
|
107
|
+
...(outer === undefined ? {} : { outer }),
|
|
108
|
+
};
|
|
74
109
|
if (statement.kind === "insert") {
|
|
75
110
|
const targets = statement.columns.length === 0
|
|
76
|
-
? Object.values(target?.table.columns ?? {}).filter((column) =>
|
|
111
|
+
? Object.values(target?.table.columns ?? {}).filter((column) => target === undefined || this.#insertable(target.table, column))
|
|
77
112
|
: statement.columns.map((column) => this.#findColumn(target?.table, column));
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
113
|
+
if (target !== undefined && statement.columns.length > 0) {
|
|
114
|
+
statement.columns.forEach((identifier, index) => {
|
|
115
|
+
const column = targets[index];
|
|
116
|
+
if (column !== undefined && !this.#insertable(target.table, column)) {
|
|
117
|
+
this.#diagnostic("TSQ218", `Cannot INSERT into non-insertable column ${column.name}`, identifier.range);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const supplied = new Set(statement.columns.map((column) => name(column).toLowerCase()));
|
|
121
|
+
const required = this.#index.requiredInsertColumns(target.table);
|
|
122
|
+
if (required !== "unknown") {
|
|
123
|
+
for (const column of required) {
|
|
124
|
+
if (!supplied.has(column.name.toLowerCase())) {
|
|
125
|
+
this.#diagnostic("TSQ219", `INSERT omits required column ${column.name}`, statement.table.range);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
81
128
|
}
|
|
82
129
|
}
|
|
83
130
|
if (statement.source.kind === "values") {
|
|
@@ -94,15 +141,16 @@ class Resolver {
|
|
|
94
141
|
if (selected.columns.length !== targets.length)
|
|
95
142
|
this.#diagnostic("TSQ214", `INSERT has ${targets.length} target columns but SELECT returns ${selected.columns.length}`, statement.source.range);
|
|
96
143
|
}
|
|
144
|
+
this.#upserts(statement, target, targetScope, ctes);
|
|
97
145
|
return statement.returning.length === 0
|
|
98
146
|
? { columns: [], resultKind: "command" }
|
|
99
|
-
: { columns: this.#
|
|
147
|
+
: { columns: this.#returning(statement.returning, targetScope, ctes), resultKind: "rows" };
|
|
100
148
|
}
|
|
101
149
|
if (statement.kind === "update") {
|
|
102
150
|
for (const assignment of statement.assignments) {
|
|
103
151
|
const column = this.#findColumn(target?.table, assignment.column);
|
|
104
|
-
if (column !== undefined && this.#
|
|
105
|
-
this.#diagnostic("
|
|
152
|
+
if (target !== undefined && column !== undefined && !this.#updatable(target.table, column)) {
|
|
153
|
+
this.#diagnostic("TSQ218", `Cannot UPDATE non-updatable column ${column.name}`, assignment.column.range);
|
|
106
154
|
}
|
|
107
155
|
this.#expression(assignment.value, scope, ctes, this.#snapshotType(column));
|
|
108
156
|
}
|
|
@@ -114,7 +162,7 @@ class Resolver {
|
|
|
114
162
|
this.#expression(statement.where, scope, ctes, this.#databaseType("boolean", false));
|
|
115
163
|
return statement.returning.length === 0
|
|
116
164
|
? { columns: [], resultKind: "command" }
|
|
117
|
-
: { columns: this.#
|
|
165
|
+
: { columns: this.#returning(statement.returning, targetScope, ctes), resultKind: "rows" };
|
|
118
166
|
}
|
|
119
167
|
if (statement.using.length > 0) {
|
|
120
168
|
this.#unsupported("SQLite does not support DELETE USING", statement.using[0].range);
|
|
@@ -125,22 +173,132 @@ class Resolver {
|
|
|
125
173
|
this.#expression(statement.where, scope, ctes, this.#databaseType("boolean", false));
|
|
126
174
|
return statement.returning.length === 0
|
|
127
175
|
? { columns: [], resultKind: "command" }
|
|
128
|
-
: { columns: this.#
|
|
176
|
+
: { columns: this.#returning(statement.returning, targetScope, ctes), resultKind: "rows" };
|
|
177
|
+
}
|
|
178
|
+
#upserts(statement, target, scope, ctes) {
|
|
179
|
+
if (statement.upserts.length === 0)
|
|
180
|
+
return;
|
|
181
|
+
if (statement.source.kind === "select" &&
|
|
182
|
+
statement.source.from !== undefined &&
|
|
183
|
+
statement.source.where === undefined) {
|
|
184
|
+
this.#diagnostic("TSQ224", "INSERT SELECT with UPSERT requires a WHERE clause to avoid SQLite's ON parsing ambiguity", statement.source.range, "Add WHERE TRUE before ON CONFLICT.");
|
|
185
|
+
}
|
|
186
|
+
const excluded = target === undefined ? undefined : { alias: "excluded", table: target.table, nullable: false };
|
|
187
|
+
const actionScope = {
|
|
188
|
+
relations: [...scope.relations, ...(excluded === undefined ? [] : [excluded])],
|
|
189
|
+
usingColumns: new Map(),
|
|
190
|
+
...(scope.outer === undefined ? {} : { outer: scope.outer }),
|
|
191
|
+
};
|
|
192
|
+
statement.upserts.forEach((upsert, upsertIndex) => {
|
|
193
|
+
if (upsert.target.length === 0 && upsertIndex !== statement.upserts.length - 1)
|
|
194
|
+
this.#diagnostic("TSQ224", "Only the final ON CONFLICT clause may omit its conflict target", upsert.range);
|
|
195
|
+
if (upsert.target.length > 0 && target !== undefined)
|
|
196
|
+
this.#validateConflictTarget(target.table, upsert.target, upsert.targetWhere !== undefined, upsert.range);
|
|
197
|
+
for (const term of upsert.target)
|
|
198
|
+
this.#expression(term.expression, scope, ctes);
|
|
199
|
+
if (upsert.targetWhere !== undefined)
|
|
200
|
+
this.#expression(upsert.targetWhere, scope, ctes, this.#databaseType("integer", false));
|
|
201
|
+
if (upsert.action.kind === "nothing")
|
|
202
|
+
return;
|
|
203
|
+
for (const assignment of upsert.action.assignments) {
|
|
204
|
+
const column = this.#findColumn(target?.table, assignment.column);
|
|
205
|
+
if (target !== undefined && column !== undefined && !this.#updatable(target.table, column))
|
|
206
|
+
this.#diagnostic("TSQ218", `Cannot UPDATE non-updatable column ${column.name}`, assignment.column.range);
|
|
207
|
+
this.#expression(assignment.value, actionScope, ctes, this.#snapshotType(column));
|
|
208
|
+
}
|
|
209
|
+
if (upsert.action.where !== undefined)
|
|
210
|
+
this.#expression(upsert.action.where, actionScope, ctes, this.#databaseType("integer", false));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
#validateConflictTarget(table, terms, partial, range) {
|
|
214
|
+
const sqliteTable = table;
|
|
215
|
+
if (partial) {
|
|
216
|
+
this.#diagnostic("TSQ402", "Exact partial conflict-target matching requires normalized predicate evidence", range);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const columns = [];
|
|
220
|
+
for (const term of terms) {
|
|
221
|
+
if (term.expression.kind !== "column" || term.expression.relation !== undefined) {
|
|
222
|
+
this.#diagnostic("TSQ402", "Exact expression conflict-target matching requires normalized index-expression evidence", term.range);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
columns.push({
|
|
226
|
+
name: name(term.expression.column),
|
|
227
|
+
...(term.collation === undefined ? {} : { collation: name(term.collation) }),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const primaryColumns = Object.values(table.columns)
|
|
231
|
+
.map((column) => column)
|
|
232
|
+
.filter((column) => column.primaryKeyPosition !== undefined)
|
|
233
|
+
.sort((left, right) => left.primaryKeyPosition - right.primaryKeyPosition)
|
|
234
|
+
.map((column) => column.name.toLowerCase());
|
|
235
|
+
const primaryMatch = !partial &&
|
|
236
|
+
primaryColumns.length === columns.length &&
|
|
237
|
+
primaryColumns.every((value, index) => value === columns[index]?.name) &&
|
|
238
|
+
columns.every(({ collation }) => collation === undefined);
|
|
239
|
+
const indexMatch = (sqliteTable.indexes ?? []).some((index) => index.unique &&
|
|
240
|
+
!index.partial &&
|
|
241
|
+
index.columns.length === columns.length &&
|
|
242
|
+
index.columns.every((column, columnIndex) => {
|
|
243
|
+
const requested = columns[columnIndex];
|
|
244
|
+
return (!column.expression &&
|
|
245
|
+
column.name?.toLowerCase() === requested.name &&
|
|
246
|
+
(requested.collation === undefined || column.collation?.toLowerCase() === requested.collation));
|
|
247
|
+
}));
|
|
248
|
+
if (!primaryMatch && !indexMatch)
|
|
249
|
+
this.#diagnostic("TSQ226", "Conflict target does not match a declared UNIQUE or PRIMARY KEY constraint", range);
|
|
250
|
+
}
|
|
251
|
+
#returning(items, scope, ctes) {
|
|
252
|
+
for (const item of items) {
|
|
253
|
+
if (containsWindowFunction(item.expression) || this.#containsTopLevelAggregate(item.expression))
|
|
254
|
+
this.#diagnostic("TSQ225", "SQLite RETURNING cannot contain top-level aggregate or window functions", item.expression.range);
|
|
255
|
+
}
|
|
256
|
+
return this.#items(items, scope, ctes);
|
|
257
|
+
}
|
|
258
|
+
#containsTopLevelAggregate(expression) {
|
|
259
|
+
if (expression.kind === "call")
|
|
260
|
+
return (isSqliteAggregateFunction(expression.name.name) ||
|
|
261
|
+
expression.arguments.some((argument) => this.#containsTopLevelAggregate(argument)) ||
|
|
262
|
+
(expression.filter !== undefined && this.#containsTopLevelAggregate(expression.filter)));
|
|
263
|
+
if (expression.kind === "array" || expression.kind === "row")
|
|
264
|
+
return expression.elements.some((element) => this.#containsTopLevelAggregate(element));
|
|
265
|
+
if (expression.kind === "cast" || expression.kind === "unary")
|
|
266
|
+
return this.#containsTopLevelAggregate(expression.expression);
|
|
267
|
+
if (expression.kind === "binary")
|
|
268
|
+
return this.#containsTopLevelAggregate(expression.left) || this.#containsTopLevelAggregate(expression.right);
|
|
269
|
+
if (expression.kind === "case")
|
|
270
|
+
return ((expression.operand !== undefined && this.#containsTopLevelAggregate(expression.operand)) ||
|
|
271
|
+
expression.branches.some(({ when, then }) => this.#containsTopLevelAggregate(when) || this.#containsTopLevelAggregate(then)) ||
|
|
272
|
+
(expression.elseExpression !== undefined && this.#containsTopLevelAggregate(expression.elseExpression)));
|
|
273
|
+
if (expression.kind === "in")
|
|
274
|
+
return (this.#containsTopLevelAggregate(expression.expression) ||
|
|
275
|
+
(!("kind" in expression.values) && expression.values.some((value) => this.#containsTopLevelAggregate(value))));
|
|
276
|
+
if (expression.kind === "between")
|
|
277
|
+
return (this.#containsTopLevelAggregate(expression.expression) ||
|
|
278
|
+
this.#containsTopLevelAggregate(expression.lower) ||
|
|
279
|
+
this.#containsTopLevelAggregate(expression.upper));
|
|
280
|
+
return false;
|
|
129
281
|
}
|
|
130
282
|
#with(withClause, outer, inherited) {
|
|
131
283
|
const ctes = new Map(inherited);
|
|
132
284
|
if (withClause === undefined)
|
|
133
285
|
return ctes;
|
|
134
|
-
if (withClause.recursive)
|
|
135
|
-
this.#unsupported("Recursive CTE inference is not supported safely", withClause.range);
|
|
136
286
|
for (const query of withClause.queries) {
|
|
137
287
|
const key = name(query.name);
|
|
138
288
|
if (ctes.has(key))
|
|
139
289
|
this.#diagnostic("TSQ211", `Duplicate CTE ${query.name.name}`, query.name.range);
|
|
140
|
-
const result = this.#statement(query.statement, outer, ctes);
|
|
141
290
|
if (query.statement.kind !== "select") {
|
|
142
291
|
this.#unsupported("SQLite CTE bodies must be SELECT statements", query.statement.range);
|
|
143
292
|
}
|
|
293
|
+
const recursive = query.statement.kind === "select" && this.#isRecursiveCte(query);
|
|
294
|
+
const result = query.statement.kind === "select"
|
|
295
|
+
? recursive
|
|
296
|
+
? this.#recursiveCte(query, outer, ctes)
|
|
297
|
+
: {
|
|
298
|
+
columns: this.#select(query.statement, outer, ctes, query.columns.length === 0),
|
|
299
|
+
resultKind: "rows",
|
|
300
|
+
}
|
|
301
|
+
: this.#statement(query.statement, outer, ctes);
|
|
144
302
|
if (result.resultKind === "command")
|
|
145
303
|
this.#diagnostic("TSQ212", `CTE ${query.name.name} does not return rows`, query.range);
|
|
146
304
|
if (query.columns.length > 0 && query.columns.length !== result.columns.length)
|
|
@@ -159,10 +317,174 @@ class Resolver {
|
|
|
159
317
|
}
|
|
160
318
|
return ctes;
|
|
161
319
|
}
|
|
162
|
-
#
|
|
320
|
+
#isRecursiveCte(query) {
|
|
321
|
+
if (query.statement.kind !== "select")
|
|
322
|
+
return false;
|
|
323
|
+
return this.#selectArms(query.statement).some(({ statement }) => this.#selfReferences(statement, query.name) > 0);
|
|
324
|
+
}
|
|
325
|
+
#recursiveCte(query, outer, inherited) {
|
|
326
|
+
const arms = this.#selectArms(query.statement);
|
|
327
|
+
const references = arms.map(({ statement }) => ({
|
|
328
|
+
direct: this.#directSelfReferences(statement, query.name),
|
|
329
|
+
total: this.#selfReferences(statement, query.name),
|
|
330
|
+
}));
|
|
331
|
+
const firstRecursive = references.findIndex(({ total }) => total > 0);
|
|
332
|
+
let valid = true;
|
|
333
|
+
const invalid = (message, range) => {
|
|
334
|
+
valid = false;
|
|
335
|
+
this.#diagnostic("TSQ220", message, range);
|
|
336
|
+
};
|
|
337
|
+
if (arms.length < 2)
|
|
338
|
+
invalid(`Recursive CTE ${query.name.name} must be a compound SELECT`, query.range);
|
|
339
|
+
if (firstRecursive <= 0)
|
|
340
|
+
invalid(`Recursive CTE ${query.name.name} requires a non-recursive SELECT before its recursive member`, query.range);
|
|
341
|
+
let recursiveAll;
|
|
342
|
+
for (let index = 0; index < arms.length; index += 1) {
|
|
343
|
+
const arm = arms[index];
|
|
344
|
+
const reference = references[index];
|
|
345
|
+
if (reference.total === 0) {
|
|
346
|
+
if (firstRecursive >= 0 && index > firstRecursive)
|
|
347
|
+
invalid(`Non-recursive members of ${query.name.name} must precede recursive members`, arm.statement.range);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (reference.direct !== 1 || reference.total !== 1) {
|
|
351
|
+
invalid(`Each recursive member of ${query.name.name} must reference itself exactly once in its top-level FROM clause`, arm.statement.range);
|
|
352
|
+
}
|
|
353
|
+
if (arm.operator !== "union") {
|
|
354
|
+
invalid(`Recursive members of ${query.name.name} must be joined with UNION or UNION ALL`, arm.range);
|
|
355
|
+
}
|
|
356
|
+
else if (recursiveAll === undefined)
|
|
357
|
+
recursiveAll = arm.all ?? false;
|
|
358
|
+
else if ((arm.all ?? false) !== recursiveAll) {
|
|
359
|
+
invalid(`All recursive members of ${query.name.name} must use the same UNION form`, arm.range);
|
|
360
|
+
}
|
|
361
|
+
if (this.#hasAggregateOrWindow(arm.statement)) {
|
|
362
|
+
this.#diagnostic("TSQ221", `Recursive member of ${query.name.name} cannot use aggregate or window functions`, arm.statement.range);
|
|
363
|
+
valid = false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const seedColumns = this.#mergeCteSeedColumns(arms.slice(0, Math.max(firstRecursive, 0)).map(({ statement }) => statement), outer, inherited);
|
|
367
|
+
const provisionalColumns = {};
|
|
368
|
+
const width = Math.max(query.columns.length, seedColumns.length);
|
|
369
|
+
for (let index = 0; index < width; index += 1) {
|
|
370
|
+
const seed = seedColumns[index];
|
|
371
|
+
const columnName = query.columns[index] === undefined ? seed?.name : name(query.columns[index]);
|
|
372
|
+
if (columnName === undefined)
|
|
373
|
+
continue;
|
|
374
|
+
provisionalColumns[columnName] = {
|
|
375
|
+
name: columnName,
|
|
376
|
+
databaseType: seed?.databaseType ?? "unknown",
|
|
377
|
+
tsType: seed?.tsType ?? "unknown",
|
|
378
|
+
nullable: seed?.nullable ?? true,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const recursiveScope = new Map(inherited);
|
|
382
|
+
recursiveScope.set(name(query.name), { name: name(query.name), columns: provisionalColumns });
|
|
383
|
+
const columns = this.#select(query.statement, outer, recursiveScope, query.columns.length === 0);
|
|
384
|
+
if (!valid && columns.length === 0) {
|
|
385
|
+
return {
|
|
386
|
+
columns: Object.values(provisionalColumns).map((column) => ({
|
|
387
|
+
name: column.name,
|
|
388
|
+
tsType: "unknown",
|
|
389
|
+
nullable: true,
|
|
390
|
+
databaseType: "unknown",
|
|
391
|
+
range: query.range,
|
|
392
|
+
})),
|
|
393
|
+
resultKind: "rows",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return { columns, resultKind: "rows" };
|
|
397
|
+
}
|
|
398
|
+
#selectArms(statement) {
|
|
399
|
+
const { compounds, ...simple } = statement;
|
|
400
|
+
const arms = [{ statement: { ...simple, compounds: [] }, range: statement.range }];
|
|
401
|
+
for (const compound of compounds) {
|
|
402
|
+
const nested = this.#selectArms(compound.statement);
|
|
403
|
+
const [first, ...rest] = nested;
|
|
404
|
+
if (first !== undefined)
|
|
405
|
+
arms.push({
|
|
406
|
+
statement: first.statement,
|
|
407
|
+
operator: compound.operator,
|
|
408
|
+
all: compound.all,
|
|
409
|
+
range: compound.range,
|
|
410
|
+
});
|
|
411
|
+
arms.push(...rest);
|
|
412
|
+
}
|
|
413
|
+
return arms;
|
|
414
|
+
}
|
|
415
|
+
#directSelfReferences(statement, identifier) {
|
|
416
|
+
const references = [statement.from, ...statement.joins.map(({ table }) => table)];
|
|
417
|
+
return references.filter((reference) => reference?.kind === "table" && reference.schema === undefined && name(reference.name) === name(identifier)).length;
|
|
418
|
+
}
|
|
419
|
+
#selfReferences(statement, identifier) {
|
|
420
|
+
let references = 0;
|
|
421
|
+
walkStatement(statement, {
|
|
422
|
+
table(reference) {
|
|
423
|
+
if (reference.kind === "table" && reference.schema === undefined && name(reference.name) === name(identifier)) {
|
|
424
|
+
references += 1;
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
return references;
|
|
429
|
+
}
|
|
430
|
+
#hasAggregateOrWindow(statement) {
|
|
431
|
+
let prohibited = statement.windows.length > 0;
|
|
432
|
+
walkStatement(statement, {
|
|
433
|
+
expression(expression) {
|
|
434
|
+
if (expression.kind === "call" &&
|
|
435
|
+
(expression.over !== undefined ||
|
|
436
|
+
expression.filter !== undefined ||
|
|
437
|
+
isSqliteAggregateFunction(expression.name.name))) {
|
|
438
|
+
prohibited = true;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
return prohibited;
|
|
443
|
+
}
|
|
444
|
+
#mergeCteSeedColumns(statements, outer, inherited) {
|
|
445
|
+
const merged = [];
|
|
446
|
+
for (const statement of statements) {
|
|
447
|
+
const probe = new _a(this.#schema, { typePolicy: this.#policy, strictExpressions: false });
|
|
448
|
+
const columns = probe.#select(statement, outer, inherited, false);
|
|
449
|
+
if (merged.length === 0) {
|
|
450
|
+
merged.push(...columns);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
for (let index = 0; index < Math.min(merged.length, columns.length); index += 1) {
|
|
454
|
+
const current = merged[index];
|
|
455
|
+
const candidate = columns[index];
|
|
456
|
+
const sameDatabaseType = current.databaseType === candidate.databaseType && current.databaseType !== undefined;
|
|
457
|
+
merged[index] = {
|
|
458
|
+
name: current.name,
|
|
459
|
+
tsType: unionSqliteTypes([current.tsType, candidate.tsType]),
|
|
460
|
+
nullable: current.nullable || candidate.nullable,
|
|
461
|
+
...(sameDatabaseType ? { databaseType: current.databaseType } : {}),
|
|
462
|
+
range: current.range,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return merged;
|
|
467
|
+
}
|
|
468
|
+
#select(statement, outer, ctes, requireOutputNames = true) {
|
|
469
|
+
const previousWindows = this.#activeWindows;
|
|
470
|
+
this.#activeWindows = this.#windows(statement);
|
|
163
471
|
const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
|
|
164
472
|
if (statement.distinctOn.length > 0)
|
|
165
473
|
this.#unsupported("SQLite does not support DISTINCT ON", statement.distinctOn[0].range);
|
|
474
|
+
const prohibitedWindowExpressions = [
|
|
475
|
+
statement.where,
|
|
476
|
+
statement.having,
|
|
477
|
+
...statement.groupBy,
|
|
478
|
+
...statement.joins.map(({ on }) => on),
|
|
479
|
+
...statement.windows.flatMap(({ specification }) => [
|
|
480
|
+
...specification.partitionBy,
|
|
481
|
+
...specification.orderBy.map(({ expression }) => expression),
|
|
482
|
+
]),
|
|
483
|
+
].filter((expression) => expression !== undefined);
|
|
484
|
+
for (const expression of prohibitedWindowExpressions) {
|
|
485
|
+
if (containsWindowFunction(expression))
|
|
486
|
+
this.#diagnostic("TSQ223", "Window functions are only allowed in SELECT results and ORDER BY", expression.range);
|
|
487
|
+
}
|
|
166
488
|
if (statement.from !== undefined)
|
|
167
489
|
this.#relation(statement.from, false, scope, ctes);
|
|
168
490
|
for (const join of statement.joins)
|
|
@@ -180,8 +502,9 @@ class Resolver {
|
|
|
180
502
|
this.#expression(value, scope, ctes);
|
|
181
503
|
for (const item of window.specification.orderBy)
|
|
182
504
|
this.#expression(item.expression, scope, ctes);
|
|
505
|
+
this.#windowFrame(window.specification, scope, ctes);
|
|
183
506
|
}
|
|
184
|
-
const columns = [...this.#items(statement.columns, scope, ctes)];
|
|
507
|
+
const columns = [...this.#items(statement.columns, scope, ctes, requireOutputNames)];
|
|
185
508
|
const outputAliases = new Set(columns.map((column) => column.name));
|
|
186
509
|
for (const compound of statement.compounds) {
|
|
187
510
|
for (const output of this.#compoundOutputNames(compound.statement))
|
|
@@ -198,7 +521,7 @@ class Resolver {
|
|
|
198
521
|
if (statement.offset !== undefined)
|
|
199
522
|
this.#expression(statement.offset, scope, ctes, this.#databaseType("int", false));
|
|
200
523
|
for (const compound of statement.compounds) {
|
|
201
|
-
const right = this.#select(compound.statement, outer, ctes);
|
|
524
|
+
const right = this.#select(compound.statement, outer, ctes, false);
|
|
202
525
|
if (right.length !== columns.length) {
|
|
203
526
|
this.#diagnostic("TSQ214", `Compound SELECT has ${columns.length} columns on the left and ${right.length} on the right`, compound.range);
|
|
204
527
|
continue;
|
|
@@ -209,7 +532,7 @@ class Resolver {
|
|
|
209
532
|
const { databaseType: _databaseType, ...leftWithoutDatabaseType } = left;
|
|
210
533
|
columns[index] = {
|
|
211
534
|
...leftWithoutDatabaseType,
|
|
212
|
-
tsType:
|
|
535
|
+
tsType: unionSqliteTypes([left.tsType, candidate.tsType]),
|
|
213
536
|
nullable: left.nullable || candidate.nullable,
|
|
214
537
|
...(left.databaseType === candidate.databaseType && left.databaseType !== undefined
|
|
215
538
|
? { databaseType: left.databaseType }
|
|
@@ -217,8 +540,74 @@ class Resolver {
|
|
|
217
540
|
};
|
|
218
541
|
}
|
|
219
542
|
}
|
|
543
|
+
this.#activeWindows = previousWindows;
|
|
220
544
|
return columns;
|
|
221
545
|
}
|
|
546
|
+
#windows(statement) {
|
|
547
|
+
const windows = new Map();
|
|
548
|
+
for (const window of statement.windows) {
|
|
549
|
+
const key = name(window.name);
|
|
550
|
+
if (windows.has(key)) {
|
|
551
|
+
this.#diagnostic("TSQ222", `Duplicate window ${window.name.name}`, window.name.range);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const specification = this.#effectiveWindow(window.specification, windows);
|
|
555
|
+
windows.set(key, specification);
|
|
556
|
+
}
|
|
557
|
+
return windows;
|
|
558
|
+
}
|
|
559
|
+
#effectiveWindow(specification, windows = this.#activeWindows) {
|
|
560
|
+
if (specification.base === undefined) {
|
|
561
|
+
this.#validateWindowFrame(specification);
|
|
562
|
+
return specification;
|
|
563
|
+
}
|
|
564
|
+
const base = windows.get(name(specification.base));
|
|
565
|
+
if (base === undefined) {
|
|
566
|
+
this.#diagnostic("TSQ222", `Unknown base window ${specification.base.name}`, specification.base.range);
|
|
567
|
+
return specification;
|
|
568
|
+
}
|
|
569
|
+
if (specification.partitionBy.length > 0)
|
|
570
|
+
this.#diagnostic("TSQ222", "A chained window cannot replace PARTITION BY", specification.range);
|
|
571
|
+
if (base.orderBy.length > 0 && specification.orderBy.length > 0)
|
|
572
|
+
this.#diagnostic("TSQ222", "A chained window cannot replace ORDER BY", specification.range);
|
|
573
|
+
if (base.frame !== undefined)
|
|
574
|
+
this.#diagnostic("TSQ222", "A framed window cannot be used as a chaining base", specification.base.range);
|
|
575
|
+
const effective = {
|
|
576
|
+
...specification,
|
|
577
|
+
partitionBy: base.partitionBy,
|
|
578
|
+
orderBy: base.orderBy.length > 0 ? base.orderBy : specification.orderBy,
|
|
579
|
+
};
|
|
580
|
+
this.#validateWindowFrame(effective);
|
|
581
|
+
return effective;
|
|
582
|
+
}
|
|
583
|
+
#validateWindowFrame(specification) {
|
|
584
|
+
const frame = specification.frame;
|
|
585
|
+
if (frame?.unit !== "range")
|
|
586
|
+
return;
|
|
587
|
+
const hasOffset = [frame.start, frame.end].some(({ kind }) => kind === "preceding" || kind === "following");
|
|
588
|
+
if (hasOffset && specification.orderBy.length !== 1) {
|
|
589
|
+
this.#diagnostic("TSQ222", "A RANGE offset frame requires exactly one ORDER BY expression", frame.range);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
#windowFrame(specification, scope, ctes) {
|
|
593
|
+
const frame = specification.frame;
|
|
594
|
+
if (frame === undefined)
|
|
595
|
+
return;
|
|
596
|
+
for (const boundary of [frame.start, frame.end]) {
|
|
597
|
+
if (boundary.kind === "preceding" || boundary.kind === "following") {
|
|
598
|
+
if (!isConstantFrameExpression(boundary.expression))
|
|
599
|
+
this.#diagnostic("TSQ222", "A window-frame offset must be a constant numeric expression", boundary.range);
|
|
600
|
+
if ((frame.unit === "rows" || frame.unit === "groups") &&
|
|
601
|
+
boundary.expression.kind === "literal" &&
|
|
602
|
+
(typeof boundary.expression.value !== "number" ||
|
|
603
|
+
!Number.isInteger(boundary.expression.value) ||
|
|
604
|
+
boundary.expression.value < 0)) {
|
|
605
|
+
this.#diagnostic("TSQ222", `${frame.unit.toUpperCase()} offsets must be non-negative integers`, boundary.range);
|
|
606
|
+
}
|
|
607
|
+
this.#expression(boundary.expression, scope, ctes, this.#databaseType("numeric", false));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
222
611
|
#compoundOutputNames(statement) {
|
|
223
612
|
const outputs = new Set();
|
|
224
613
|
for (const item of statement.columns) {
|
|
@@ -253,7 +642,7 @@ class Resolver {
|
|
|
253
642
|
const left = this.#columnType(leftMatches[0], this.#column(leftMatches[0].table, columnName));
|
|
254
643
|
const rightType = this.#columnType(relation, right);
|
|
255
644
|
scope.usingColumns.set(columnName, {
|
|
256
|
-
tsType:
|
|
645
|
+
tsType: unionSqliteTypes([left.tsType, rightType.tsType]),
|
|
257
646
|
nullable: join.kind === "left"
|
|
258
647
|
? left.nullable
|
|
259
648
|
: join.kind === "right"
|
|
@@ -268,7 +657,85 @@ class Resolver {
|
|
|
268
657
|
#relation(reference, nullable, scope, ctes) {
|
|
269
658
|
let table;
|
|
270
659
|
let alias;
|
|
271
|
-
if (reference.kind === "
|
|
660
|
+
if (reference.kind === "table-function") {
|
|
661
|
+
if (reference.lateral)
|
|
662
|
+
this.#unsupported("SQLite does not support the LATERAL keyword", reference.range);
|
|
663
|
+
for (const argument of reference.arguments)
|
|
664
|
+
this.#expression(argument, scope, ctes);
|
|
665
|
+
const functionName = reference.name.name.toUpperCase();
|
|
666
|
+
const definition = [...SQLITE_JSON_TABLE_FUNCTIONS, ...SQLITE_EXTENSION_TABLE_FUNCTIONS].find(({ name }) => name === functionName);
|
|
667
|
+
if (definition === undefined) {
|
|
668
|
+
this.#diagnostic("TSQ100", `Unknown table-valued function ${reference.name.name}`, reference.name.range);
|
|
669
|
+
return undefined;
|
|
670
|
+
}
|
|
671
|
+
if (reference.arguments.length < definition.arguments[0] ||
|
|
672
|
+
reference.arguments.length > definition.arguments[1]) {
|
|
673
|
+
this.#diagnostic("TSQ227", `${reference.name.name} expects ${this.#arity(definition.arguments)} arguments, received ${reference.arguments.length}`, reference.range);
|
|
674
|
+
}
|
|
675
|
+
if (!this.#tableFunctionAvailable(definition, reference.range))
|
|
676
|
+
return undefined;
|
|
677
|
+
if (definition.result === "carray") {
|
|
678
|
+
if (reference.arguments[1]?.kind === "parameter")
|
|
679
|
+
this.#expression(reference.arguments[1], scope, ctes, this.#databaseType("integer", false));
|
|
680
|
+
if (reference.arguments[2]?.kind === "parameter")
|
|
681
|
+
this.#expression(reference.arguments[2], scope, ctes, this.#databaseType("text", false));
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
const jsonInput = {
|
|
685
|
+
tsType: sqliteFlexibleType(this.#policy),
|
|
686
|
+
nullable: true,
|
|
687
|
+
databaseType: "json",
|
|
688
|
+
};
|
|
689
|
+
const rootPath = { tsType: "string", nullable: true, databaseType: "text" };
|
|
690
|
+
if (reference.arguments[0]?.kind === "parameter")
|
|
691
|
+
this.#expression(reference.arguments[0], scope, ctes, jsonInput);
|
|
692
|
+
if (reference.arguments[1]?.kind === "parameter")
|
|
693
|
+
this.#expression(reference.arguments[1], scope, ctes, rootPath);
|
|
694
|
+
}
|
|
695
|
+
const flexible = sqliteFlexibleType(this.#policy);
|
|
696
|
+
table =
|
|
697
|
+
definition.result === "carray"
|
|
698
|
+
? {
|
|
699
|
+
name: name(reference.alias ?? reference.name),
|
|
700
|
+
columns: {
|
|
701
|
+
value: { name: "value", databaseType: "ANY", tsType: flexible, nullable: true },
|
|
702
|
+
},
|
|
703
|
+
}
|
|
704
|
+
: {
|
|
705
|
+
name: name(reference.alias ?? reference.name),
|
|
706
|
+
columns: {
|
|
707
|
+
key: {
|
|
708
|
+
name: "key",
|
|
709
|
+
databaseType: "ANY",
|
|
710
|
+
tsType: unionSqliteTypes([this.#policy.integer, "string"]),
|
|
711
|
+
nullable: true,
|
|
712
|
+
},
|
|
713
|
+
value: {
|
|
714
|
+
name: "value",
|
|
715
|
+
databaseType: "ANY",
|
|
716
|
+
tsType: definition.result === "jsonb"
|
|
717
|
+
? unionSqliteTypes([this.#policy.integer, "number", "string", "Uint8Array"])
|
|
718
|
+
: unionSqliteTypes([this.#policy.integer, "number", "string"]),
|
|
719
|
+
nullable: true,
|
|
720
|
+
},
|
|
721
|
+
type: { name: "type", databaseType: "TEXT", tsType: "string", nullable: false },
|
|
722
|
+
atom: {
|
|
723
|
+
name: "atom",
|
|
724
|
+
databaseType: "ANY",
|
|
725
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number", "string"]),
|
|
726
|
+
nullable: true,
|
|
727
|
+
},
|
|
728
|
+
id: { name: "id", databaseType: "INTEGER", tsType: this.#policy.integer, nullable: false },
|
|
729
|
+
parent: { name: "parent", databaseType: "INTEGER", tsType: this.#policy.integer, nullable: true },
|
|
730
|
+
fullkey: { name: "fullkey", databaseType: "TEXT", tsType: "string", nullable: false },
|
|
731
|
+
path: { name: "path", databaseType: "TEXT", tsType: "string", nullable: false },
|
|
732
|
+
json: { name: "json", databaseType: "JSON", tsType: "string", nullable: true, hidden: true },
|
|
733
|
+
root: { name: "root", databaseType: "TEXT", tsType: "string", nullable: true, hidden: true },
|
|
734
|
+
},
|
|
735
|
+
};
|
|
736
|
+
alias = name(reference.alias ?? reference.name);
|
|
737
|
+
}
|
|
738
|
+
else if (reference.kind === "subquery") {
|
|
272
739
|
if (reference.lateral)
|
|
273
740
|
this.#unsupported("SQLite does not support LATERAL subqueries", reference.range);
|
|
274
741
|
const result = this.#statement(reference.query, reference.lateral ? scope : scope.outer, ctes);
|
|
@@ -310,7 +777,7 @@ class Resolver {
|
|
|
310
777
|
scope.relations.push(relation);
|
|
311
778
|
return relation;
|
|
312
779
|
}
|
|
313
|
-
#items(items, scope, ctes) {
|
|
780
|
+
#items(items, scope, ctes, requireOutputNames = true) {
|
|
314
781
|
const columns = [];
|
|
315
782
|
const names = new Set();
|
|
316
783
|
const add = (column) => {
|
|
@@ -340,6 +807,8 @@ class Resolver {
|
|
|
340
807
|
for (const column of Object.values(relation.table.columns)) {
|
|
341
808
|
if (star.relation === undefined && scope.usingColumns.has(column.name.toLowerCase()))
|
|
342
809
|
continue;
|
|
810
|
+
if ("hidden" in column && column.hidden === true)
|
|
811
|
+
continue;
|
|
343
812
|
add({ name: column.name, ...this.#columnType(relation, column), range: item.range });
|
|
344
813
|
}
|
|
345
814
|
continue;
|
|
@@ -347,11 +816,12 @@ class Resolver {
|
|
|
347
816
|
const type = this.#expression(item.expression, scope, ctes);
|
|
348
817
|
const output = item.alias === undefined ? this.#outputName(item.expression) : name(item.alias);
|
|
349
818
|
if (output === undefined) {
|
|
350
|
-
if (this.#strict)
|
|
819
|
+
if (this.#strict && requireOutputNames)
|
|
351
820
|
this.#diagnostic("TSQ104", "Expressions in SELECT require an explicit alias", item.range, "Add AS <name>.");
|
|
352
|
-
|
|
821
|
+
if (requireOutputNames)
|
|
822
|
+
continue;
|
|
353
823
|
}
|
|
354
|
-
add({ name: output
|
|
824
|
+
add({ name: output ?? `column${columns.length + 1}`, ...type, range: item.range });
|
|
355
825
|
}
|
|
356
826
|
return columns;
|
|
357
827
|
}
|
|
@@ -359,6 +829,10 @@ class Resolver {
|
|
|
359
829
|
if (expression.kind === "column") {
|
|
360
830
|
if (expression.relation === undefined && expression.column.name === "DEFAULT")
|
|
361
831
|
return { tsType: "unknown", nullable: true };
|
|
832
|
+
if (expression.relation === undefined &&
|
|
833
|
+
!expression.column.quoted &&
|
|
834
|
+
SQLITE_CURRENT_TIME_KEYWORDS.has(expression.column.name.toUpperCase()))
|
|
835
|
+
return { tsType: "string", nullable: false, databaseType: "text" };
|
|
362
836
|
return this.#resolveColumn(expression.relation, expression.column, scope);
|
|
363
837
|
}
|
|
364
838
|
if (expression.kind === "literal") {
|
|
@@ -396,7 +870,7 @@ class Resolver {
|
|
|
396
870
|
return {
|
|
397
871
|
tsType: mapSqliteCastType(expression.databaseType.name, this.#policy),
|
|
398
872
|
nullable: source.nullable,
|
|
399
|
-
databaseType:
|
|
873
|
+
databaseType: normalizeSqliteDatabaseType(expression.databaseType.name),
|
|
400
874
|
};
|
|
401
875
|
}
|
|
402
876
|
if (expression.kind === "unary") {
|
|
@@ -418,7 +892,7 @@ class Resolver {
|
|
|
418
892
|
if (expression.elseExpression !== undefined)
|
|
419
893
|
values.push(this.#expression(expression.elseExpression, scope, ctes));
|
|
420
894
|
return {
|
|
421
|
-
tsType:
|
|
895
|
+
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
422
896
|
nullable: expression.elseExpression === undefined || values.some((value) => value.nullable),
|
|
423
897
|
};
|
|
424
898
|
}
|
|
@@ -442,10 +916,21 @@ class Resolver {
|
|
|
442
916
|
if (expression.kind === "in") {
|
|
443
917
|
const subject = this.#expression(expression.expression, scope, ctes);
|
|
444
918
|
let nullable = subject.nullable;
|
|
445
|
-
if (
|
|
919
|
+
if (!("kind" in expression.values))
|
|
446
920
|
nullable ||= expression.values
|
|
447
921
|
.map((value) => this.#expression(value, scope, ctes, subject))
|
|
448
922
|
.some((value) => value.nullable);
|
|
923
|
+
else if (expression.values.kind !== "select") {
|
|
924
|
+
const tableScope = { relations: [], usingColumns: new Map(), outer: scope };
|
|
925
|
+
const relation = this.#relation(expression.values, false, tableScope, ctes);
|
|
926
|
+
const columns = Object.values(relation?.table.columns ?? {}).filter((column) => !("hidden" in column && column.hidden === true));
|
|
927
|
+
if (columns.length !== 1)
|
|
928
|
+
this.#diagnostic("TSQ217", `IN table expression returns ${columns.length} columns instead of one`, expression.range);
|
|
929
|
+
const column = columns[0];
|
|
930
|
+
if (column !== undefined && expression.expression.kind === "parameter")
|
|
931
|
+
this.#expression(expression.expression, scope, ctes, this.#snapshotType(column));
|
|
932
|
+
nullable ||= column?.nullable ?? true;
|
|
933
|
+
}
|
|
449
934
|
else {
|
|
450
935
|
const result = this.#statement(expression.values, scope, ctes);
|
|
451
936
|
if (result.columns.length !== 1)
|
|
@@ -467,7 +952,7 @@ class Resolver {
|
|
|
467
952
|
};
|
|
468
953
|
}
|
|
469
954
|
#binary(expression, scope, ctes) {
|
|
470
|
-
if (
|
|
955
|
+
if (SQLITE_UNSUPPORTED_OPERATORS.has(expression.operator)) {
|
|
471
956
|
this.#unsupported(`SQLite does not support operator ${expression.operator}`, expression.range);
|
|
472
957
|
}
|
|
473
958
|
let left;
|
|
@@ -480,30 +965,31 @@ class Resolver {
|
|
|
480
965
|
left = this.#expression(expression.left, scope, ctes);
|
|
481
966
|
right = this.#expression(expression.right, scope, ctes, expression.right.kind === "parameter" ? left : undefined);
|
|
482
967
|
}
|
|
483
|
-
|
|
968
|
+
const operator = sqliteOperator(expression.operator);
|
|
969
|
+
if (operator?.result === "json-text" || operator?.result === "json-flexible") {
|
|
970
|
+
const availability = resolveSqliteBuiltin("JSON", 1, this.#schema.server?.versionKey ?? this.#schema.version, this.#schema.server?.features);
|
|
971
|
+
if (!this.#catalogAvailable(availability, `operator ${expression.operator}`, expression.range))
|
|
972
|
+
return { tsType: "unknown", nullable: true };
|
|
973
|
+
}
|
|
974
|
+
if (operator?.result === "comparison")
|
|
484
975
|
return {
|
|
485
976
|
tsType: this.#policy.integer,
|
|
486
|
-
nullable:
|
|
977
|
+
nullable: operator.nullSafe === true ? false : left.nullable || right.nullable,
|
|
487
978
|
databaseType: "integer",
|
|
488
979
|
};
|
|
489
|
-
if (
|
|
980
|
+
if (operator?.result === "text") {
|
|
490
981
|
return { tsType: "string", nullable: left.nullable || right.nullable, databaseType: "text" };
|
|
491
982
|
}
|
|
492
|
-
if (
|
|
983
|
+
if (operator?.result === "json-text")
|
|
493
984
|
return { tsType: "string", nullable: true, databaseType: "text" };
|
|
494
|
-
if (
|
|
985
|
+
if (operator?.result === "json-flexible")
|
|
495
986
|
return {
|
|
496
987
|
tsType: sqliteFlexibleType(this.#policy),
|
|
497
988
|
nullable: true,
|
|
498
989
|
};
|
|
499
|
-
|
|
500
|
-
const rightType = right.databaseType === undefined ? undefined : normalized(right.databaseType);
|
|
501
|
-
if (leftType !== undefined &&
|
|
502
|
-
rightType !== undefined &&
|
|
503
|
-
numericTypes.has(leftType) &&
|
|
504
|
-
numericTypes.has(rightType)) {
|
|
990
|
+
if (operator?.result === "numeric" && sqliteNumericOperands(left.databaseType, right.databaseType)) {
|
|
505
991
|
return {
|
|
506
|
-
tsType:
|
|
992
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number"]),
|
|
507
993
|
nullable: left.nullable || right.nullable,
|
|
508
994
|
databaseType: "numeric",
|
|
509
995
|
};
|
|
@@ -518,63 +1004,408 @@ class Resolver {
|
|
|
518
1004
|
if (expression.filter !== undefined)
|
|
519
1005
|
this.#expression(expression.filter, scope, ctes, this.#databaseType("integer", false));
|
|
520
1006
|
if (expression.over !== undefined && "partitionBy" in expression.over) {
|
|
521
|
-
|
|
1007
|
+
const window = this.#effectiveWindow(expression.over);
|
|
1008
|
+
for (const value of window.partitionBy)
|
|
522
1009
|
this.#expression(value, scope, ctes);
|
|
523
|
-
for (const item of
|
|
1010
|
+
for (const item of window.orderBy)
|
|
524
1011
|
this.#expression(item.expression, scope, ctes);
|
|
1012
|
+
this.#windowFrame(window, scope, ctes);
|
|
1013
|
+
}
|
|
1014
|
+
else if (expression.over !== undefined && !this.#activeWindows.has(name(expression.over))) {
|
|
1015
|
+
this.#diagnostic("TSQ222", `Unknown window ${expression.over.name}`, expression.over.range);
|
|
525
1016
|
}
|
|
526
1017
|
const functionName = expression.name.name.toUpperCase();
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
tsType:
|
|
532
|
-
nullable: true,
|
|
533
|
-
databaseType: "numeric",
|
|
534
|
-
};
|
|
535
|
-
if (functionName === "AVG")
|
|
536
|
-
return { tsType: "number", nullable: true, databaseType: "real" };
|
|
537
|
-
if (["MIN", "MAX"].includes(functionName))
|
|
538
|
-
return { ...(values[0] ?? { tsType: "unknown" }), nullable: true };
|
|
539
|
-
if (functionName === "COALESCE")
|
|
540
|
-
return {
|
|
541
|
-
tsType: unionTypeLiterals(values.map((value) => value.tsType)),
|
|
542
|
-
nullable: values.every((value) => value.nullable),
|
|
543
|
-
};
|
|
544
|
-
if (functionName === "IFNULL" || functionName === "NULLIF") {
|
|
545
|
-
const firstArgument = expression.arguments[0];
|
|
546
|
-
const secondArgument = expression.arguments[1];
|
|
547
|
-
if (firstArgument?.kind === "parameter" && values[1] !== undefined) {
|
|
548
|
-
this.#expression(firstArgument, scope, ctes, values[1]);
|
|
549
|
-
}
|
|
550
|
-
if (secondArgument?.kind === "parameter" && values[0] !== undefined) {
|
|
551
|
-
this.#expression(secondArgument, scope, ctes, values[0]);
|
|
1018
|
+
const routineCandidates = this.#index.routineOverloads(name(expression.name), values.length, expression.schema === undefined ? undefined : name(expression.schema));
|
|
1019
|
+
if (routineCandidates.length > 0) {
|
|
1020
|
+
if (routineCandidates.length > 1) {
|
|
1021
|
+
this.#diagnostic("TSQ204", `Ambiguous function ${expression.name.name}`, expression.range);
|
|
1022
|
+
return { tsType: "unknown", nullable: true };
|
|
552
1023
|
}
|
|
553
|
-
return
|
|
554
|
-
...(values[0] ?? { tsType: "unknown" }),
|
|
555
|
-
nullable: functionName === "NULLIF" || values.every((value) => value.nullable),
|
|
556
|
-
};
|
|
1024
|
+
return this.#applicationRoutine(expression, routineCandidates[0], values, scope, ctes);
|
|
557
1025
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const selected = candidates.length === 1 ? candidates[0] : undefined;
|
|
568
|
-
if (selected !== undefined) {
|
|
1026
|
+
const declaredCandidates = this.#index.functions(name(expression.name), values.length, expression.schema === undefined ? undefined : name(expression.schema));
|
|
1027
|
+
if (declaredCandidates.length > 0) {
|
|
1028
|
+
if (declaredCandidates.length > 1) {
|
|
1029
|
+
this.#diagnostic("TSQ204", `Ambiguous function ${expression.name.name}`, expression.range);
|
|
1030
|
+
return { tsType: "unknown", nullable: true };
|
|
1031
|
+
}
|
|
1032
|
+
if (expression.over !== undefined || expression.filter !== undefined)
|
|
1033
|
+
this.#diagnostic("TSQ227", `Legacy function ${expression.name.name} has no aggregate or window kind evidence`, expression.range);
|
|
1034
|
+
const selected = declaredCandidates[0];
|
|
569
1035
|
expression.arguments.forEach((argument, index) => {
|
|
570
1036
|
if (argument.kind === "parameter")
|
|
571
1037
|
this.#expression(argument, scope, ctes, this.#databaseType(selected.argumentTypes[index], true));
|
|
572
1038
|
});
|
|
573
1039
|
return this.#function(selected);
|
|
574
1040
|
}
|
|
575
|
-
|
|
1041
|
+
const builtinWindow = isSqliteWindowFunction(functionName);
|
|
1042
|
+
if (builtinWindow && expression.over === undefined)
|
|
1043
|
+
this.#diagnostic("TSQ223", `${expression.name.name} requires an OVER clause`, expression.range);
|
|
1044
|
+
if (expression.over !== undefined && (expression.distinct || (builtinWindow && expression.filter !== undefined))) {
|
|
1045
|
+
this.#diagnostic("TSQ223", expression.distinct
|
|
1046
|
+
? "SQLite window functions cannot use DISTINCT"
|
|
1047
|
+
: `SQLite built-in window function ${expression.name.name} cannot use FILTER`, expression.range);
|
|
1048
|
+
}
|
|
1049
|
+
const builtin = resolveSqliteBuiltin(functionName, values.length, this.#schema.server?.versionKey ?? this.#schema.version, this.#schema.server?.features);
|
|
1050
|
+
if (builtin.status === "exact") {
|
|
1051
|
+
if (expression.over !== undefined && builtin.definition.kind === "scalar")
|
|
1052
|
+
this.#diagnostic("TSQ223", `${expression.name.name} is not a window or aggregate function`, expression.range);
|
|
1053
|
+
if (expression.filter !== undefined && builtin.definition.kind === "scalar")
|
|
1054
|
+
this.#diagnostic("TSQ227", `${expression.name.name} cannot use FILTER`, expression.range);
|
|
1055
|
+
if (builtin.definition.result === "coalesce" || builtin.definition.result === "nullif") {
|
|
1056
|
+
const firstArgument = expression.arguments[0];
|
|
1057
|
+
const secondArgument = expression.arguments[1];
|
|
1058
|
+
if (firstArgument?.kind === "parameter" && values[1] !== undefined) {
|
|
1059
|
+
this.#expression(firstArgument, scope, ctes, values[1]);
|
|
1060
|
+
}
|
|
1061
|
+
if (secondArgument?.kind === "parameter" && values[0] !== undefined) {
|
|
1062
|
+
this.#expression(secondArgument, scope, ctes, values[0]);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
this.#builtinParameters(functionName, expression, scope, ctes);
|
|
1066
|
+
this.#dateTimeArguments(functionName, expression);
|
|
1067
|
+
return this.#builtinResult(builtin.definition, values, expression.arguments);
|
|
1068
|
+
}
|
|
1069
|
+
if (builtin.status === "arity") {
|
|
1070
|
+
const ranges = [...new Set(builtin.definitions.map(({ arguments: range }) => this.#arity(range)))].join(" or ");
|
|
1071
|
+
this.#diagnostic(builtinWindow ? "TSQ223" : "TSQ227", `${expression.name.name} expects ${ranges} arguments, received ${values.length}`, expression.range);
|
|
1072
|
+
return { tsType: "unknown", nullable: true };
|
|
1073
|
+
}
|
|
1074
|
+
if (builtin.status === "evidence-required") {
|
|
1075
|
+
this.#diagnostic("TSQ402", `SQLite ${builtin.since} server-version evidence is required to resolve ${expression.name.name}`, expression.range);
|
|
1076
|
+
return { tsType: "unknown", nullable: true };
|
|
1077
|
+
}
|
|
1078
|
+
if (builtin.status === "outside-supported-version") {
|
|
1079
|
+
this.#diagnostic("TSQ403", `SQLite ${builtin.status === "outside-supported-version" ? builtin.version : "version"} is outside the tested built-in catalog range`, expression.range);
|
|
1080
|
+
return { tsType: "unknown", nullable: true };
|
|
1081
|
+
}
|
|
1082
|
+
if (builtin.status === "unavailable") {
|
|
1083
|
+
this.#diagnostic("TSQ404", `${expression.name.name} requires SQLite ${builtin.since} or newer`, expression.range);
|
|
1084
|
+
return { tsType: "unknown", nullable: true };
|
|
1085
|
+
}
|
|
1086
|
+
if (builtin.status === "compile-evidence-required") {
|
|
1087
|
+
this.#diagnostic("TSQ402", `SQLite compile-option evidence is required to resolve ${expression.name.name} (${builtin.option})`, expression.range);
|
|
1088
|
+
return { tsType: "unknown", nullable: true };
|
|
1089
|
+
}
|
|
1090
|
+
if (builtin.status === "compile-option-unavailable") {
|
|
1091
|
+
this.#diagnostic("TSQ406", `${expression.name.name} is unavailable under the recorded SQLite compile options (${builtin.option})`, expression.range);
|
|
1092
|
+
return { tsType: "unknown", nullable: true };
|
|
1093
|
+
}
|
|
1094
|
+
this.#diagnostic("TSQ202", `Unknown function ${expression.name.name}`, expression.range, undefined, "warning");
|
|
1095
|
+
return { tsType: "unknown", nullable: true };
|
|
1096
|
+
}
|
|
1097
|
+
#applicationRoutine(expression, routine, _values, scope, ctes) {
|
|
1098
|
+
const inputs = routine.arguments.filter(({ mode }) => mode !== "out");
|
|
1099
|
+
expression.arguments.forEach((argument, index) => {
|
|
1100
|
+
const expected = inputs[index];
|
|
1101
|
+
if (argument.kind === "parameter" && expected !== undefined)
|
|
1102
|
+
this.#expression(argument, scope, ctes, {
|
|
1103
|
+
tsType: expected.tsType,
|
|
1104
|
+
nullable: true,
|
|
1105
|
+
databaseType: expected.databaseType,
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
if (routine.kind === "window" && expression.over === undefined)
|
|
1109
|
+
this.#diagnostic("TSQ223", `${expression.name.name} requires an OVER clause`, expression.range);
|
|
1110
|
+
if (routine.kind === "function" && expression.over !== undefined)
|
|
1111
|
+
this.#diagnostic("TSQ223", `${expression.name.name} is not a window or aggregate function`, expression.range);
|
|
1112
|
+
if (routine.kind === "function" && expression.filter !== undefined)
|
|
1113
|
+
this.#diagnostic("TSQ227", `${expression.name.name} cannot use FILTER`, expression.range);
|
|
1114
|
+
if (routine.kind === "window" && expression.distinct)
|
|
1115
|
+
this.#diagnostic("TSQ223", "SQLite window functions cannot use DISTINCT", expression.range);
|
|
1116
|
+
const result = routine.result;
|
|
1117
|
+
if (result.kind === "scalar")
|
|
1118
|
+
return { tsType: result.tsType, nullable: result.nullable, databaseType: result.databaseType };
|
|
1119
|
+
this.#diagnostic("TSQ227", `${expression.name.name} does not have a scalar SQL result`, expression.range);
|
|
576
1120
|
return { tsType: "unknown", nullable: true };
|
|
577
1121
|
}
|
|
1122
|
+
#catalogAvailable(resolution, displayName, range) {
|
|
1123
|
+
if (resolution.status === "exact")
|
|
1124
|
+
return true;
|
|
1125
|
+
if (resolution.status === "evidence-required")
|
|
1126
|
+
this.#diagnostic("TSQ402", `SQLite ${resolution.since} server-version evidence is required to resolve ${displayName}`, range);
|
|
1127
|
+
else if (resolution.status === "outside-supported-version")
|
|
1128
|
+
this.#diagnostic("TSQ403", `SQLite ${resolution.version} is outside the tested built-in catalog range`, range);
|
|
1129
|
+
else if (resolution.status === "unavailable")
|
|
1130
|
+
this.#diagnostic("TSQ404", `${displayName} requires SQLite ${resolution.since} or newer`, range);
|
|
1131
|
+
else if (resolution.status === "compile-evidence-required")
|
|
1132
|
+
this.#diagnostic("TSQ402", `SQLite compile-option evidence is required to resolve ${displayName} (${resolution.option})`, range);
|
|
1133
|
+
else if (resolution.status === "compile-option-unavailable")
|
|
1134
|
+
this.#diagnostic("TSQ406", `${displayName} is unavailable under the recorded SQLite compile options (${resolution.option})`, range);
|
|
1135
|
+
else
|
|
1136
|
+
this.#diagnostic(resolution.status === "arity" ? "TSQ227" : "TSQ202", resolution.status === "arity" ? `Invalid invocation of ${displayName}` : `Unknown function ${displayName}`, range);
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
#tableFunctionAvailable(definition, range) {
|
|
1140
|
+
const version = this.#schema.server?.versionKey ?? this.#schema.version;
|
|
1141
|
+
const parsed = version === undefined ? undefined : parseSqliteVersion(version);
|
|
1142
|
+
if (parsed === undefined) {
|
|
1143
|
+
this.#diagnostic("TSQ402", `SQLite ${definition.availableSince} server-version evidence is required to resolve ${definition.name}`, range);
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
const support = sqliteVersionSupport(version);
|
|
1147
|
+
if (support === "below-supported" || support === "newer-than-tested" || support === "prerelease") {
|
|
1148
|
+
this.#diagnostic("TSQ403", `SQLite ${version} is outside the tested table-function catalog range`, range);
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
if (compareSqliteVersions(parsed, parseSqliteVersion(definition.availableSince)) < 0) {
|
|
1152
|
+
this.#diagnostic("TSQ404", `${definition.name} requires SQLite ${definition.availableSince} or newer`, range);
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
const option = definition.requiredCompileOption ?? definition.omittedByCompileOption;
|
|
1156
|
+
const features = this.#schema.server?.features;
|
|
1157
|
+
if (features === undefined) {
|
|
1158
|
+
this.#diagnostic("TSQ402", `SQLite compile-option evidence is required to resolve ${definition.name} (${option})`, range);
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
const has = (candidate) => features.some((feature) => feature === candidate || feature.startsWith(`${candidate}=`));
|
|
1162
|
+
if ((definition.requiredCompileOption !== undefined && !has(definition.requiredCompileOption)) ||
|
|
1163
|
+
(definition.omittedByCompileOption !== undefined && has(definition.omittedByCompileOption))) {
|
|
1164
|
+
this.#diagnostic("TSQ406", `${definition.name} is unavailable under the recorded SQLite compile options (${option})`, range);
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
return true;
|
|
1168
|
+
}
|
|
1169
|
+
#dateTimeArguments(functionName, expression) {
|
|
1170
|
+
if (!["DATE", "DATETIME", "JULIANDAY", "STRFTIME", "TIME", "UNIXEPOCH"].includes(functionName))
|
|
1171
|
+
return;
|
|
1172
|
+
const modifierStart = functionName === "STRFTIME" ? 2 : 1;
|
|
1173
|
+
const argumentsToCheck = [...expression.arguments.slice(modifierStart)];
|
|
1174
|
+
const first = expression.arguments[0];
|
|
1175
|
+
if (first?.kind === "literal" &&
|
|
1176
|
+
typeof first.value === "string" &&
|
|
1177
|
+
["subsec", "subsecond"].includes(first.value.toLowerCase()))
|
|
1178
|
+
argumentsToCheck.push(first);
|
|
1179
|
+
for (const argument of argumentsToCheck) {
|
|
1180
|
+
if (argument.kind !== "literal" || typeof argument.value !== "string")
|
|
1181
|
+
continue;
|
|
1182
|
+
const modifier = argument.value.toLowerCase();
|
|
1183
|
+
if (modifier === "subsec" || modifier === "subsecond")
|
|
1184
|
+
this.#dateTimeVersion("3.42.0", `date/time modifier ${argument.value}`, argument.range);
|
|
1185
|
+
if (modifier === "ceiling" || modifier === "floor")
|
|
1186
|
+
this.#dateTimeVersion("3.46.0", `date/time modifier ${argument.value}`, argument.range);
|
|
1187
|
+
}
|
|
1188
|
+
if (functionName === "STRFTIME") {
|
|
1189
|
+
const format = expression.arguments[0];
|
|
1190
|
+
if (format?.kind === "literal" && typeof format.value === "string" && /%(?:G|g|U|V)/u.test(format.value))
|
|
1191
|
+
this.#dateTimeVersion("3.46.0", "the requested strftime substitution", format.range);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
#builtinParameters(functionName, expression, scope, ctes) {
|
|
1195
|
+
const numeric = {
|
|
1196
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number"]),
|
|
1197
|
+
nullable: true,
|
|
1198
|
+
databaseType: "numeric",
|
|
1199
|
+
};
|
|
1200
|
+
const text = { tsType: "string", nullable: true, databaseType: "text" };
|
|
1201
|
+
const json = { tsType: sqliteFlexibleType(this.#policy), nullable: true, databaseType: "json" };
|
|
1202
|
+
const jsonValue = { tsType: sqliteFlexibleType(this.#policy), nullable: true };
|
|
1203
|
+
const timeValue = {
|
|
1204
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number", "string"]),
|
|
1205
|
+
nullable: true,
|
|
1206
|
+
databaseType: "numeric",
|
|
1207
|
+
};
|
|
1208
|
+
const expect = (index, expected) => {
|
|
1209
|
+
const argument = expression.arguments[index];
|
|
1210
|
+
if (argument?.kind === "parameter")
|
|
1211
|
+
this.#expression(argument, scope, ctes, expected);
|
|
1212
|
+
};
|
|
1213
|
+
if (SQLITE_MATH_FUNCTIONS.some(({ name }) => name === functionName) ||
|
|
1214
|
+
["MEDIAN", "PERCENTILE", "PERCENTILE_CONT", "PERCENTILE_DISC"].includes(functionName)) {
|
|
1215
|
+
expression.arguments.forEach((_, index) => {
|
|
1216
|
+
expect(index, numeric);
|
|
1217
|
+
});
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (functionName === "SOUNDEX") {
|
|
1221
|
+
expect(0, text);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
if (["DATE", "DATETIME", "JULIANDAY", "TIME", "UNIXEPOCH"].includes(functionName)) {
|
|
1225
|
+
expect(0, timeValue);
|
|
1226
|
+
for (let index = 1; index < expression.arguments.length; index += 1)
|
|
1227
|
+
expect(index, text);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (functionName === "STRFTIME") {
|
|
1231
|
+
expect(0, text);
|
|
1232
|
+
expect(1, timeValue);
|
|
1233
|
+
for (let index = 2; index < expression.arguments.length; index += 1)
|
|
1234
|
+
expect(index, text);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (functionName === "TIMEDIFF") {
|
|
1238
|
+
expect(0, timeValue);
|
|
1239
|
+
expect(1, timeValue);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (!functionName.startsWith("JSON"))
|
|
1243
|
+
return;
|
|
1244
|
+
if (["JSON_ARRAY", "JSONB_ARRAY"].includes(functionName)) {
|
|
1245
|
+
expression.arguments.forEach((_, index) => {
|
|
1246
|
+
expect(index, jsonValue);
|
|
1247
|
+
});
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (["JSON_GROUP_ARRAY", "JSONB_GROUP_ARRAY", "JSON_QUOTE"].includes(functionName)) {
|
|
1251
|
+
expect(0, jsonValue);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
if (["JSON_OBJECT", "JSONB_OBJECT"].includes(functionName)) {
|
|
1255
|
+
for (let index = 0; index < expression.arguments.length; index += 2) {
|
|
1256
|
+
expect(index, text);
|
|
1257
|
+
expect(index + 1, jsonValue);
|
|
1258
|
+
}
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (["JSON_GROUP_OBJECT", "JSONB_GROUP_OBJECT"].includes(functionName)) {
|
|
1262
|
+
expect(0, text);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
expect(0, json);
|
|
1266
|
+
if (["JSON_ARRAY_LENGTH", "JSON_EXTRACT", "JSONB_EXTRACT", "JSON_REMOVE", "JSONB_REMOVE", "JSON_TYPE"].includes(functionName)) {
|
|
1267
|
+
for (let index = 1; index < expression.arguments.length; index += 1)
|
|
1268
|
+
expect(index, text);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if ([
|
|
1272
|
+
"JSON_ARRAY_INSERT",
|
|
1273
|
+
"JSON_INSERT",
|
|
1274
|
+
"JSON_REPLACE",
|
|
1275
|
+
"JSON_SET",
|
|
1276
|
+
"JSONB_ARRAY_INSERT",
|
|
1277
|
+
"JSONB_INSERT",
|
|
1278
|
+
"JSONB_REPLACE",
|
|
1279
|
+
"JSONB_SET",
|
|
1280
|
+
].includes(functionName)) {
|
|
1281
|
+
for (let index = 1; index < expression.arguments.length; index += 2)
|
|
1282
|
+
expect(index, text);
|
|
1283
|
+
for (let index = 2; index < expression.arguments.length; index += 2)
|
|
1284
|
+
expect(index, jsonValue);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (functionName === "JSON_VALID")
|
|
1288
|
+
expect(1, numeric);
|
|
1289
|
+
if (functionName === "JSON_PRETTY")
|
|
1290
|
+
expect(1, text);
|
|
1291
|
+
}
|
|
1292
|
+
#dateTimeVersion(minimum, feature, range) {
|
|
1293
|
+
const version = this.#schema.server?.versionKey ?? this.#schema.version;
|
|
1294
|
+
if (version === undefined || parseSqliteVersion(version) === undefined) {
|
|
1295
|
+
this.#diagnostic("TSQ402", `SQLite ${minimum} server-version evidence is required for ${feature}`, range);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (compareSqliteVersions(parseSqliteVersion(version), parseSqliteVersion(minimum)) < 0)
|
|
1299
|
+
this.#diagnostic("TSQ404", `${feature} requires SQLite ${minimum} or newer`, range);
|
|
1300
|
+
}
|
|
1301
|
+
#arity(range) {
|
|
1302
|
+
if (range[0] === range[1])
|
|
1303
|
+
return String(range[0]);
|
|
1304
|
+
if (!Number.isFinite(range[1]))
|
|
1305
|
+
return `at least ${range[0]}`;
|
|
1306
|
+
return `${range[0]} to ${range[1]}`;
|
|
1307
|
+
}
|
|
1308
|
+
#builtinResult(definition, values, arguments_) {
|
|
1309
|
+
const first = values[0] ?? { tsType: "unknown", nullable: true };
|
|
1310
|
+
switch (definition.result) {
|
|
1311
|
+
case "blob":
|
|
1312
|
+
return { tsType: "Uint8Array", nullable: false, databaseType: "blob" };
|
|
1313
|
+
case "blob-nullable":
|
|
1314
|
+
return { tsType: "Uint8Array", nullable: true, databaseType: "blob" };
|
|
1315
|
+
case "coalesce":
|
|
1316
|
+
return {
|
|
1317
|
+
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
1318
|
+
nullable: values.every((value) => value.nullable),
|
|
1319
|
+
};
|
|
1320
|
+
case "concat":
|
|
1321
|
+
return { tsType: "string", nullable: false, databaseType: "text" };
|
|
1322
|
+
case "concat-ws":
|
|
1323
|
+
return { tsType: "string", nullable: first.nullable, databaseType: "text" };
|
|
1324
|
+
case "first-argument":
|
|
1325
|
+
return first;
|
|
1326
|
+
case "first-argument-nullable":
|
|
1327
|
+
return { ...first, nullable: true };
|
|
1328
|
+
case "flexible-nullable":
|
|
1329
|
+
return {
|
|
1330
|
+
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
1331
|
+
nullable: values.some((value) => value.nullable),
|
|
1332
|
+
};
|
|
1333
|
+
case "iif": {
|
|
1334
|
+
const branches = values.filter((_, index) => index % 2 === 1 || (values.length % 2 === 1 && index === values.length - 1));
|
|
1335
|
+
return {
|
|
1336
|
+
tsType: unionSqliteTypes(branches.map((value) => value.tsType)),
|
|
1337
|
+
nullable: values.length % 2 === 0 || branches.some((value) => value.nullable),
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
case "integer":
|
|
1341
|
+
return { tsType: this.#policy.integer, nullable: false, databaseType: "integer" };
|
|
1342
|
+
case "integer-always-nullable":
|
|
1343
|
+
return { tsType: this.#policy.integer, nullable: true, databaseType: "integer" };
|
|
1344
|
+
case "integer-nullable":
|
|
1345
|
+
return {
|
|
1346
|
+
tsType: this.#policy.integer,
|
|
1347
|
+
nullable: values.some((value) => value.nullable),
|
|
1348
|
+
databaseType: "integer",
|
|
1349
|
+
};
|
|
1350
|
+
case "json-extract":
|
|
1351
|
+
return values.length === 2
|
|
1352
|
+
? { tsType: sqliteFlexibleType(this.#policy), nullable: true }
|
|
1353
|
+
: { tsType: "string", nullable: true, databaseType: "text" };
|
|
1354
|
+
case "jsonb-extract":
|
|
1355
|
+
return values.length === 2
|
|
1356
|
+
? { tsType: sqliteFlexibleType(this.#policy), nullable: true }
|
|
1357
|
+
: { tsType: "Uint8Array", nullable: true, databaseType: "blob" };
|
|
1358
|
+
case "lag-lead": {
|
|
1359
|
+
const fallback = values[2];
|
|
1360
|
+
return {
|
|
1361
|
+
...first,
|
|
1362
|
+
tsType: fallback === undefined ? first.tsType : unionSqliteTypes([first.tsType, fallback.tsType]),
|
|
1363
|
+
nullable: first.nullable || fallback === undefined || fallback.nullable,
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
case "nullif":
|
|
1367
|
+
return { ...first, nullable: true };
|
|
1368
|
+
case "numeric-from-arguments":
|
|
1369
|
+
return {
|
|
1370
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number"]),
|
|
1371
|
+
nullable: values.some((value) => value.nullable),
|
|
1372
|
+
databaseType: "numeric",
|
|
1373
|
+
};
|
|
1374
|
+
case "numeric-nullable":
|
|
1375
|
+
return {
|
|
1376
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number"]),
|
|
1377
|
+
nullable: true,
|
|
1378
|
+
databaseType: "numeric",
|
|
1379
|
+
};
|
|
1380
|
+
case "real":
|
|
1381
|
+
return { tsType: "number", nullable: false, databaseType: "real" };
|
|
1382
|
+
case "real-nullable":
|
|
1383
|
+
return { tsType: "number", nullable: true, databaseType: "real" };
|
|
1384
|
+
case "text":
|
|
1385
|
+
return { tsType: "string", nullable: false, databaseType: "text" };
|
|
1386
|
+
case "text-always-nullable":
|
|
1387
|
+
return { tsType: "string", nullable: true, databaseType: "text" };
|
|
1388
|
+
case "text-nullable":
|
|
1389
|
+
return { tsType: "string", nullable: values.some((value) => value.nullable), databaseType: "text" };
|
|
1390
|
+
case "unixepoch": {
|
|
1391
|
+
const dynamic = arguments_.some((argument) => argument.kind !== "literal" || typeof argument.value !== "string");
|
|
1392
|
+
const subsecond = arguments_.some((argument) => argument.kind === "literal" &&
|
|
1393
|
+
typeof argument.value === "string" &&
|
|
1394
|
+
["subsec", "subsecond"].includes(argument.value.toLowerCase()));
|
|
1395
|
+
return {
|
|
1396
|
+
tsType: subsecond
|
|
1397
|
+
? "number"
|
|
1398
|
+
: dynamic
|
|
1399
|
+
? unionSqliteTypes([this.#policy.integer, "number"])
|
|
1400
|
+
: this.#policy.integer,
|
|
1401
|
+
nullable: true,
|
|
1402
|
+
databaseType: subsecond ? "real" : dynamic ? "numeric" : "integer",
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
case "unknown-nullable":
|
|
1406
|
+
return { tsType: "unknown", nullable: true };
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
578
1409
|
#resolveColumn(relationIdentifier, columnIdentifier, scope) {
|
|
579
1410
|
const columnName = name(columnIdentifier);
|
|
580
1411
|
if (relationIdentifier === undefined) {
|
|
@@ -587,7 +1418,11 @@ class Resolver {
|
|
|
587
1418
|
: scope.relations.filter((relation) => relation.alias === name(relationIdentifier));
|
|
588
1419
|
const matches = relations.flatMap((relation) => {
|
|
589
1420
|
const column = this.#column(relation.table, columnName);
|
|
590
|
-
|
|
1421
|
+
if (column !== undefined)
|
|
1422
|
+
return [[relation, this.#columnType(relation, column)]];
|
|
1423
|
+
return this.#hasImplicitRowid(relation.table, columnName)
|
|
1424
|
+
? [[relation, { tsType: this.#policy.integer, nullable: relation.nullable, databaseType: "integer" }]]
|
|
1425
|
+
: [];
|
|
591
1426
|
});
|
|
592
1427
|
if (matches.length > 1) {
|
|
593
1428
|
this.#diagnostic("TSQ102", `Ambiguous column ${columnIdentifier.name}`, columnIdentifier.range);
|
|
@@ -601,13 +1436,23 @@ class Resolver {
|
|
|
601
1436
|
: `Unknown relation or column ${relationIdentifier.name}.${columnIdentifier.name}`, columnIdentifier.range);
|
|
602
1437
|
return { tsType: "unknown", nullable: true };
|
|
603
1438
|
}
|
|
604
|
-
return
|
|
1439
|
+
return matches[0][1];
|
|
605
1440
|
}
|
|
606
1441
|
#column(table, columnName) {
|
|
607
1442
|
return this.#index.column(table, columnName);
|
|
608
1443
|
}
|
|
609
1444
|
#findColumn(table, identifier) {
|
|
610
|
-
const column = table === undefined
|
|
1445
|
+
const column = table === undefined
|
|
1446
|
+
? undefined
|
|
1447
|
+
: (this.#column(table, name(identifier)) ??
|
|
1448
|
+
(this.#hasImplicitRowid(table, name(identifier))
|
|
1449
|
+
? {
|
|
1450
|
+
name: identifier.name,
|
|
1451
|
+
databaseType: "INTEGER",
|
|
1452
|
+
tsType: this.#policy.integer,
|
|
1453
|
+
nullable: false,
|
|
1454
|
+
}
|
|
1455
|
+
: undefined));
|
|
611
1456
|
if (column === undefined)
|
|
612
1457
|
this.#diagnostic("TSQ101", `Unknown column ${identifier.name}`, identifier.range);
|
|
613
1458
|
return column;
|
|
@@ -615,6 +1460,14 @@ class Resolver {
|
|
|
615
1460
|
#columnType(relation, column) {
|
|
616
1461
|
return { tsType: column.tsType, nullable: column.nullable || relation.nullable, databaseType: column.databaseType };
|
|
617
1462
|
}
|
|
1463
|
+
#hasImplicitRowid(table, columnName) {
|
|
1464
|
+
if (!["rowid", "oid", "_rowid_"].includes(columnName.toLowerCase()))
|
|
1465
|
+
return false;
|
|
1466
|
+
if (this.#column(table, columnName) !== undefined)
|
|
1467
|
+
return false;
|
|
1468
|
+
const sqliteTable = table;
|
|
1469
|
+
return sqliteTable.withoutRowid !== true && (sqliteTable.kind === "table" || sqliteTable.kind === "shadow");
|
|
1470
|
+
}
|
|
618
1471
|
#outputName(expression) {
|
|
619
1472
|
if (expression.kind === "column")
|
|
620
1473
|
return name(expression.column);
|
|
@@ -641,11 +1494,22 @@ class Resolver {
|
|
|
641
1494
|
#generated(column) {
|
|
642
1495
|
return "generated" in column && (column.generated === "virtual" || column.generated === "stored");
|
|
643
1496
|
}
|
|
1497
|
+
#insertable(table, column) {
|
|
1498
|
+
const evidence = this.#index.columnEligibility(table, column, "insert");
|
|
1499
|
+
return evidence === "unknown" ? !this.#generated(column) && !this.#hidden(column) : evidence;
|
|
1500
|
+
}
|
|
1501
|
+
#updatable(table, column) {
|
|
1502
|
+
const evidence = this.#index.columnEligibility(table, column, "update");
|
|
1503
|
+
return evidence === "unknown" ? !this.#generated(column) && !this.#hidden(column) : evidence;
|
|
1504
|
+
}
|
|
1505
|
+
#hidden(column) {
|
|
1506
|
+
return "hidden" in column && column.hidden === true;
|
|
1507
|
+
}
|
|
644
1508
|
#databaseType(databaseType, nullable) {
|
|
645
1509
|
return {
|
|
646
1510
|
tsType: mapSqliteCastType(databaseType, this.#policy),
|
|
647
1511
|
nullable,
|
|
648
|
-
databaseType:
|
|
1512
|
+
databaseType: normalizeSqliteDatabaseType(databaseType),
|
|
649
1513
|
};
|
|
650
1514
|
}
|
|
651
1515
|
#recordParameter(index, expected) {
|
|
@@ -658,6 +1522,7 @@ class Resolver {
|
|
|
658
1522
|
this.#diagnostics.push({ code, message, range, severity, ...(suggestion === undefined ? {} : { suggestion }) });
|
|
659
1523
|
}
|
|
660
1524
|
}
|
|
1525
|
+
_a = Resolver;
|
|
661
1526
|
export function resolveSqliteStatement(statement, schema, options = {}) {
|
|
662
1527
|
return new Resolver(schema, options).resolve(statement);
|
|
663
1528
|
}
|