@typed-sql/mysql 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -0
- package/README.md +12 -0
- package/dist/packages/mysql/src/bulk.d.ts.map +1 -1
- package/dist/packages/mysql/src/bulk.js +9 -3
- package/dist/packages/mysql/src/bulk.js.map +1 -1
- package/dist/packages/mysql/src/capabilities.d.ts +30 -0
- package/dist/packages/mysql/src/capabilities.d.ts.map +1 -0
- package/dist/packages/mysql/src/capabilities.js +233 -0
- package/dist/packages/mysql/src/capabilities.js.map +1 -0
- package/dist/packages/mysql/src/catalog/generated/26.7.d.ts +4 -0
- package/dist/packages/mysql/src/catalog/generated/26.7.d.ts.map +1 -0
- package/dist/packages/mysql/src/catalog/generated/26.7.js +3 -0
- package/dist/packages/mysql/src/catalog/generated/26.7.js.map +1 -0
- package/dist/packages/mysql/src/catalog/generated/8.4.d.ts +4 -0
- package/dist/packages/mysql/src/catalog/generated/8.4.d.ts.map +1 -0
- package/dist/packages/mysql/src/catalog/generated/8.4.js +3 -0
- package/dist/packages/mysql/src/catalog/generated/8.4.js.map +1 -0
- package/dist/packages/mysql/src/catalog/generated/9.7.d.ts +4 -0
- package/dist/packages/mysql/src/catalog/generated/9.7.d.ts.map +1 -0
- package/dist/packages/mysql/src/catalog/generated/9.7.js +3 -0
- package/dist/packages/mysql/src/catalog/generated/9.7.js.map +1 -0
- package/dist/packages/mysql/src/catalog/index.d.ts +15 -0
- package/dist/packages/mysql/src/catalog/index.d.ts.map +1 -0
- package/dist/packages/mysql/src/catalog/index.js +129 -0
- package/dist/packages/mysql/src/catalog/index.js.map +1 -0
- package/dist/packages/mysql/src/catalog/types.d.ts +50 -0
- package/dist/packages/mysql/src/catalog/types.d.ts.map +1 -0
- package/dist/packages/mysql/src/catalog/types.js +2 -0
- package/dist/packages/mysql/src/catalog/types.js.map +1 -0
- package/dist/packages/mysql/src/index.d.ts +9 -1
- package/dist/packages/mysql/src/index.d.ts.map +1 -1
- package/dist/packages/mysql/src/index.js +37 -15
- package/dist/packages/mysql/src/index.js.map +1 -1
- package/dist/packages/mysql/src/mysql2.d.ts +9 -1
- package/dist/packages/mysql/src/mysql2.d.ts.map +1 -1
- package/dist/packages/mysql/src/mysql2.js +86 -3
- package/dist/packages/mysql/src/mysql2.js.map +1 -1
- package/dist/packages/mysql/src/parser/index.d.ts +8 -0
- package/dist/packages/mysql/src/parser/index.d.ts.map +1 -0
- package/dist/packages/mysql/src/parser/index.js +4 -0
- package/dist/packages/mysql/src/parser/index.js.map +1 -0
- package/dist/packages/mysql/src/parser/parser.d.ts +14 -0
- package/dist/packages/mysql/src/parser/parser.d.ts.map +1 -0
- package/dist/packages/mysql/src/parser/parser.js +1276 -0
- package/dist/packages/mysql/src/parser/parser.js.map +1 -0
- package/dist/packages/mysql/src/parser/tokenizer.d.ts +18 -0
- package/dist/packages/mysql/src/parser/tokenizer.d.ts.map +1 -0
- package/dist/packages/mysql/src/parser/tokenizer.js +385 -0
- package/dist/packages/mysql/src/parser/tokenizer.js.map +1 -0
- package/dist/packages/mysql/src/parser/types.d.ts +313 -0
- package/dist/packages/mysql/src/parser/types.d.ts.map +1 -0
- package/dist/packages/mysql/src/parser/types.js +9 -0
- package/dist/packages/mysql/src/parser/types.js.map +1 -0
- package/dist/packages/mysql/src/parser/walk.d.ts +13 -0
- package/dist/packages/mysql/src/parser/walk.d.ts.map +1 -0
- package/dist/packages/mysql/src/parser/walk.js +190 -0
- package/dist/packages/mysql/src/parser/walk.js.map +1 -0
- package/dist/packages/mysql/src/prepared.d.ts +6 -3
- package/dist/packages/mysql/src/prepared.d.ts.map +1 -1
- package/dist/packages/mysql/src/prepared.js +15 -14
- package/dist/packages/mysql/src/prepared.js.map +1 -1
- package/dist/packages/mysql/src/provider.d.ts +21 -5
- package/dist/packages/mysql/src/provider.d.ts.map +1 -1
- package/dist/packages/mysql/src/provider.js +647 -44
- package/dist/packages/mysql/src/provider.js.map +1 -1
- package/dist/packages/mysql/src/resolver.d.ts +5 -1
- package/dist/packages/mysql/src/resolver.d.ts.map +1 -1
- package/dist/packages/mysql/src/resolver.js +1207 -95
- package/dist/packages/mysql/src/resolver.js.map +1 -1
- package/dist/packages/mysql/src/routing.d.ts.map +1 -1
- package/dist/packages/mysql/src/routing.js +3 -2
- package/dist/packages/mysql/src/routing.js.map +1 -1
- package/dist/packages/mysql/src/runtime.d.ts +33 -0
- package/dist/packages/mysql/src/runtime.d.ts.map +1 -1
- package/dist/packages/mysql/src/runtime.js +166 -12
- package/dist/packages/mysql/src/runtime.js.map +1 -1
- package/dist/packages/mysql/src/semantics.d.ts +1 -1
- package/dist/packages/mysql/src/semantics.d.ts.map +1 -1
- package/dist/packages/mysql/src/semantics.js +27 -3
- package/dist/packages/mysql/src/semantics.js.map +1 -1
- package/dist/packages/mysql/src/support.d.ts +43 -0
- package/dist/packages/mysql/src/support.d.ts.map +1 -0
- package/dist/packages/mysql/src/support.js +102 -0
- package/dist/packages/mysql/src/support.js.map +1 -0
- package/dist/packages/mysql/src/type-policy.d.ts +2 -0
- package/dist/packages/mysql/src/type-policy.d.ts.map +1 -1
- package/dist/packages/mysql/src/type-policy.js +24 -50
- package/dist/packages/mysql/src/type-policy.js.map +1 -1
- package/package.json +8 -5
|
@@ -1,20 +1,8 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import { ParameterCollector, ResolverSchemaIndex, unionTypeLiterals } from "@typed-sql/core";
|
|
3
|
+
import { mySqlCatalogCollation, mySqlCatalogHasRoutineInAnotherSeries, mySqlCatalogOperator, mySqlCatalogRoutine, mySqlCatalogType, mySqlCoreCatalogForSchema, normalizeMySqlType, } from "./catalog/index.js";
|
|
4
|
+
import { walkStatement } from "./parser/index.js";
|
|
2
5
|
import { defaultMySqlTypePolicy, isKnownMySqlType, mapMySqlType } 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
6
|
const comparisonOperators = new Set([
|
|
19
7
|
"=",
|
|
20
8
|
"!=",
|
|
@@ -23,24 +11,167 @@ const comparisonOperators = new Set([
|
|
|
23
11
|
"<=",
|
|
24
12
|
">",
|
|
25
13
|
">=",
|
|
14
|
+
"<=>",
|
|
26
15
|
"IS",
|
|
27
16
|
"IS NOT",
|
|
28
|
-
"IS DISTINCT FROM",
|
|
29
|
-
"IS NOT DISTINCT FROM",
|
|
30
17
|
"LIKE",
|
|
31
18
|
"NOT LIKE",
|
|
32
19
|
"AND",
|
|
33
20
|
"OR",
|
|
21
|
+
"XOR",
|
|
22
|
+
]);
|
|
23
|
+
const aggregateFunctions = new Set([
|
|
24
|
+
"AVG",
|
|
25
|
+
"COUNT",
|
|
26
|
+
"GROUP_CONCAT",
|
|
27
|
+
"JSON_ARRAYAGG",
|
|
28
|
+
"JSON_OBJECTAGG",
|
|
29
|
+
"MAX",
|
|
30
|
+
"MIN",
|
|
31
|
+
"SUM",
|
|
32
|
+
]);
|
|
33
|
+
const windowAggregateFunctions = new Set(["AVG", "COUNT", "JSON_ARRAYAGG", "JSON_OBJECTAGG", "MAX", "MIN", "SUM"]);
|
|
34
|
+
const windowOnlyFunctions = new Set([
|
|
35
|
+
"CUME_DIST",
|
|
36
|
+
"DENSE_RANK",
|
|
37
|
+
"FIRST_VALUE",
|
|
38
|
+
"LAG",
|
|
39
|
+
"LAST_VALUE",
|
|
40
|
+
"LEAD",
|
|
41
|
+
"NTH_VALUE",
|
|
42
|
+
"NTILE",
|
|
43
|
+
"PERCENT_RANK",
|
|
44
|
+
"RANK",
|
|
45
|
+
"ROW_NUMBER",
|
|
34
46
|
]);
|
|
47
|
+
const windowFunctionArity = new Map([
|
|
48
|
+
["CUME_DIST", [0, 0]],
|
|
49
|
+
["DENSE_RANK", [0, 0]],
|
|
50
|
+
["FIRST_VALUE", [1, 1]],
|
|
51
|
+
["LAG", [1, 3]],
|
|
52
|
+
["LAST_VALUE", [1, 1]],
|
|
53
|
+
["LEAD", [1, 3]],
|
|
54
|
+
["NTH_VALUE", [2, 2]],
|
|
55
|
+
["NTILE", [1, 1]],
|
|
56
|
+
["PERCENT_RANK", [0, 0]],
|
|
57
|
+
["RANK", [0, 0]],
|
|
58
|
+
["ROW_NUMBER", [0, 0]],
|
|
59
|
+
]);
|
|
60
|
+
function containsWindowFunction(expression) {
|
|
61
|
+
if (expression.kind === "call") {
|
|
62
|
+
return expression.over !== undefined || expression.arguments.some(containsWindowFunction);
|
|
63
|
+
}
|
|
64
|
+
if (expression.kind === "array" || expression.kind === "row")
|
|
65
|
+
return expression.elements.some(containsWindowFunction);
|
|
66
|
+
if (expression.kind === "cast" || expression.kind === "unary")
|
|
67
|
+
return containsWindowFunction(expression.expression);
|
|
68
|
+
if (expression.kind === "binary") {
|
|
69
|
+
return containsWindowFunction(expression.left) || containsWindowFunction(expression.right);
|
|
70
|
+
}
|
|
71
|
+
if (expression.kind === "case") {
|
|
72
|
+
return ((expression.operand !== undefined && containsWindowFunction(expression.operand)) ||
|
|
73
|
+
expression.branches.some((branch) => containsWindowFunction(branch.when) || containsWindowFunction(branch.then)) ||
|
|
74
|
+
(expression.elseExpression !== undefined && containsWindowFunction(expression.elseExpression)));
|
|
75
|
+
}
|
|
76
|
+
if (expression.kind === "in") {
|
|
77
|
+
return (containsWindowFunction(expression.expression) ||
|
|
78
|
+
(!("kind" in expression.values) && expression.values.some(containsWindowFunction)));
|
|
79
|
+
}
|
|
80
|
+
if (expression.kind === "between") {
|
|
81
|
+
return (containsWindowFunction(expression.expression) ||
|
|
82
|
+
containsWindowFunction(expression.lower) ||
|
|
83
|
+
containsWindowFunction(expression.upper));
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function containsAggregate(expression) {
|
|
88
|
+
if (expression.kind === "call") {
|
|
89
|
+
return aggregateFunctions.has(expression.name.name.toUpperCase()) || expression.arguments.some(containsAggregate);
|
|
90
|
+
}
|
|
91
|
+
if (expression.kind === "array" || expression.kind === "row")
|
|
92
|
+
return expression.elements.some(containsAggregate);
|
|
93
|
+
if (expression.kind === "cast" || expression.kind === "unary")
|
|
94
|
+
return containsAggregate(expression.expression);
|
|
95
|
+
if (expression.kind === "binary")
|
|
96
|
+
return containsAggregate(expression.left) || containsAggregate(expression.right);
|
|
97
|
+
if (expression.kind === "case") {
|
|
98
|
+
return ((expression.operand !== undefined && containsAggregate(expression.operand)) ||
|
|
99
|
+
expression.branches.some((branch) => containsAggregate(branch.when) || containsAggregate(branch.then)) ||
|
|
100
|
+
(expression.elseExpression !== undefined && containsAggregate(expression.elseExpression)));
|
|
101
|
+
}
|
|
102
|
+
if (expression.kind === "in") {
|
|
103
|
+
return (containsAggregate(expression.expression) ||
|
|
104
|
+
(!("kind" in expression.values) && expression.values.some(containsAggregate)));
|
|
105
|
+
}
|
|
106
|
+
if (expression.kind === "between") {
|
|
107
|
+
return (containsAggregate(expression.expression) ||
|
|
108
|
+
containsAggregate(expression.lower) ||
|
|
109
|
+
containsAggregate(expression.upper));
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function expressionKey(expression) {
|
|
114
|
+
return JSON.stringify(expression, (key, value) => (key === "range" ? undefined : value));
|
|
115
|
+
}
|
|
116
|
+
function unaggregatedColumns(expression, insideAggregate = false) {
|
|
117
|
+
if (expression.kind === "column")
|
|
118
|
+
return insideAggregate ? [] : [expression];
|
|
119
|
+
if (expression.kind === "call") {
|
|
120
|
+
const exempt = aggregateFunctions.has(expression.name.name.toUpperCase()) || expression.name.name.toUpperCase() === "ANY_VALUE";
|
|
121
|
+
return expression.arguments.flatMap((argument) => unaggregatedColumns(argument, insideAggregate || exempt));
|
|
122
|
+
}
|
|
123
|
+
if (expression.kind === "array" || expression.kind === "row") {
|
|
124
|
+
return expression.elements.flatMap((element) => unaggregatedColumns(element, insideAggregate));
|
|
125
|
+
}
|
|
126
|
+
if (expression.kind === "cast" || expression.kind === "unary") {
|
|
127
|
+
return unaggregatedColumns(expression.expression, insideAggregate);
|
|
128
|
+
}
|
|
129
|
+
if (expression.kind === "binary") {
|
|
130
|
+
return [
|
|
131
|
+
...unaggregatedColumns(expression.left, insideAggregate),
|
|
132
|
+
...unaggregatedColumns(expression.right, insideAggregate),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
if (expression.kind === "case") {
|
|
136
|
+
return [
|
|
137
|
+
...(expression.operand === undefined ? [] : unaggregatedColumns(expression.operand, insideAggregate)),
|
|
138
|
+
...expression.branches.flatMap((branch) => [
|
|
139
|
+
...unaggregatedColumns(branch.when, insideAggregate),
|
|
140
|
+
...unaggregatedColumns(branch.then, insideAggregate),
|
|
141
|
+
]),
|
|
142
|
+
...(expression.elseExpression === undefined
|
|
143
|
+
? []
|
|
144
|
+
: unaggregatedColumns(expression.elseExpression, insideAggregate)),
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
if (expression.kind === "in") {
|
|
148
|
+
return [
|
|
149
|
+
...unaggregatedColumns(expression.expression, insideAggregate),
|
|
150
|
+
...("kind" in expression.values
|
|
151
|
+
? []
|
|
152
|
+
: expression.values.flatMap((value) => unaggregatedColumns(value, insideAggregate))),
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
if (expression.kind === "between") {
|
|
156
|
+
return [
|
|
157
|
+
...unaggregatedColumns(expression.expression, insideAggregate),
|
|
158
|
+
...unaggregatedColumns(expression.lower, insideAggregate),
|
|
159
|
+
...unaggregatedColumns(expression.upper, insideAggregate),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
35
164
|
function name(identifier) {
|
|
36
165
|
return identifier.quoted ? identifier.name : identifier.name.toLowerCase();
|
|
37
166
|
}
|
|
38
167
|
function normalized(databaseType) {
|
|
39
|
-
return databaseType
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
168
|
+
return normalizeMySqlType(databaseType);
|
|
169
|
+
}
|
|
170
|
+
function isUnsigned(databaseType) {
|
|
171
|
+
return databaseType !== undefined && /(?:^|\s)unsigned(?:\s|$)/iu.test(databaseType);
|
|
172
|
+
}
|
|
173
|
+
function unionMySqlTypes(types) {
|
|
174
|
+
return unionTypeLiterals([...new Set(types.flatMap((type) => type.split(" | ")))]);
|
|
44
175
|
}
|
|
45
176
|
class Resolver {
|
|
46
177
|
#schema;
|
|
@@ -49,11 +180,17 @@ class Resolver {
|
|
|
49
180
|
#diagnostics = [];
|
|
50
181
|
#parameters = new ParameterCollector();
|
|
51
182
|
#index;
|
|
183
|
+
#sqlModes;
|
|
184
|
+
#activeWindows = new Map();
|
|
185
|
+
#duplicateTarget;
|
|
52
186
|
constructor(schema, options) {
|
|
53
187
|
this.#schema = schema;
|
|
54
188
|
this.#index = ResolverSchemaIndex.for(schema);
|
|
55
189
|
this.#policy = options.typePolicy ?? defaultMySqlTypePolicy;
|
|
56
190
|
this.#strict = options.strictExpressions ?? true;
|
|
191
|
+
this.#sqlModes = new Set(typeof schema.server?.settings.sqlMode === "string"
|
|
192
|
+
? schema.server.settings.sqlMode.split(",").filter(Boolean)
|
|
193
|
+
: []);
|
|
57
194
|
}
|
|
58
195
|
resolve(statement) {
|
|
59
196
|
if (this.#schema.dialect !== "mysql")
|
|
@@ -69,49 +206,130 @@ class Resolver {
|
|
|
69
206
|
const ctes = this.#with(statement.with, outer, inherited);
|
|
70
207
|
if (statement.kind === "select")
|
|
71
208
|
return { columns: this.#select(statement, outer, ctes), resultKind: "rows" };
|
|
72
|
-
const scope = {
|
|
73
|
-
|
|
209
|
+
const scope = {
|
|
210
|
+
relations: [],
|
|
211
|
+
usingColumns: new Map(),
|
|
212
|
+
outputAliases: new Map(),
|
|
213
|
+
...(outer === undefined ? {} : { outer }),
|
|
214
|
+
};
|
|
74
215
|
if (statement.kind === "insert") {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
216
|
+
const target = this.#relation(statement.table, false, scope, ctes);
|
|
217
|
+
const targets = statement.source.kind === "set"
|
|
218
|
+
? statement.source.assignments.map(({ column }) => this.#findColumn(target?.table, column.column))
|
|
219
|
+
: statement.columnList
|
|
220
|
+
? statement.columns.map((column) => this.#findColumn(target?.table, column))
|
|
221
|
+
: Object.values(target?.table.columns ?? {});
|
|
222
|
+
const suppliedIdentifiers = statement.source.kind === "set"
|
|
223
|
+
? statement.source.assignments.map(({ column }) => column.column)
|
|
224
|
+
: statement.columns;
|
|
225
|
+
if (target !== undefined) {
|
|
226
|
+
suppliedIdentifiers.forEach((identifier, index) => {
|
|
227
|
+
const column = targets[index];
|
|
228
|
+
const defaultAssignment = statement.source.kind === "set"
|
|
229
|
+
? this.#isDefault(statement.source.assignments[index]?.value)
|
|
230
|
+
: statement.source.kind === "values"
|
|
231
|
+
? statement.source.rows.every((row) => this.#isDefault(row[index]))
|
|
232
|
+
: false;
|
|
233
|
+
if (column !== undefined &&
|
|
234
|
+
this.#index.columnEligibility(target.table, column, "insert") === false &&
|
|
235
|
+
!defaultAssignment) {
|
|
236
|
+
this.#diagnostic("TSQ218", `Cannot INSERT into non-insertable column ${column.name}`, identifier.range);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
const supplied = new Set(statement.columnList || statement.source.kind === "set"
|
|
240
|
+
? suppliedIdentifiers.map((column) => name(column).toLowerCase())
|
|
241
|
+
: Object.values(target.table.columns).map((column) => column.name.toLowerCase()));
|
|
242
|
+
const required = this.#index.requiredInsertColumns(target.table);
|
|
243
|
+
if (required !== "unknown") {
|
|
244
|
+
for (const column of required) {
|
|
245
|
+
if (!supplied.has(column.name.toLowerCase())) {
|
|
246
|
+
this.#diagnostic("TSQ219", `INSERT omits required column ${column.name}`, statement.table.range);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
78
251
|
if (statement.source.kind === "values") {
|
|
79
252
|
for (const row of statement.source.rows) {
|
|
80
253
|
if (row.length !== targets.length)
|
|
81
254
|
this.#diagnostic("TSQ214", `INSERT has ${targets.length} target columns but ${row.length} values`, statement.source.range);
|
|
82
255
|
row.forEach((value, index) => {
|
|
256
|
+
this.#validateInsertValueReferences(value, index, targets, target);
|
|
83
257
|
this.#expression(value, scope, ctes, this.#snapshotType(targets[index]));
|
|
84
258
|
});
|
|
85
259
|
}
|
|
86
260
|
}
|
|
261
|
+
else if (statement.source.kind === "set") {
|
|
262
|
+
for (const [index, assignment] of statement.source.assignments.entries()) {
|
|
263
|
+
this.#validateInsertValueReferences(assignment.value, index, targets, target);
|
|
264
|
+
this.#expression(assignment.value, scope, ctes, this.#snapshotType(targets[index]));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
87
267
|
else if (statement.source.kind === "select") {
|
|
268
|
+
this.#contextualizeInsertSelect(statement.source, targets);
|
|
88
269
|
const selected = this.#statement(statement.source, outer, ctes);
|
|
89
270
|
if (selected.columns.length !== targets.length)
|
|
90
271
|
this.#diagnostic("TSQ214", `INSERT has ${targets.length} target columns but SELECT returns ${selected.columns.length}`, statement.source.range);
|
|
91
272
|
}
|
|
273
|
+
else {
|
|
274
|
+
this.#unsupported("MySQL does not support INSERT DEFAULT VALUES", statement.source.range);
|
|
275
|
+
}
|
|
276
|
+
if (statement.priority === "delayed") {
|
|
277
|
+
this.#diagnostic("TSQ401", `${statement.operation.toUpperCase()} DELAYED is accepted but ignored by supported MySQL versions`, statement.range, undefined, "warning");
|
|
278
|
+
}
|
|
279
|
+
this.#insertAliases(statement, target, targets, scope);
|
|
280
|
+
const previousDuplicateTarget = this.#duplicateTarget;
|
|
281
|
+
this.#duplicateTarget = target;
|
|
282
|
+
for (const assignment of statement.duplicateKey) {
|
|
283
|
+
const column = this.#assignmentColumn(assignment.column, scope, "update", new Set([target?.alias ?? ""]), assignment.value);
|
|
284
|
+
this.#expression(assignment.value, scope, ctes, this.#snapshotType(column));
|
|
285
|
+
}
|
|
286
|
+
this.#duplicateTarget = previousDuplicateTarget;
|
|
92
287
|
if (statement.returning.length > 0)
|
|
93
288
|
this.#unsupported("MySQL does not support INSERT RETURNING", statement.returning[0].range);
|
|
94
289
|
return { columns: [], resultKind: "command" };
|
|
95
290
|
}
|
|
96
291
|
if (statement.kind === "update") {
|
|
292
|
+
this.#relation(statement.table, false, scope, ctes);
|
|
293
|
+
for (const join of statement.joins)
|
|
294
|
+
this.#join(join, scope, ctes);
|
|
97
295
|
for (const assignment of statement.assignments) {
|
|
98
|
-
const column = this.#
|
|
296
|
+
const column = this.#assignmentColumn(assignment.column, scope, "update", undefined, assignment.value);
|
|
99
297
|
this.#expression(assignment.value, scope, ctes, this.#snapshotType(column));
|
|
100
298
|
}
|
|
101
|
-
if (statement.from !== undefined)
|
|
102
|
-
this.#relation(statement.from, false, scope, ctes);
|
|
103
|
-
for (const join of statement.joins)
|
|
104
|
-
this.#join(join, scope, ctes);
|
|
105
299
|
if (statement.where !== undefined)
|
|
106
300
|
this.#expression(statement.where, scope, ctes, this.#databaseType("boolean", false));
|
|
301
|
+
if (statement.joins.length > 0 && (statement.orderBy.length > 0 || statement.limit !== undefined)) {
|
|
302
|
+
this.#unsupported("MySQL multiple-table UPDATE does not support ORDER BY or LIMIT", statement.orderBy[0]?.range ?? statement.limit.range);
|
|
303
|
+
}
|
|
304
|
+
for (const item of statement.orderBy)
|
|
305
|
+
this.#expression(item.expression, scope, ctes);
|
|
306
|
+
if (statement.limit !== undefined)
|
|
307
|
+
this.#expression(statement.limit, scope, ctes, this.#databaseType("int", false));
|
|
107
308
|
if (statement.returning.length > 0)
|
|
108
309
|
this.#unsupported("MySQL does not support UPDATE RETURNING", statement.returning[0].range);
|
|
109
310
|
return { columns: [], resultKind: "command" };
|
|
110
311
|
}
|
|
111
|
-
|
|
112
|
-
|
|
312
|
+
this.#relation(statement.table, false, scope, ctes);
|
|
313
|
+
for (const join of statement.joins)
|
|
314
|
+
this.#join(join, scope, ctes);
|
|
315
|
+
for (const identifier of statement.targets) {
|
|
316
|
+
const relation = scope.relations.find(({ alias }) => alias === name(identifier));
|
|
317
|
+
if (relation === undefined) {
|
|
318
|
+
this.#diagnostic("TSQ103", `Unknown DELETE target ${identifier.name}`, identifier.range);
|
|
319
|
+
}
|
|
320
|
+
else if (!relation.writable) {
|
|
321
|
+
this.#diagnostic("TSQ218", `DELETE target ${identifier.name} is not an updatable base relation`, identifier.range);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
113
324
|
if (statement.where !== undefined)
|
|
114
325
|
this.#expression(statement.where, scope, ctes, this.#databaseType("boolean", false));
|
|
326
|
+
if (statement.multiTable && (statement.orderBy.length > 0 || statement.limit !== undefined)) {
|
|
327
|
+
this.#unsupported("MySQL multiple-table DELETE does not support ORDER BY or LIMIT", statement.orderBy[0]?.range ?? statement.limit.range);
|
|
328
|
+
}
|
|
329
|
+
for (const item of statement.orderBy)
|
|
330
|
+
this.#expression(item.expression, scope, ctes);
|
|
331
|
+
if (statement.limit !== undefined)
|
|
332
|
+
this.#expression(statement.limit, scope, ctes, this.#databaseType("int", false));
|
|
115
333
|
if (statement.returning.length > 0)
|
|
116
334
|
this.#unsupported("MySQL does not support DELETE RETURNING", statement.returning[0].range);
|
|
117
335
|
return { columns: [], resultKind: "command" };
|
|
@@ -120,13 +338,20 @@ class Resolver {
|
|
|
120
338
|
const ctes = new Map(inherited);
|
|
121
339
|
if (withClause === undefined)
|
|
122
340
|
return ctes;
|
|
123
|
-
if (withClause.recursive)
|
|
124
|
-
this.#unsupported("Recursive CTE inference is not supported safely", withClause.range);
|
|
125
341
|
for (const query of withClause.queries) {
|
|
126
342
|
const key = name(query.name);
|
|
127
343
|
if (ctes.has(key))
|
|
128
344
|
this.#diagnostic("TSQ211", `Duplicate CTE ${query.name.name}`, query.name.range);
|
|
129
|
-
|
|
345
|
+
if (query.statement.kind !== "select") {
|
|
346
|
+
this.#unsupported("MySQL CTE bodies must be SELECT statements", query.statement.range);
|
|
347
|
+
}
|
|
348
|
+
const selfReferences = query.statement.kind === "select" ? this.#selfReferences(query.statement, query.name) : 0;
|
|
349
|
+
if (selfReferences > 0 && !withClause.recursive) {
|
|
350
|
+
this.#diagnostic("TSQ220", `Recursive CTE ${query.name.name} requires WITH RECURSIVE`, query.range);
|
|
351
|
+
}
|
|
352
|
+
const result = query.statement.kind === "select" && selfReferences > 0 && withClause.recursive
|
|
353
|
+
? this.#recursiveCte(query, outer, ctes)
|
|
354
|
+
: this.#statement(query.statement, outer, ctes);
|
|
130
355
|
if (result.resultKind === "command")
|
|
131
356
|
this.#diagnostic("TSQ212", `CTE ${query.name.name} does not return rows`, query.range);
|
|
132
357
|
if (query.columns.length > 0 && query.columns.length !== result.columns.length)
|
|
@@ -145,17 +370,179 @@ class Resolver {
|
|
|
145
370
|
}
|
|
146
371
|
return ctes;
|
|
147
372
|
}
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
373
|
+
#recursiveCte(query, outer, inherited) {
|
|
374
|
+
const arms = this.#selectArms(query.statement);
|
|
375
|
+
const references = arms.map(({ statement }) => ({
|
|
376
|
+
direct: this.#directSelfReferences(statement, query.name),
|
|
377
|
+
total: this.#selfReferences(statement, query.name),
|
|
378
|
+
}));
|
|
379
|
+
const firstRecursive = references.findIndex(({ total }) => total > 0);
|
|
380
|
+
let valid = true;
|
|
381
|
+
const invalid = (message, range) => {
|
|
382
|
+
valid = false;
|
|
383
|
+
this.#diagnostic("TSQ220", message, range);
|
|
384
|
+
};
|
|
385
|
+
if (arms.length < 2)
|
|
386
|
+
invalid(`Recursive CTE ${query.name.name} must be a compound SELECT`, query.range);
|
|
387
|
+
if (firstRecursive <= 0) {
|
|
388
|
+
invalid(`Recursive CTE ${query.name.name} requires a non-recursive SELECT before its recursive member`, query.range);
|
|
389
|
+
}
|
|
390
|
+
for (let index = 0; index < arms.length; index += 1) {
|
|
391
|
+
const arm = arms[index];
|
|
392
|
+
const reference = references[index];
|
|
393
|
+
if (reference.total === 0) {
|
|
394
|
+
if (firstRecursive >= 0 && index > firstRecursive) {
|
|
395
|
+
invalid(`Non-recursive members of ${query.name.name} must precede recursive members`, arm.statement.range);
|
|
396
|
+
}
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (reference.direct !== 1 || reference.total !== 1) {
|
|
400
|
+
invalid(`Each recursive member of ${query.name.name} must reference itself exactly once in its top-level FROM clause`, arm.statement.range);
|
|
401
|
+
}
|
|
402
|
+
if (arm.operator !== "union") {
|
|
403
|
+
invalid(`Recursive members of ${query.name.name} must be joined with UNION or UNION ALL`, arm.range);
|
|
404
|
+
}
|
|
405
|
+
if (this.#hasProhibitedRecursiveConstruct(arm.statement)) {
|
|
406
|
+
valid = false;
|
|
407
|
+
this.#diagnostic("TSQ221", `Recursive member of ${query.name.name} cannot use DISTINCT, grouping, ordering, aggregate, or window functions`, arm.statement.range);
|
|
408
|
+
}
|
|
409
|
+
if (arm.statement.joins.some((join) => join.kind === "left" &&
|
|
410
|
+
join.table.kind === "table" &&
|
|
411
|
+
join.table.schema === undefined &&
|
|
412
|
+
name(join.table.name) === name(query.name))) {
|
|
413
|
+
invalid(`Recursive CTE ${query.name.name} cannot be the right operand of a LEFT JOIN`, arm.statement.range);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const seedColumns = this.#mergeCteSeedColumns(arms.slice(0, Math.max(firstRecursive, 0)).map(({ statement }) => statement), outer, inherited);
|
|
417
|
+
const provisional = {};
|
|
418
|
+
const width = Math.max(query.columns.length, seedColumns.length);
|
|
419
|
+
for (let index = 0; index < width; index += 1) {
|
|
420
|
+
const seed = seedColumns[index];
|
|
421
|
+
const columnName = query.columns[index] === undefined ? seed?.name : name(query.columns[index]);
|
|
422
|
+
if (columnName === undefined)
|
|
423
|
+
continue;
|
|
424
|
+
provisional[columnName] = {
|
|
425
|
+
name: columnName,
|
|
426
|
+
databaseType: seed?.databaseType ?? "unknown",
|
|
427
|
+
tsType: seed?.tsType ?? "unknown",
|
|
428
|
+
nullable: seed?.nullable ?? true,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const recursiveScope = new Map(inherited);
|
|
432
|
+
recursiveScope.set(name(query.name), { name: name(query.name), columns: provisional });
|
|
433
|
+
const analyzed = this.#select(query.statement, outer, recursiveScope);
|
|
434
|
+
const columns = Object.values(provisional).map((column, index) => ({
|
|
435
|
+
name: column.name,
|
|
436
|
+
tsType: column.tsType,
|
|
437
|
+
nullable: true,
|
|
438
|
+
databaseType: column.databaseType,
|
|
439
|
+
range: seedColumns[index]?.range ?? query.range,
|
|
440
|
+
}));
|
|
441
|
+
if (!valid && analyzed.length === 0) {
|
|
442
|
+
return {
|
|
443
|
+
columns: Object.values(provisional).map((column) => ({
|
|
444
|
+
name: column.name,
|
|
445
|
+
tsType: "unknown",
|
|
446
|
+
nullable: true,
|
|
447
|
+
databaseType: "unknown",
|
|
448
|
+
range: query.range,
|
|
449
|
+
})),
|
|
450
|
+
resultKind: "rows",
|
|
451
|
+
};
|
|
151
452
|
}
|
|
152
|
-
|
|
453
|
+
return { columns: columns.length > 0 ? columns : analyzed, resultKind: "rows" };
|
|
454
|
+
}
|
|
455
|
+
#selectArms(statement) {
|
|
456
|
+
const { compounds, ...simple } = statement;
|
|
457
|
+
const arms = [{ statement: { ...simple, compounds: [] }, range: statement.range }];
|
|
458
|
+
for (const compound of compounds) {
|
|
459
|
+
const nested = this.#selectArms(compound.statement);
|
|
460
|
+
const [first, ...rest] = nested;
|
|
461
|
+
if (first !== undefined) {
|
|
462
|
+
arms.push({
|
|
463
|
+
statement: first.statement,
|
|
464
|
+
operator: compound.operator,
|
|
465
|
+
all: compound.all,
|
|
466
|
+
range: compound.range,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
arms.push(...rest);
|
|
470
|
+
}
|
|
471
|
+
return arms;
|
|
472
|
+
}
|
|
473
|
+
#directSelfReferences(statement, identifier) {
|
|
474
|
+
return [statement.from, ...statement.joins.map(({ table }) => table)].filter((reference) => reference?.kind === "table" && reference.schema === undefined && name(reference.name) === name(identifier)).length;
|
|
475
|
+
}
|
|
476
|
+
#selfReferences(statement, identifier) {
|
|
477
|
+
let references = 0;
|
|
478
|
+
walkStatement(statement, {
|
|
479
|
+
table(reference) {
|
|
480
|
+
if (reference.kind === "table" && reference.schema === undefined && name(reference.name) === name(identifier)) {
|
|
481
|
+
references += 1;
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
return references;
|
|
486
|
+
}
|
|
487
|
+
#hasProhibitedRecursiveConstruct(statement) {
|
|
488
|
+
let prohibited = statement.distinct ||
|
|
489
|
+
statement.groupBy.length > 0 ||
|
|
490
|
+
statement.having !== undefined ||
|
|
491
|
+
statement.windows.length > 0 ||
|
|
492
|
+
statement.orderBy.length > 0;
|
|
493
|
+
walkStatement(statement, {
|
|
494
|
+
expression(expression) {
|
|
495
|
+
if (expression.kind === "call" &&
|
|
496
|
+
(expression.over !== undefined || aggregateFunctions.has(expression.name.name.toUpperCase()))) {
|
|
497
|
+
prohibited = true;
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
return prohibited;
|
|
502
|
+
}
|
|
503
|
+
#mergeCteSeedColumns(statements, outer, inherited) {
|
|
504
|
+
const merged = [];
|
|
505
|
+
for (const statement of statements) {
|
|
506
|
+
const probe = new _a(this.#schema, { typePolicy: this.#policy, strictExpressions: false });
|
|
507
|
+
const columns = probe.#select(statement, outer, inherited);
|
|
508
|
+
if (merged.length === 0) {
|
|
509
|
+
merged.push(...columns);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
for (let index = 0; index < Math.min(merged.length, columns.length); index += 1) {
|
|
513
|
+
const current = merged[index];
|
|
514
|
+
const candidate = columns[index];
|
|
515
|
+
merged[index] = {
|
|
516
|
+
name: current.name,
|
|
517
|
+
tsType: unionMySqlTypes([current.tsType, candidate.tsType]),
|
|
518
|
+
nullable: current.nullable || candidate.nullable,
|
|
519
|
+
...(current.databaseType === candidate.databaseType && current.databaseType !== undefined
|
|
520
|
+
? { databaseType: current.databaseType }
|
|
521
|
+
: {}),
|
|
522
|
+
range: current.range,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return merged;
|
|
527
|
+
}
|
|
528
|
+
#select(statement, outer, ctes) {
|
|
529
|
+
const previousWindows = this.#activeWindows;
|
|
530
|
+
this.#activeWindows = this.#windows(statement);
|
|
531
|
+
const scope = {
|
|
532
|
+
relations: [],
|
|
533
|
+
usingColumns: new Map(),
|
|
534
|
+
outputAliases: new Map(),
|
|
535
|
+
...(outer === undefined ? {} : { outer }),
|
|
536
|
+
};
|
|
153
537
|
if (statement.distinctOn.length > 0)
|
|
154
538
|
this.#unsupported("MySQL does not support DISTINCT ON", statement.distinctOn[0].range);
|
|
155
539
|
if (statement.from !== undefined)
|
|
156
540
|
this.#relation(statement.from, false, scope, ctes);
|
|
157
541
|
for (const join of statement.joins)
|
|
158
542
|
this.#join(join, scope, ctes);
|
|
543
|
+
if (statement.compounds.length > 0 && statement.locking.length > 0 && statement.parenthesized !== true) {
|
|
544
|
+
this.#unsupported("A locking clause on a MySQL set-operation arm requires parentheses", statement.locking[0].range);
|
|
545
|
+
}
|
|
159
546
|
const lockingTargets = new Set();
|
|
160
547
|
for (const clause of statement.locking) {
|
|
161
548
|
if (statement.locking.length > 1 && clause.relations.length === 0) {
|
|
@@ -172,25 +559,362 @@ class Resolver {
|
|
|
172
559
|
lockingTargets.add(target);
|
|
173
560
|
}
|
|
174
561
|
}
|
|
562
|
+
const prohibitedWindowExpressions = [
|
|
563
|
+
statement.where,
|
|
564
|
+
statement.having,
|
|
565
|
+
statement.limit,
|
|
566
|
+
statement.offset,
|
|
567
|
+
...statement.groupBy,
|
|
568
|
+
...statement.joins.map(({ on }) => on),
|
|
569
|
+
...statement.windows.flatMap(({ specification }) => [
|
|
570
|
+
...specification.partitionBy,
|
|
571
|
+
...specification.orderBy.map(({ expression }) => expression),
|
|
572
|
+
]),
|
|
573
|
+
].filter((expression) => expression !== undefined);
|
|
574
|
+
for (const expression of prohibitedWindowExpressions) {
|
|
575
|
+
if (containsWindowFunction(expression)) {
|
|
576
|
+
this.#diagnostic("TSQ223", "Window functions are only allowed in SELECT results and ORDER BY", expression.range);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
175
579
|
if (statement.where !== undefined)
|
|
176
580
|
this.#expression(statement.where, scope, ctes, this.#databaseType("boolean", false));
|
|
581
|
+
const columns = statement.queryValues === undefined
|
|
582
|
+
? [...this.#items(statement.columns, scope, ctes)]
|
|
583
|
+
: this.#valuesColumns(statement.queryValues, scope, ctes);
|
|
584
|
+
if (statement.groupRollup === true) {
|
|
585
|
+
const groupingKeys = new Set(statement.groupBy.map((grouping) => {
|
|
586
|
+
if (grouping.kind !== "column" || grouping.relation !== undefined)
|
|
587
|
+
return expressionKey(grouping);
|
|
588
|
+
const aliased = statement.columns.find((item) => item.alias !== undefined && name(item.alias) === name(grouping.column));
|
|
589
|
+
return expressionKey(aliased?.expression ?? grouping);
|
|
590
|
+
}));
|
|
591
|
+
for (const item of statement.columns) {
|
|
592
|
+
if (!groupingKeys.has(expressionKey(item.expression)))
|
|
593
|
+
continue;
|
|
594
|
+
const outputName = item.alias === undefined ? this.#outputName(item.expression) : name(item.alias);
|
|
595
|
+
const index = columns.findIndex((column) => column.name === outputName);
|
|
596
|
+
if (index >= 0)
|
|
597
|
+
columns[index] = { ...columns[index], nullable: true };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
for (const window of statement.windows) {
|
|
601
|
+
const effective = this.#activeWindows.get(name(window.name)) ?? window.specification;
|
|
602
|
+
for (const value of effective.partitionBy)
|
|
603
|
+
this.#expression(value, scope, ctes);
|
|
604
|
+
for (const item of effective.orderBy)
|
|
605
|
+
this.#expression(item.expression, scope, ctes);
|
|
606
|
+
this.#windowFrame(effective, scope, ctes);
|
|
607
|
+
}
|
|
608
|
+
statement.columns.forEach((item) => {
|
|
609
|
+
const alias = item.alias;
|
|
610
|
+
const output = alias === undefined ? undefined : columns.find((column) => column.name === name(alias));
|
|
611
|
+
if (alias !== undefined && output !== undefined) {
|
|
612
|
+
const { name: _name, range: _range, ...type } = output;
|
|
613
|
+
scope.outputAliases.set(name(alias), type);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
177
616
|
for (const value of statement.groupBy)
|
|
178
617
|
this.#expression(value, scope, ctes);
|
|
179
618
|
if (statement.having !== undefined)
|
|
180
619
|
this.#expression(statement.having, scope, ctes, this.#databaseType("boolean", false));
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
620
|
+
this.#validateGroupedQuery(statement, scope);
|
|
621
|
+
const outputNames = new Set(columns.map((column) => column.name));
|
|
622
|
+
for (const item of statement.orderBy) {
|
|
623
|
+
if (statement.compounds.length > 0 || statement.queryValues !== undefined) {
|
|
624
|
+
const validName = item.expression.kind === "column" &&
|
|
625
|
+
item.expression.relation === undefined &&
|
|
626
|
+
outputNames.has(name(item.expression.column));
|
|
627
|
+
const validPosition = item.expression.kind === "literal" &&
|
|
628
|
+
typeof item.expression.value === "number" &&
|
|
629
|
+
Number.isInteger(item.expression.value) &&
|
|
630
|
+
item.expression.value >= 1 &&
|
|
631
|
+
item.expression.value <= columns.length;
|
|
632
|
+
if (!validName && !validPosition) {
|
|
633
|
+
this.#diagnostic("TSQ228", "A compound-query ORDER BY item must name or position a result column", item.range);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else
|
|
185
637
|
this.#expression(item.expression, scope, ctes);
|
|
186
638
|
}
|
|
187
|
-
for (const item of statement.orderBy)
|
|
188
|
-
this.#expression(item.expression, scope, ctes);
|
|
189
639
|
if (statement.limit !== undefined)
|
|
190
640
|
this.#expression(statement.limit, scope, ctes, this.#databaseType("int", false));
|
|
191
641
|
if (statement.offset !== undefined)
|
|
192
642
|
this.#expression(statement.offset, scope, ctes, this.#databaseType("int", false));
|
|
193
|
-
|
|
643
|
+
for (const compound of statement.compounds) {
|
|
644
|
+
const right = this.#select(compound.statement, outer, ctes);
|
|
645
|
+
if (right.length !== columns.length) {
|
|
646
|
+
this.#diagnostic("TSQ214", `Compound SELECT has ${columns.length} columns on the left and ${right.length} on the right`, compound.range);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
650
|
+
const left = columns[index];
|
|
651
|
+
const candidate = right[index];
|
|
652
|
+
columns[index] = {
|
|
653
|
+
name: left.name,
|
|
654
|
+
tsType: unionMySqlTypes([left.tsType, candidate.tsType]),
|
|
655
|
+
nullable: left.nullable || candidate.nullable,
|
|
656
|
+
...(left.databaseType === candidate.databaseType && left.databaseType !== undefined
|
|
657
|
+
? { databaseType: left.databaseType }
|
|
658
|
+
: {}),
|
|
659
|
+
range: left.range,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
this.#activeWindows = previousWindows;
|
|
664
|
+
return columns;
|
|
665
|
+
}
|
|
666
|
+
#valuesColumns(values, scope, ctes) {
|
|
667
|
+
const first = values.rows[0] ?? [];
|
|
668
|
+
const columns = first.map((expression, index) => ({
|
|
669
|
+
name: `column_${index}`,
|
|
670
|
+
...this.#expression(expression, scope, ctes),
|
|
671
|
+
range: expression.range,
|
|
672
|
+
}));
|
|
673
|
+
for (const row of values.rows.slice(1)) {
|
|
674
|
+
if (row.length !== columns.length) {
|
|
675
|
+
this.#diagnostic("TSQ214", `VALUES has ${columns.length} columns in its first row and ${row.length} in a later row`, values.range);
|
|
676
|
+
}
|
|
677
|
+
row.forEach((expression, index) => {
|
|
678
|
+
const candidate = this.#expression(expression, scope, ctes, columns[index]);
|
|
679
|
+
const current = columns[index];
|
|
680
|
+
if (current === undefined)
|
|
681
|
+
return;
|
|
682
|
+
if (expression.kind === "literal" && expression.value === null) {
|
|
683
|
+
columns[index] = { ...current, nullable: true };
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (first[index]?.kind === "literal" && first[index].value === null) {
|
|
687
|
+
columns[index] = { name: current.name, ...candidate, nullable: true, range: current.range };
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
columns[index] = {
|
|
691
|
+
name: current.name,
|
|
692
|
+
tsType: unionMySqlTypes([current.tsType, candidate.tsType]),
|
|
693
|
+
nullable: current.nullable || candidate.nullable,
|
|
694
|
+
...(current.databaseType === candidate.databaseType && current.databaseType !== undefined
|
|
695
|
+
? { databaseType: current.databaseType }
|
|
696
|
+
: {}),
|
|
697
|
+
range: current.range,
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return columns;
|
|
702
|
+
}
|
|
703
|
+
#windows(statement) {
|
|
704
|
+
const raw = new Map();
|
|
705
|
+
for (const window of statement.windows) {
|
|
706
|
+
const key = name(window.name);
|
|
707
|
+
if (raw.has(key)) {
|
|
708
|
+
this.#diagnostic("TSQ222", `Duplicate window ${window.name.name}`, window.name.range);
|
|
709
|
+
}
|
|
710
|
+
else
|
|
711
|
+
raw.set(key, window.specification);
|
|
712
|
+
}
|
|
713
|
+
let inlineWindows = 0;
|
|
714
|
+
walkStatement(statement, {
|
|
715
|
+
expression(expression, owner) {
|
|
716
|
+
if (owner === statement &&
|
|
717
|
+
expression.kind === "call" &&
|
|
718
|
+
expression.over !== undefined &&
|
|
719
|
+
"partitionBy" in expression.over) {
|
|
720
|
+
inlineWindows += 1;
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
if (raw.size + inlineWindows > 127) {
|
|
725
|
+
this.#diagnostic("TSQ222", "MySQL permits at most 127 distinct windows per SELECT", statement.range);
|
|
726
|
+
}
|
|
727
|
+
const resolved = new Map();
|
|
728
|
+
const resolving = new Set();
|
|
729
|
+
const resolve = (key) => {
|
|
730
|
+
const cached = resolved.get(key);
|
|
731
|
+
if (cached !== undefined)
|
|
732
|
+
return cached;
|
|
733
|
+
const specification = raw.get(key);
|
|
734
|
+
if (specification === undefined)
|
|
735
|
+
return undefined;
|
|
736
|
+
if (resolving.has(key)) {
|
|
737
|
+
this.#diagnostic("TSQ222", `Named window ${key} participates in a reference cycle`, specification.range);
|
|
738
|
+
return specification;
|
|
739
|
+
}
|
|
740
|
+
resolving.add(key);
|
|
741
|
+
const effective = this.#effectiveWindow(specification, specification.base === undefined ? undefined : resolve(name(specification.base)));
|
|
742
|
+
resolving.delete(key);
|
|
743
|
+
resolved.set(key, effective);
|
|
744
|
+
return effective;
|
|
745
|
+
};
|
|
746
|
+
for (const key of raw.keys())
|
|
747
|
+
resolve(key);
|
|
748
|
+
return resolved;
|
|
749
|
+
}
|
|
750
|
+
#effectiveWindow(specification, knownBase = specification.base === undefined
|
|
751
|
+
? undefined
|
|
752
|
+
: this.#activeWindows.get(name(specification.base))) {
|
|
753
|
+
if (specification.base === undefined) {
|
|
754
|
+
this.#validateWindowFrame(specification);
|
|
755
|
+
return specification;
|
|
756
|
+
}
|
|
757
|
+
if (knownBase === undefined) {
|
|
758
|
+
this.#diagnostic("TSQ222", `Unknown base window ${specification.base.name}`, specification.base.range);
|
|
759
|
+
this.#validateWindowFrame(specification);
|
|
760
|
+
return specification;
|
|
761
|
+
}
|
|
762
|
+
if (knownBase.partitionBy.length > 0 && specification.partitionBy.length > 0) {
|
|
763
|
+
this.#diagnostic("TSQ222", "A derived window cannot replace PARTITION BY", specification.range);
|
|
764
|
+
}
|
|
765
|
+
if (knownBase.orderBy.length > 0 && specification.orderBy.length > 0) {
|
|
766
|
+
this.#diagnostic("TSQ222", "A derived window cannot replace ORDER BY", specification.range);
|
|
767
|
+
}
|
|
768
|
+
if (knownBase.frame !== undefined) {
|
|
769
|
+
this.#diagnostic("TSQ222", "A framed window cannot be used as a base window", specification.base.range);
|
|
770
|
+
}
|
|
771
|
+
const effective = {
|
|
772
|
+
...specification,
|
|
773
|
+
partitionBy: knownBase.partitionBy.length > 0 ? knownBase.partitionBy : specification.partitionBy,
|
|
774
|
+
orderBy: knownBase.orderBy.length > 0 ? knownBase.orderBy : specification.orderBy,
|
|
775
|
+
};
|
|
776
|
+
this.#validateWindowFrame(effective);
|
|
777
|
+
return effective;
|
|
778
|
+
}
|
|
779
|
+
#validateWindowFrame(specification) {
|
|
780
|
+
const frame = specification.frame;
|
|
781
|
+
if (frame === undefined)
|
|
782
|
+
return;
|
|
783
|
+
if (frame.start.kind === "unbounded-following") {
|
|
784
|
+
this.#diagnostic("TSQ222", "A window frame cannot start with UNBOUNDED FOLLOWING", frame.start.range);
|
|
785
|
+
}
|
|
786
|
+
if (frame.end?.kind === "unbounded-preceding") {
|
|
787
|
+
this.#diagnostic("TSQ222", "A window frame cannot end with UNBOUNDED PRECEDING", frame.end.range);
|
|
788
|
+
}
|
|
789
|
+
if (frame.end === undefined && (frame.start.kind === "following" || frame.start.kind === "unbounded-following")) {
|
|
790
|
+
this.#diagnostic("TSQ222", "A single-bound window frame must end at CURRENT ROW", frame.start.range);
|
|
791
|
+
}
|
|
792
|
+
const ranks = new Map([
|
|
793
|
+
["unbounded-preceding", 0],
|
|
794
|
+
["preceding", 1],
|
|
795
|
+
["current-row", 2],
|
|
796
|
+
["following", 3],
|
|
797
|
+
["unbounded-following", 4],
|
|
798
|
+
]);
|
|
799
|
+
if (frame.end !== undefined && ranks.get(frame.start.kind) > ranks.get(frame.end.kind)) {
|
|
800
|
+
this.#diagnostic("TSQ222", "A window frame start cannot follow its end", frame.range);
|
|
801
|
+
}
|
|
802
|
+
const hasOffset = [frame.start, frame.end].some((boundary) => boundary?.kind === "preceding" || boundary?.kind === "following");
|
|
803
|
+
if (frame.unit === "range" && hasOffset && specification.orderBy.length !== 1) {
|
|
804
|
+
this.#diagnostic("TSQ222", "A RANGE offset frame requires exactly one ORDER BY expression", frame.range);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
#windowFrame(specification, scope, ctes) {
|
|
808
|
+
const frame = specification.frame;
|
|
809
|
+
if (frame === undefined)
|
|
810
|
+
return;
|
|
811
|
+
for (const boundary of [frame.start, frame.end]) {
|
|
812
|
+
if (boundary?.kind !== "preceding" && boundary?.kind !== "following")
|
|
813
|
+
continue;
|
|
814
|
+
if (boundary.expression.kind !== "parameter" &&
|
|
815
|
+
(boundary.expression.kind !== "literal" ||
|
|
816
|
+
typeof boundary.expression.value !== "number" ||
|
|
817
|
+
!Number.isInteger(boundary.expression.value) ||
|
|
818
|
+
boundary.expression.value < 0)) {
|
|
819
|
+
this.#diagnostic("TSQ222", "A MySQL window-frame offset must be a non-negative integer or parameter", boundary.range);
|
|
820
|
+
}
|
|
821
|
+
this.#expression(boundary.expression, scope, ctes, this.#databaseType("int", false));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
#validateGroupedQuery(statement, scope) {
|
|
825
|
+
const sqlMode = this.#schema.server?.settings.sqlMode;
|
|
826
|
+
if (typeof sqlMode !== "string" || !sqlMode.split(",").includes("ONLY_FULL_GROUP_BY"))
|
|
827
|
+
return;
|
|
828
|
+
const clauseExpressions = [
|
|
829
|
+
...statement.columns.map(({ expression }) => expression),
|
|
830
|
+
...(statement.having === undefined ? [] : [statement.having]),
|
|
831
|
+
...statement.orderBy.map(({ expression }) => expression),
|
|
832
|
+
];
|
|
833
|
+
if (statement.groupBy.length === 0 && !clauseExpressions.some(containsAggregate))
|
|
834
|
+
return;
|
|
835
|
+
const groupingExpressions = statement.groupBy.map((grouping) => {
|
|
836
|
+
if (grouping.kind !== "column" || grouping.relation !== undefined)
|
|
837
|
+
return grouping;
|
|
838
|
+
return (statement.columns.find((item) => item.alias !== undefined && name(item.alias) === name(grouping.column))
|
|
839
|
+
?.expression ?? grouping);
|
|
840
|
+
});
|
|
841
|
+
const groupedKeys = new Set(groupingExpressions.map(expressionKey));
|
|
842
|
+
const groupedColumns = new Set(groupingExpressions
|
|
843
|
+
.filter((expression) => expression.kind === "column")
|
|
844
|
+
.map((expression) => this.#columnReferenceKey(expression, scope)));
|
|
845
|
+
const outputAliases = new Map(statement.columns.flatMap((item) => item.alias === undefined ? [] : [[name(item.alias), item.expression]]));
|
|
846
|
+
const singleValueColumns = this.#singleValueColumns(statement.where, scope);
|
|
847
|
+
const diagnosed = new Set();
|
|
848
|
+
for (const expression of clauseExpressions) {
|
|
849
|
+
if (groupedKeys.has(expressionKey(expression)))
|
|
850
|
+
continue;
|
|
851
|
+
for (const column of unaggregatedColumns(expression)) {
|
|
852
|
+
const validate = (candidate, seenAliases = new Set()) => {
|
|
853
|
+
const aliasName = candidate.relation === undefined ? name(candidate.column) : undefined;
|
|
854
|
+
const alias = aliasName === undefined ? undefined : outputAliases.get(aliasName);
|
|
855
|
+
if (aliasName !== undefined && alias !== undefined && !seenAliases.has(aliasName)) {
|
|
856
|
+
seenAliases.add(aliasName);
|
|
857
|
+
for (const nested of unaggregatedColumns(alias))
|
|
858
|
+
validate(nested, seenAliases);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const key = this.#columnReferenceKey(candidate, scope);
|
|
862
|
+
if (groupedColumns.has(key) ||
|
|
863
|
+
singleValueColumns.has(key) ||
|
|
864
|
+
this.#functionallyDependent(candidate, groupedColumns, scope) ||
|
|
865
|
+
diagnosed.has(`${key}:${candidate.range.start}`)) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
diagnosed.add(`${key}:${candidate.range.start}`);
|
|
869
|
+
this.#diagnostic("TSQ228", `Column ${candidate.column.name} is neither grouped nor functionally dependent on grouped columns`, candidate.range);
|
|
870
|
+
};
|
|
871
|
+
validate(column);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
#singleValueColumns(expression, scope) {
|
|
876
|
+
const columns = new Set();
|
|
877
|
+
const visit = (value) => {
|
|
878
|
+
if (value.kind === "binary" && value.operator === "AND") {
|
|
879
|
+
visit(value.left);
|
|
880
|
+
visit(value.right);
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (value.kind !== "binary" || value.operator !== "=")
|
|
884
|
+
return;
|
|
885
|
+
const column = value.left.kind === "column" && this.#isSingleValueExpression(value.right)
|
|
886
|
+
? value.left
|
|
887
|
+
: value.right.kind === "column" && this.#isSingleValueExpression(value.left)
|
|
888
|
+
? value.right
|
|
889
|
+
: undefined;
|
|
890
|
+
if (column !== undefined)
|
|
891
|
+
columns.add(this.#columnReferenceKey(column, scope));
|
|
892
|
+
};
|
|
893
|
+
if (expression !== undefined)
|
|
894
|
+
visit(expression);
|
|
895
|
+
return columns;
|
|
896
|
+
}
|
|
897
|
+
#isSingleValueExpression(expression) {
|
|
898
|
+
if (expression.kind === "literal" || expression.kind === "parameter")
|
|
899
|
+
return true;
|
|
900
|
+
return expression.kind === "cast" && this.#isSingleValueExpression(expression.expression);
|
|
901
|
+
}
|
|
902
|
+
#columnReferenceKey(expression, scope) {
|
|
903
|
+
if (expression.relation !== undefined)
|
|
904
|
+
return `${name(expression.relation)}.${name(expression.column)}`;
|
|
905
|
+
const matches = scope.relations.filter(({ table }) => this.#column(table, name(expression.column)) !== undefined);
|
|
906
|
+
return matches.length === 1 ? `${matches[0].alias}.${name(expression.column)}` : name(expression.column);
|
|
907
|
+
}
|
|
908
|
+
#functionallyDependent(expression, groupedColumns, scope) {
|
|
909
|
+
const relation = expression.relation === undefined
|
|
910
|
+
? scope.relations.filter(({ table }) => this.#column(table, name(expression.column)) !== undefined)[0]
|
|
911
|
+
: scope.relations.find(({ alias }) => alias === name(expression.relation));
|
|
912
|
+
if (relation === undefined)
|
|
913
|
+
return false;
|
|
914
|
+
return this.#index.uniqueColumnSets(relation.table).some((columns) => columns.every((column) => {
|
|
915
|
+
const snapshot = this.#column(relation.table, column);
|
|
916
|
+
return snapshot?.nullable === false && groupedColumns.has(`${relation.alias}.${column.toLowerCase()}`);
|
|
917
|
+
}));
|
|
194
918
|
}
|
|
195
919
|
#join(join, scope, ctes) {
|
|
196
920
|
const previous = [...scope.relations];
|
|
@@ -199,7 +923,7 @@ class Resolver {
|
|
|
199
923
|
if (join.kind === "right" || join.kind === "full")
|
|
200
924
|
for (const relation of previous)
|
|
201
925
|
relation.nullable = true;
|
|
202
|
-
const relation = this.#relation(join.table, join.kind === "left" || join.kind === "full", scope, ctes);
|
|
926
|
+
const relation = this.#relation(join.table, join.kind === "left" || join.kind === "full", scope, ctes, join.kind !== "right");
|
|
203
927
|
if (join.on !== undefined)
|
|
204
928
|
this.#expression(join.on, scope, ctes, this.#databaseType("boolean", false));
|
|
205
929
|
if (join.using === undefined || relation === undefined)
|
|
@@ -227,11 +951,12 @@ class Resolver {
|
|
|
227
951
|
});
|
|
228
952
|
}
|
|
229
953
|
}
|
|
230
|
-
#relation(reference, nullable, scope, ctes) {
|
|
954
|
+
#relation(reference, nullable, scope, ctes, allowLateralScope = true) {
|
|
231
955
|
let table;
|
|
232
956
|
let alias;
|
|
957
|
+
let writable = false;
|
|
233
958
|
if (reference.kind === "subquery") {
|
|
234
|
-
const result = this.#statement(reference.query, reference.lateral ? scope : scope.outer, ctes);
|
|
959
|
+
const result = this.#statement(reference.query, reference.lateral && allowLateralScope ? scope : scope.outer, ctes);
|
|
235
960
|
const columns = {};
|
|
236
961
|
for (const column of result.columns)
|
|
237
962
|
columns[column.name] = {
|
|
@@ -259,6 +984,7 @@ class Resolver {
|
|
|
259
984
|
return undefined;
|
|
260
985
|
}
|
|
261
986
|
table = matches[0].table;
|
|
987
|
+
writable = true;
|
|
262
988
|
}
|
|
263
989
|
alias = reference.alias === undefined ? requested : name(reference.alias);
|
|
264
990
|
}
|
|
@@ -266,7 +992,7 @@ class Resolver {
|
|
|
266
992
|
this.#diagnostic("TSQ108", `Duplicate relation alias ${alias}`, reference.range);
|
|
267
993
|
return undefined;
|
|
268
994
|
}
|
|
269
|
-
const relation = { alias, table, nullable };
|
|
995
|
+
const relation = { alias, table, writable, nullable };
|
|
270
996
|
scope.relations.push(relation);
|
|
271
997
|
return relation;
|
|
272
998
|
}
|
|
@@ -323,16 +1049,36 @@ class Resolver {
|
|
|
323
1049
|
}
|
|
324
1050
|
if (expression.kind === "literal") {
|
|
325
1051
|
if (expression.value === null)
|
|
326
|
-
return { tsType: "unknown", nullable: true };
|
|
1052
|
+
return { tsType: "unknown", nullable: true, coercibility: 6 };
|
|
327
1053
|
if (typeof expression.value === "boolean")
|
|
328
|
-
return { tsType: "boolean", nullable: false, databaseType: "boolean" };
|
|
329
|
-
if (typeof expression.value === "number")
|
|
1054
|
+
return { tsType: "boolean", nullable: false, databaseType: "boolean", coercibility: 5 };
|
|
1055
|
+
if (typeof expression.value === "number") {
|
|
1056
|
+
const databaseType = Number.isInteger(expression.value) ? "int" : "decimal";
|
|
330
1057
|
return {
|
|
331
|
-
tsType: "number",
|
|
1058
|
+
tsType: databaseType === "decimal" ? this.#policy.decimal : "number",
|
|
332
1059
|
nullable: false,
|
|
333
|
-
databaseType
|
|
1060
|
+
databaseType,
|
|
1061
|
+
unsigned: false,
|
|
1062
|
+
coercibility: 5,
|
|
334
1063
|
};
|
|
335
|
-
|
|
1064
|
+
}
|
|
1065
|
+
const characterSet = expression.characterSet?.name.slice(1).toLowerCase();
|
|
1066
|
+
const connectionCharacterSet = this.#schema.server?.settings.characterSetConnection;
|
|
1067
|
+
const connectionCollation = this.#schema.server?.settings.collationConnection;
|
|
1068
|
+
return {
|
|
1069
|
+
tsType: "string",
|
|
1070
|
+
nullable: false,
|
|
1071
|
+
databaseType: characterSet === "binary" ? "varbinary" : "varchar",
|
|
1072
|
+
...(typeof (characterSet ?? connectionCharacterSet) === "string"
|
|
1073
|
+
? { characterSet: String(characterSet ?? connectionCharacterSet) }
|
|
1074
|
+
: {}),
|
|
1075
|
+
...(characterSet === "binary"
|
|
1076
|
+
? { collation: "binary" }
|
|
1077
|
+
: characterSet === undefined && typeof connectionCollation === "string"
|
|
1078
|
+
? { collation: connectionCollation }
|
|
1079
|
+
: {}),
|
|
1080
|
+
coercibility: 4,
|
|
1081
|
+
};
|
|
336
1082
|
}
|
|
337
1083
|
if (expression.kind === "parameter")
|
|
338
1084
|
return this.#recordParameter(expression.index, expected);
|
|
@@ -357,13 +1103,41 @@ class Resolver {
|
|
|
357
1103
|
tsType: mapMySqlType(expression.databaseType.name, this.#policy, this.#schema),
|
|
358
1104
|
nullable: source.nullable,
|
|
359
1105
|
databaseType: normalized(expression.databaseType.name),
|
|
1106
|
+
unsigned: isUnsigned(expression.databaseType.name),
|
|
1107
|
+
...(mySqlCatalogType(expression.databaseType.name, this.#schema)?.category === "string"
|
|
1108
|
+
? this.#connectionCollation(2)
|
|
1109
|
+
: {}),
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
if (expression.kind === "collate") {
|
|
1113
|
+
const source = this.#expression(expression.expression, scope, ctes);
|
|
1114
|
+
const collation = mySqlCatalogCollation(name(expression.collation), this.#schema);
|
|
1115
|
+
if (collation === undefined) {
|
|
1116
|
+
this.#diagnostic("TSQ106", `Unknown MySQL collation ${expression.collation.name}`, expression.collation.range);
|
|
1117
|
+
return { tsType: "unknown", nullable: true };
|
|
1118
|
+
}
|
|
1119
|
+
if (!this.#collatable(source)) {
|
|
1120
|
+
this.#diagnostic("TSQ203", `COLLATE requires a character string, received ${source.databaseType ?? source.tsType}`, expression.range);
|
|
1121
|
+
return { tsType: "unknown", nullable: true };
|
|
1122
|
+
}
|
|
1123
|
+
if (source.characterSet !== undefined && source.characterSet !== collation.characterSet) {
|
|
1124
|
+
this.#diagnostic("TSQ203", `Collation ${collation.name} is not valid for character set ${source.characterSet}`, expression.collation.range);
|
|
1125
|
+
return { tsType: "unknown", nullable: true };
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
...source,
|
|
1129
|
+
characterSet: collation.characterSet,
|
|
1130
|
+
collation: collation.name,
|
|
1131
|
+
coercibility: 0,
|
|
360
1132
|
};
|
|
361
1133
|
}
|
|
362
1134
|
if (expression.kind === "unary") {
|
|
363
1135
|
const operand = this.#expression(expression.expression, scope, ctes);
|
|
364
1136
|
return expression.operator === "NOT"
|
|
365
1137
|
? { tsType: "boolean", nullable: operand.nullable, databaseType: "boolean" }
|
|
366
|
-
:
|
|
1138
|
+
: expression.operator === "-"
|
|
1139
|
+
? { ...operand, unsigned: false }
|
|
1140
|
+
: operand;
|
|
367
1141
|
}
|
|
368
1142
|
if (expression.kind === "binary")
|
|
369
1143
|
return this.#binary(expression, scope, ctes);
|
|
@@ -433,27 +1207,62 @@ class Resolver {
|
|
|
433
1207
|
left = this.#expression(expression.left, scope, ctes);
|
|
434
1208
|
right = this.#expression(expression.right, scope, ctes, expression.right.kind === "parameter" ? left : undefined);
|
|
435
1209
|
}
|
|
436
|
-
|
|
1210
|
+
const operator = mySqlCatalogOperator(expression.operator, this.#schema);
|
|
1211
|
+
if (comparisonOperators.has(expression.operator)) {
|
|
1212
|
+
if (this.#collatable(left) && this.#collatable(right))
|
|
1213
|
+
this.#resolveCollation(left, right, expression.range);
|
|
437
1214
|
return {
|
|
438
1215
|
tsType: "boolean",
|
|
439
|
-
nullable: expression.operator.startsWith("IS")
|
|
1216
|
+
nullable: expression.operator.startsWith("IS") || expression.operator === "<=>"
|
|
1217
|
+
? false
|
|
1218
|
+
: left.nullable || right.nullable,
|
|
440
1219
|
databaseType: "boolean",
|
|
441
1220
|
};
|
|
1221
|
+
}
|
|
442
1222
|
if (expression.operator === "->")
|
|
443
1223
|
return { tsType: this.#policy.json, nullable: true, databaseType: "json" };
|
|
444
1224
|
if (expression.operator === "->>")
|
|
445
|
-
return { tsType: "string", nullable: true, databaseType: "varchar" };
|
|
446
|
-
|
|
447
|
-
|
|
1225
|
+
return { tsType: "string", nullable: true, databaseType: "varchar", ...this.#connectionCollation(2) };
|
|
1226
|
+
if (expression.operator === "||") {
|
|
1227
|
+
const collation = this.#resolveCollation(left, right, expression.range);
|
|
1228
|
+
return this.#collatable(left) && this.#collatable(right)
|
|
1229
|
+
? {
|
|
1230
|
+
tsType: "string",
|
|
1231
|
+
nullable: left.nullable || right.nullable,
|
|
1232
|
+
databaseType: "varchar",
|
|
1233
|
+
...(collation ?? {}),
|
|
1234
|
+
}
|
|
1235
|
+
: { tsType: "unknown", nullable: left.nullable || right.nullable };
|
|
1236
|
+
}
|
|
1237
|
+
const leftType = left.databaseType === undefined ? undefined : mySqlCatalogType(left.databaseType, this.#schema);
|
|
1238
|
+
const rightType = right.databaseType === undefined ? undefined : mySqlCatalogType(right.databaseType, this.#schema);
|
|
448
1239
|
if (leftType !== undefined &&
|
|
449
1240
|
rightType !== undefined &&
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
1241
|
+
leftType.category.startsWith("numeric-") &&
|
|
1242
|
+
rightType.category.startsWith("numeric-") &&
|
|
1243
|
+
operator?.result === "numeric") {
|
|
1244
|
+
const approximate = leftType.category === "numeric-approximate" || rightType.category === "numeric-approximate";
|
|
1245
|
+
const decimal = !approximate &&
|
|
1246
|
+
(leftType.category === "numeric-decimal" ||
|
|
1247
|
+
rightType.category === "numeric-decimal" ||
|
|
1248
|
+
expression.operator === "/");
|
|
1249
|
+
const unsigned = !approximate &&
|
|
1250
|
+
!decimal &&
|
|
1251
|
+
(left.unsigned === true || right.unsigned === true) &&
|
|
1252
|
+
!(expression.operator === "-" && this.#sqlModes.has("NO_UNSIGNED_SUBTRACTION"));
|
|
453
1253
|
return {
|
|
454
|
-
tsType: decimal ? this.#policy.decimal :
|
|
1254
|
+
tsType: approximate ? "number" : decimal ? this.#policy.decimal : this.#policy.bigint,
|
|
1255
|
+
nullable: left.nullable || right.nullable || expression.operator === "/",
|
|
1256
|
+
databaseType: approximate ? "double" : decimal ? "decimal" : `bigint${unsigned ? " unsigned" : ""}`,
|
|
1257
|
+
unsigned,
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
if (operator?.result === "bitwise") {
|
|
1261
|
+
return {
|
|
1262
|
+
tsType: this.#policy.bigint,
|
|
455
1263
|
nullable: left.nullable || right.nullable,
|
|
456
|
-
databaseType:
|
|
1264
|
+
databaseType: "bigint unsigned",
|
|
1265
|
+
unsigned: true,
|
|
457
1266
|
};
|
|
458
1267
|
}
|
|
459
1268
|
if (left.tsType === "unknown" || right.tsType === "unknown")
|
|
@@ -462,36 +1271,78 @@ class Resolver {
|
|
|
462
1271
|
return { tsType: "unknown", nullable: true };
|
|
463
1272
|
}
|
|
464
1273
|
#call(expression, scope, ctes) {
|
|
1274
|
+
const functionName = expression.name.name.toUpperCase();
|
|
1275
|
+
if (functionName === "VALUES") {
|
|
1276
|
+
const argument = expression.arguments[0];
|
|
1277
|
+
if (this.#duplicateTarget === undefined ||
|
|
1278
|
+
expression.arguments.length !== 1 ||
|
|
1279
|
+
argument?.kind !== "column" ||
|
|
1280
|
+
argument.relation !== undefined) {
|
|
1281
|
+
this.#diagnostic("TSQ224", "VALUES(column) is valid only inside ON DUPLICATE KEY UPDATE", expression.range);
|
|
1282
|
+
return { tsType: "unknown", nullable: true };
|
|
1283
|
+
}
|
|
1284
|
+
const column = this.#column(this.#duplicateTarget.table, name(argument.column));
|
|
1285
|
+
if (column === undefined) {
|
|
1286
|
+
this.#diagnostic("TSQ101", `Unknown inserted column ${argument.column.name}`, argument.range);
|
|
1287
|
+
return { tsType: "unknown", nullable: true };
|
|
1288
|
+
}
|
|
1289
|
+
this.#diagnostic("TSQ401", "VALUES(column) in ON DUPLICATE KEY UPDATE is deprecated; use an inserted-row alias", expression.range, undefined, "warning");
|
|
1290
|
+
return this.#snapshotType(column);
|
|
1291
|
+
}
|
|
1292
|
+
if (expression.arguments.some(containsWindowFunction)) {
|
|
1293
|
+
this.#diagnostic("TSQ223", "MySQL does not support nested window functions", expression.range);
|
|
1294
|
+
}
|
|
465
1295
|
const values = expression.arguments.map((argument) => this.#expression(argument, scope, ctes));
|
|
466
1296
|
if (expression.filter !== undefined)
|
|
467
1297
|
this.#unsupported("MySQL does not support aggregate FILTER", expression.filter.range);
|
|
468
1298
|
if (expression.over !== undefined && "partitionBy" in expression.over) {
|
|
469
|
-
|
|
1299
|
+
const window = this.#effectiveWindow(expression.over);
|
|
1300
|
+
for (const value of window.partitionBy)
|
|
470
1301
|
this.#expression(value, scope, ctes);
|
|
471
|
-
for (const item of
|
|
1302
|
+
for (const item of window.orderBy)
|
|
472
1303
|
this.#expression(item.expression, scope, ctes);
|
|
1304
|
+
this.#windowFrame(window, scope, ctes);
|
|
1305
|
+
}
|
|
1306
|
+
else if (expression.over !== undefined && !this.#activeWindows.has(name(expression.over))) {
|
|
1307
|
+
this.#diagnostic("TSQ222", `Unknown window ${expression.over.name}`, expression.over.range);
|
|
1308
|
+
}
|
|
1309
|
+
if (expression.over !== undefined && expression.distinct) {
|
|
1310
|
+
this.#diagnostic("TSQ223", "MySQL does not support DISTINCT aggregate window functions", expression.range);
|
|
1311
|
+
}
|
|
1312
|
+
if (windowOnlyFunctions.has(functionName) && expression.over === undefined) {
|
|
1313
|
+
this.#diagnostic("TSQ223", `${functionName} requires an OVER clause`, expression.range);
|
|
1314
|
+
}
|
|
1315
|
+
if (expression.over !== undefined &&
|
|
1316
|
+
!windowOnlyFunctions.has(functionName) &&
|
|
1317
|
+
!windowAggregateFunctions.has(functionName)) {
|
|
1318
|
+
this.#diagnostic("TSQ223", `${functionName} is not a MySQL window or aggregate function`, expression.range);
|
|
1319
|
+
}
|
|
1320
|
+
const expectedWindowArity = windowFunctionArity.get(functionName);
|
|
1321
|
+
if (expectedWindowArity !== undefined &&
|
|
1322
|
+
(values.length < expectedWindowArity[0] || values.length > expectedWindowArity[1])) {
|
|
1323
|
+
this.#diagnostic("TSQ227", `${functionName} has an invalid argument count`, expression.range);
|
|
1324
|
+
}
|
|
1325
|
+
const builtin = expression.schema === undefined ? mySqlCatalogRoutine(functionName, this.#schema) : undefined;
|
|
1326
|
+
if (builtin !== undefined) {
|
|
1327
|
+
if (values.length < builtin.minimumArguments ||
|
|
1328
|
+
(builtin.maximumArguments !== null && values.length > builtin.maximumArguments)) {
|
|
1329
|
+
this.#diagnostic("TSQ227", `${functionName} has an invalid argument count`, expression.range);
|
|
1330
|
+
}
|
|
1331
|
+
const edition = this.#schema.server?.settings.edition;
|
|
1332
|
+
if (typeof edition === "string" && edition !== "unknown" && !builtin.editions.includes(edition)) {
|
|
1333
|
+
this.#diagnostic("TSQ403", `${functionName} is not available in the ${edition} MySQL edition`, expression.range);
|
|
1334
|
+
return { tsType: "unknown", nullable: true };
|
|
1335
|
+
}
|
|
1336
|
+
return this.#builtinResult(builtin.result, functionName, values, expression.range);
|
|
1337
|
+
}
|
|
1338
|
+
if (mySqlCoreCatalogForSchema(this.#schema) !== undefined &&
|
|
1339
|
+
mySqlCatalogHasRoutineInAnotherSeries(functionName, this.#schema)) {
|
|
1340
|
+
this.#diagnostic("TSQ403", `${functionName} is not available in MySQL ${this.#schema.server?.versionKey ?? this.#schema.version}`, expression.range);
|
|
1341
|
+
return { tsType: "unknown", nullable: true };
|
|
1342
|
+
}
|
|
1343
|
+
if (functionName === "GROUPING") {
|
|
1344
|
+
return { tsType: this.#policy.bigint, nullable: false, databaseType: "bigint unsigned" };
|
|
473
1345
|
}
|
|
474
|
-
const functionName = expression.name.name.toUpperCase();
|
|
475
|
-
if (functionName === "COUNT")
|
|
476
|
-
return { tsType: this.#policy.bigint, nullable: false, databaseType: "bigint" };
|
|
477
|
-
if (["SUM", "AVG"].includes(functionName))
|
|
478
|
-
return { tsType: this.#policy.decimal, nullable: true, databaseType: "decimal" };
|
|
479
|
-
if (["MIN", "MAX"].includes(functionName))
|
|
480
|
-
return { ...(values[0] ?? { tsType: "unknown" }), nullable: true };
|
|
481
|
-
if (functionName === "COALESCE")
|
|
482
|
-
return {
|
|
483
|
-
tsType: unionTypeLiterals(values.map((value) => value.tsType)),
|
|
484
|
-
nullable: values.every((value) => value.nullable),
|
|
485
|
-
};
|
|
486
|
-
if (functionName === "IFNULL" || functionName === "NULLIF")
|
|
487
|
-
return {
|
|
488
|
-
...(values[0] ?? { tsType: "unknown" }),
|
|
489
|
-
nullable: functionName === "NULLIF" || values.every((value) => value.nullable),
|
|
490
|
-
};
|
|
491
|
-
if (functionName === "GROUP_CONCAT")
|
|
492
|
-
return { tsType: "string", nullable: true, databaseType: "text" };
|
|
493
|
-
if (["JSON_ARRAYAGG", "JSON_OBJECTAGG", "JSON_EXTRACT"].includes(functionName))
|
|
494
|
-
return { tsType: this.#policy.json, nullable: true, databaseType: "json" };
|
|
495
1346
|
const candidates = this.#index.functions(name(expression.name), values.length, expression.schema === undefined ? undefined : name(expression.schema));
|
|
496
1347
|
const selected = candidates.length === 1 ? candidates[0] : undefined;
|
|
497
1348
|
if (selected !== undefined) {
|
|
@@ -510,6 +1361,9 @@ class Resolver {
|
|
|
510
1361
|
const using = scope.usingColumns.get(columnName);
|
|
511
1362
|
if (using !== undefined)
|
|
512
1363
|
return using;
|
|
1364
|
+
const output = scope.outputAliases.get(columnName);
|
|
1365
|
+
if (output !== undefined)
|
|
1366
|
+
return output;
|
|
513
1367
|
}
|
|
514
1368
|
const relations = relationIdentifier === undefined
|
|
515
1369
|
? scope.relations
|
|
@@ -532,6 +1386,122 @@ class Resolver {
|
|
|
532
1386
|
}
|
|
533
1387
|
return this.#columnType(matches[0][0], matches[0][1]);
|
|
534
1388
|
}
|
|
1389
|
+
#isDefault(expression) {
|
|
1390
|
+
return (expression?.kind === "column" &&
|
|
1391
|
+
expression.relation === undefined &&
|
|
1392
|
+
expression.column.quoted === false &&
|
|
1393
|
+
expression.column.name === "DEFAULT");
|
|
1394
|
+
}
|
|
1395
|
+
#validateInsertValueReferences(expression, position, targets, target) {
|
|
1396
|
+
if (target === undefined)
|
|
1397
|
+
return;
|
|
1398
|
+
for (const reference of unaggregatedColumns(expression)) {
|
|
1399
|
+
if (reference.relation !== undefined && name(reference.relation) !== target.alias)
|
|
1400
|
+
continue;
|
|
1401
|
+
const referenced = targets.findIndex((column) => column !== undefined && column.name.toLowerCase() === name(reference.column));
|
|
1402
|
+
if (referenced > position) {
|
|
1403
|
+
this.#diagnostic("TSQ228", `INSERT value cannot reference later target column ${reference.column.name}`, reference.range);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
#contextualizeInsertSelect(statement, targets) {
|
|
1408
|
+
statement.columns.forEach((item, index) => {
|
|
1409
|
+
this.#contextualizeInsertExpression(item.expression, this.#snapshotType(targets[index]), statement);
|
|
1410
|
+
});
|
|
1411
|
+
for (const compound of statement.compounds)
|
|
1412
|
+
this.#contextualizeInsertSelect(compound.statement, targets);
|
|
1413
|
+
}
|
|
1414
|
+
#contextualizeInsertExpression(expression, expected, statement) {
|
|
1415
|
+
if (expression.kind === "parameter") {
|
|
1416
|
+
this.#recordParameter(expression.index, expected);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
if (expression.kind === "case") {
|
|
1420
|
+
for (const branch of expression.branches)
|
|
1421
|
+
this.#contextualizeInsertExpression(branch.then, expected, statement);
|
|
1422
|
+
if (expression.elseExpression !== undefined) {
|
|
1423
|
+
this.#contextualizeInsertExpression(expression.elseExpression, expected, statement);
|
|
1424
|
+
}
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (expression.kind !== "column" || statement.with === undefined)
|
|
1428
|
+
return;
|
|
1429
|
+
const source = statement.from;
|
|
1430
|
+
if (source?.kind !== "table" || source.schema !== undefined)
|
|
1431
|
+
return;
|
|
1432
|
+
if (expression.relation !== undefined &&
|
|
1433
|
+
name(expression.relation) !== (source.alias === undefined ? name(source.name) : name(source.alias))) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const query = statement.with.queries.find(({ name: queryName }) => name(queryName) === name(source.name));
|
|
1437
|
+
if (query?.statement.kind !== "select")
|
|
1438
|
+
return;
|
|
1439
|
+
const outputIndex = query.statement.columns.findIndex((item) => {
|
|
1440
|
+
const output = item.alias === undefined ? this.#outputName(item.expression) : name(item.alias);
|
|
1441
|
+
return output === name(expression.column);
|
|
1442
|
+
});
|
|
1443
|
+
if (outputIndex < 0)
|
|
1444
|
+
return;
|
|
1445
|
+
this.#contextualizeInsertExpression(query.statement.columns[outputIndex].expression, expected, query.statement);
|
|
1446
|
+
}
|
|
1447
|
+
#insertAliases(statement, target, targets, scope) {
|
|
1448
|
+
if (statement.rowAlias === undefined || target === undefined)
|
|
1449
|
+
return;
|
|
1450
|
+
const alias = name(statement.rowAlias);
|
|
1451
|
+
if (alias === target.alias || alias === name(statement.table.name)) {
|
|
1452
|
+
this.#diagnostic("TSQ224", "An inserted-row alias must differ from the target table name", statement.rowAlias.range);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
if (statement.columnAliases.length > 0 && statement.columnAliases.length !== targets.length) {
|
|
1456
|
+
this.#diagnostic("TSQ214", `Inserted-row alias has ${statement.columnAliases.length} columns for ${targets.length} inserted values`, statement.rowAlias.range);
|
|
1457
|
+
}
|
|
1458
|
+
const aliasNames = statement.columnAliases.map(name);
|
|
1459
|
+
if (new Set(aliasNames).size !== aliasNames.length) {
|
|
1460
|
+
this.#diagnostic("TSQ224", "Inserted-row column aliases must be unique", statement.rowAlias.range);
|
|
1461
|
+
}
|
|
1462
|
+
const columns = {};
|
|
1463
|
+
targets.forEach((column, index) => {
|
|
1464
|
+
if (column === undefined)
|
|
1465
|
+
return;
|
|
1466
|
+
const columnName = aliasNames[index] ?? column.name;
|
|
1467
|
+
columns[columnName] = { ...column, name: columnName };
|
|
1468
|
+
});
|
|
1469
|
+
scope.relations.push({ alias, table: { name: alias, columns }, writable: false, nullable: false });
|
|
1470
|
+
}
|
|
1471
|
+
#assignmentColumn(expression, scope, operation, allowedAliases, value) {
|
|
1472
|
+
const columnName = name(expression.column);
|
|
1473
|
+
const matched = expression.relation === undefined
|
|
1474
|
+
? scope.relations.filter((relation) => this.#column(relation.table, columnName) !== undefined)
|
|
1475
|
+
: scope.relations.filter(({ alias }) => alias === name(expression.relation));
|
|
1476
|
+
const candidates = expression.relation === undefined && allowedAliases !== undefined
|
|
1477
|
+
? matched.filter(({ alias }) => allowedAliases.has(alias))
|
|
1478
|
+
: matched;
|
|
1479
|
+
if (candidates.length === 0) {
|
|
1480
|
+
this.#diagnostic(expression.relation === undefined ? "TSQ101" : "TSQ103", expression.relation === undefined
|
|
1481
|
+
? `Unknown assignment column ${expression.column.name}`
|
|
1482
|
+
: `Unknown assignment target ${expression.relation.name}.${expression.column.name}`, expression.range);
|
|
1483
|
+
return undefined;
|
|
1484
|
+
}
|
|
1485
|
+
if (candidates.length > 1) {
|
|
1486
|
+
this.#diagnostic("TSQ102", `Ambiguous assignment column ${expression.column.name}`, expression.range);
|
|
1487
|
+
return undefined;
|
|
1488
|
+
}
|
|
1489
|
+
const relation = candidates[0];
|
|
1490
|
+
const column = this.#column(relation.table, columnName);
|
|
1491
|
+
if (column === undefined) {
|
|
1492
|
+
this.#diagnostic("TSQ101", `Unknown assignment column ${expression.column.name}`, expression.range);
|
|
1493
|
+
return undefined;
|
|
1494
|
+
}
|
|
1495
|
+
const permittedAlias = allowedAliases === undefined || allowedAliases.has(relation.alias);
|
|
1496
|
+
const evidence = this.#index.columnEvidence(relation.table, column);
|
|
1497
|
+
const generatedDefault = evidence !== undefined && evidence.generated !== "none" && this.#isDefault(value);
|
|
1498
|
+
if (!permittedAlias ||
|
|
1499
|
+
!relation.writable ||
|
|
1500
|
+
(this.#index.columnEligibility(relation.table, column, operation) === false && !generatedDefault)) {
|
|
1501
|
+
this.#diagnostic("TSQ218", `Cannot UPDATE non-updatable column ${column.name}`, expression.range);
|
|
1502
|
+
}
|
|
1503
|
+
return column;
|
|
1504
|
+
}
|
|
535
1505
|
#column(table, columnName) {
|
|
536
1506
|
return this.#index.column(table, columnName);
|
|
537
1507
|
}
|
|
@@ -542,7 +1512,16 @@ class Resolver {
|
|
|
542
1512
|
return column;
|
|
543
1513
|
}
|
|
544
1514
|
#columnType(relation, column) {
|
|
545
|
-
|
|
1515
|
+
const evidence = this.#index.columnEvidence(relation.table, column);
|
|
1516
|
+
return {
|
|
1517
|
+
tsType: column.tsType,
|
|
1518
|
+
nullable: column.nullable || relation.nullable,
|
|
1519
|
+
databaseType: column.databaseType,
|
|
1520
|
+
unsigned: isUnsigned(column.databaseType),
|
|
1521
|
+
...(evidence?.characterSet === undefined ? {} : { characterSet: evidence.characterSet }),
|
|
1522
|
+
...(evidence?.collation === undefined ? {} : { collation: evidence.collation }),
|
|
1523
|
+
...(evidence?.characterSet === undefined ? {} : { coercibility: 2 }),
|
|
1524
|
+
};
|
|
546
1525
|
}
|
|
547
1526
|
#outputName(expression) {
|
|
548
1527
|
if (expression.kind === "column")
|
|
@@ -562,20 +1541,152 @@ class Resolver {
|
|
|
562
1541
|
...(value.databaseReturnType === undefined ? {} : { databaseType: value.databaseReturnType }),
|
|
563
1542
|
};
|
|
564
1543
|
}
|
|
1544
|
+
#builtinResult(rule, functionName, values, range) {
|
|
1545
|
+
const argumentNullable = values.some((value) => value.nullable);
|
|
1546
|
+
if (rule === "count")
|
|
1547
|
+
return { tsType: this.#policy.bigint, nullable: false, databaseType: "bigint", unsigned: false };
|
|
1548
|
+
if (rule === "bigint")
|
|
1549
|
+
return { tsType: this.#policy.bigint, nullable: false, databaseType: "bigint", unsigned: false };
|
|
1550
|
+
if (rule === "integer" || rule === "coercibility")
|
|
1551
|
+
return {
|
|
1552
|
+
tsType: "number",
|
|
1553
|
+
nullable: rule === "integer" && argumentNullable,
|
|
1554
|
+
databaseType: "bigint",
|
|
1555
|
+
unsigned: true,
|
|
1556
|
+
};
|
|
1557
|
+
if (rule === "double")
|
|
1558
|
+
return { tsType: "number", nullable: argumentNullable, databaseType: "double" };
|
|
1559
|
+
if (rule === "decimal-aggregate")
|
|
1560
|
+
return { tsType: this.#policy.decimal, nullable: true, databaseType: "decimal", unsigned: false };
|
|
1561
|
+
if (rule === "extrema")
|
|
1562
|
+
return { ...(values[0] ?? { tsType: "unknown" }), nullable: true };
|
|
1563
|
+
if (rule === "first-argument") {
|
|
1564
|
+
return {
|
|
1565
|
+
...(values[0] ?? { tsType: "unknown" }),
|
|
1566
|
+
nullable: functionName === "NULLIF" || (functionName === "IFNULL" && values.every((value) => value.nullable)),
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
if (rule === "value-window")
|
|
1570
|
+
return { ...(values[0] ?? { tsType: "unknown" }), nullable: true };
|
|
1571
|
+
if (rule === "coalesce") {
|
|
1572
|
+
const collation = values.reduce((current, value) => current === undefined ? value : (this.#resolveCollation(current, value, range) ?? current), undefined);
|
|
1573
|
+
return {
|
|
1574
|
+
tsType: unionTypeLiterals(values.map((value) => value.tsType)),
|
|
1575
|
+
nullable: values.every((value) => value.nullable),
|
|
1576
|
+
...(collation ?? {}),
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
if (rule === "concat") {
|
|
1580
|
+
const collation = values.reduce((current, value) => current === undefined ? value : (this.#resolveCollation(current, value, range) ?? current), undefined);
|
|
1581
|
+
return {
|
|
1582
|
+
tsType: "string",
|
|
1583
|
+
nullable: argumentNullable,
|
|
1584
|
+
databaseType: "text",
|
|
1585
|
+
...(collation ?? this.#connectionCollation(1)),
|
|
1586
|
+
coercibility: values.length > 1 ? 1 : (collation?.coercibility ?? 1),
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
if (rule === "string" || rule === "collation-name") {
|
|
1590
|
+
const systemConstant = ["CURRENT_USER", "DATABASE", "USER", "UUID", "VERSION"].includes(functionName);
|
|
1591
|
+
return {
|
|
1592
|
+
tsType: "string",
|
|
1593
|
+
nullable: argumentNullable,
|
|
1594
|
+
databaseType: "varchar",
|
|
1595
|
+
...this.#connectionCollation(systemConstant ? 3 : 2),
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
if (rule === "bytes" || rule === "vector") {
|
|
1599
|
+
return { tsType: "Uint8Array", nullable: argumentNullable, databaseType: rule === "vector" ? "vector" : "blob" };
|
|
1600
|
+
}
|
|
1601
|
+
if (rule === "date") {
|
|
1602
|
+
return { tsType: this.#policy.date, nullable: false, databaseType: "datetime", coercibility: 5 };
|
|
1603
|
+
}
|
|
1604
|
+
return { tsType: this.#policy.json, nullable: true, databaseType: "json" };
|
|
1605
|
+
}
|
|
1606
|
+
#connectionCollation(coercibility) {
|
|
1607
|
+
const characterSet = this.#schema.server?.settings.characterSetConnection;
|
|
1608
|
+
const collation = this.#schema.server?.settings.collationConnection;
|
|
1609
|
+
return {
|
|
1610
|
+
...(typeof characterSet === "string" ? { characterSet } : {}),
|
|
1611
|
+
...(typeof collation === "string" ? { collation } : {}),
|
|
1612
|
+
coercibility,
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
#collatable(value) {
|
|
1616
|
+
if (value.characterSet !== undefined)
|
|
1617
|
+
return true;
|
|
1618
|
+
const category = value.databaseType === undefined ? undefined : mySqlCatalogType(value.databaseType, this.#schema)?.category;
|
|
1619
|
+
return category === "string" || category === "collection";
|
|
1620
|
+
}
|
|
1621
|
+
#resolveCollation(left, right, range) {
|
|
1622
|
+
const leftCollation = left.collation === undefined ? undefined : mySqlCatalogCollation(left.collation, this.#schema);
|
|
1623
|
+
const rightCollation = right.collation === undefined ? undefined : mySqlCatalogCollation(right.collation, this.#schema);
|
|
1624
|
+
if (leftCollation === undefined)
|
|
1625
|
+
return rightCollation === undefined ? undefined : this.#collationResult(rightCollation, right);
|
|
1626
|
+
if (rightCollation === undefined)
|
|
1627
|
+
return this.#collationResult(leftCollation, left);
|
|
1628
|
+
if (leftCollation.name === rightCollation.name) {
|
|
1629
|
+
return this.#collationResult(leftCollation, (left.coercibility ?? 6) <= (right.coercibility ?? 6) ? left : right);
|
|
1630
|
+
}
|
|
1631
|
+
const leftCoercibility = left.coercibility ?? 6;
|
|
1632
|
+
const rightCoercibility = right.coercibility ?? 6;
|
|
1633
|
+
if (leftCoercibility < rightCoercibility)
|
|
1634
|
+
return this.#collationResult(leftCollation, left);
|
|
1635
|
+
if (rightCoercibility < leftCoercibility)
|
|
1636
|
+
return this.#collationResult(rightCollation, right);
|
|
1637
|
+
if (leftCollation.unicode !== rightCollation.unicode) {
|
|
1638
|
+
const selected = leftCollation.unicode ? leftCollation : rightCollation;
|
|
1639
|
+
return this.#collationResult(selected, { coercibility: leftCoercibility });
|
|
1640
|
+
}
|
|
1641
|
+
if (leftCollation.characterSet === rightCollation.characterSet && leftCollation.binary !== rightCollation.binary) {
|
|
1642
|
+
const selected = leftCollation.binary ? leftCollation : rightCollation;
|
|
1643
|
+
return this.#collationResult(selected, { coercibility: leftCoercibility });
|
|
1644
|
+
}
|
|
1645
|
+
this.#diagnostic("TSQ203", `Cannot combine collations ${leftCollation.name} and ${rightCollation.name} at equal coercibility`, range);
|
|
1646
|
+
return undefined;
|
|
1647
|
+
}
|
|
1648
|
+
#collationResult(collation, source) {
|
|
1649
|
+
return {
|
|
1650
|
+
characterSet: collation.characterSet,
|
|
1651
|
+
collation: collation.name,
|
|
1652
|
+
coercibility: source.coercibility ?? 6,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
565
1655
|
#snapshotType(column) {
|
|
566
1656
|
if (column === undefined)
|
|
567
1657
|
return undefined;
|
|
568
|
-
return {
|
|
1658
|
+
return {
|
|
1659
|
+
tsType: column.tsType,
|
|
1660
|
+
nullable: column.nullable,
|
|
1661
|
+
databaseType: column.databaseType,
|
|
1662
|
+
unsigned: isUnsigned(column.databaseType),
|
|
1663
|
+
};
|
|
569
1664
|
}
|
|
570
1665
|
#databaseType(databaseType, nullable) {
|
|
571
1666
|
return {
|
|
572
1667
|
tsType: mapMySqlType(databaseType, this.#policy, this.#schema),
|
|
573
1668
|
nullable,
|
|
574
1669
|
databaseType: normalized(databaseType),
|
|
1670
|
+
unsigned: isUnsigned(databaseType),
|
|
1671
|
+
...(mySqlCatalogType(databaseType, this.#schema)?.category === "string" ? this.#connectionCollation(2) : {}),
|
|
575
1672
|
};
|
|
576
1673
|
}
|
|
577
1674
|
#recordParameter(index, expected) {
|
|
578
|
-
|
|
1675
|
+
const parameterExpected = expected === undefined
|
|
1676
|
+
? undefined
|
|
1677
|
+
: {
|
|
1678
|
+
tsType: expected.tsType,
|
|
1679
|
+
nullable: expected.nullable,
|
|
1680
|
+
...(expected.databaseType === undefined ? {} : { databaseType: expected.databaseType }),
|
|
1681
|
+
};
|
|
1682
|
+
const parameter = this.#parameters.record(index, parameterExpected);
|
|
1683
|
+
return {
|
|
1684
|
+
...parameter,
|
|
1685
|
+
...(expected?.characterSet === undefined ? {} : { characterSet: expected.characterSet }),
|
|
1686
|
+
...(expected?.collation === undefined ? {} : { collation: expected.collation }),
|
|
1687
|
+
...(expected?.coercibility === undefined ? {} : { coercibility: expected.coercibility }),
|
|
1688
|
+
...(expected?.unsigned === undefined ? {} : { unsigned: expected.unsigned }),
|
|
1689
|
+
};
|
|
579
1690
|
}
|
|
580
1691
|
#unsupported(message, range) {
|
|
581
1692
|
this.#diagnostic("TSQ401", message, range);
|
|
@@ -584,6 +1695,7 @@ class Resolver {
|
|
|
584
1695
|
this.#diagnostics.push({ code, message, range, severity, ...(suggestion === undefined ? {} : { suggestion }) });
|
|
585
1696
|
}
|
|
586
1697
|
}
|
|
1698
|
+
_a = Resolver;
|
|
587
1699
|
export function resolveMySqlStatement(statement, schema, options = {}) {
|
|
588
1700
|
return new Resolver(schema, options).resolve(statement);
|
|
589
1701
|
}
|