@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +12 -0
  3. package/dist/packages/mysql/src/bulk.d.ts.map +1 -1
  4. package/dist/packages/mysql/src/bulk.js +9 -3
  5. package/dist/packages/mysql/src/bulk.js.map +1 -1
  6. package/dist/packages/mysql/src/capabilities.d.ts +30 -0
  7. package/dist/packages/mysql/src/capabilities.d.ts.map +1 -0
  8. package/dist/packages/mysql/src/capabilities.js +233 -0
  9. package/dist/packages/mysql/src/capabilities.js.map +1 -0
  10. package/dist/packages/mysql/src/catalog/generated/26.7.d.ts +4 -0
  11. package/dist/packages/mysql/src/catalog/generated/26.7.d.ts.map +1 -0
  12. package/dist/packages/mysql/src/catalog/generated/26.7.js +3 -0
  13. package/dist/packages/mysql/src/catalog/generated/26.7.js.map +1 -0
  14. package/dist/packages/mysql/src/catalog/generated/8.4.d.ts +4 -0
  15. package/dist/packages/mysql/src/catalog/generated/8.4.d.ts.map +1 -0
  16. package/dist/packages/mysql/src/catalog/generated/8.4.js +3 -0
  17. package/dist/packages/mysql/src/catalog/generated/8.4.js.map +1 -0
  18. package/dist/packages/mysql/src/catalog/generated/9.7.d.ts +4 -0
  19. package/dist/packages/mysql/src/catalog/generated/9.7.d.ts.map +1 -0
  20. package/dist/packages/mysql/src/catalog/generated/9.7.js +3 -0
  21. package/dist/packages/mysql/src/catalog/generated/9.7.js.map +1 -0
  22. package/dist/packages/mysql/src/catalog/index.d.ts +15 -0
  23. package/dist/packages/mysql/src/catalog/index.d.ts.map +1 -0
  24. package/dist/packages/mysql/src/catalog/index.js +129 -0
  25. package/dist/packages/mysql/src/catalog/index.js.map +1 -0
  26. package/dist/packages/mysql/src/catalog/types.d.ts +50 -0
  27. package/dist/packages/mysql/src/catalog/types.d.ts.map +1 -0
  28. package/dist/packages/mysql/src/catalog/types.js +2 -0
  29. package/dist/packages/mysql/src/catalog/types.js.map +1 -0
  30. package/dist/packages/mysql/src/index.d.ts +9 -1
  31. package/dist/packages/mysql/src/index.d.ts.map +1 -1
  32. package/dist/packages/mysql/src/index.js +37 -15
  33. package/dist/packages/mysql/src/index.js.map +1 -1
  34. package/dist/packages/mysql/src/mysql2.d.ts +9 -1
  35. package/dist/packages/mysql/src/mysql2.d.ts.map +1 -1
  36. package/dist/packages/mysql/src/mysql2.js +86 -3
  37. package/dist/packages/mysql/src/mysql2.js.map +1 -1
  38. package/dist/packages/mysql/src/parser/index.d.ts +8 -0
  39. package/dist/packages/mysql/src/parser/index.d.ts.map +1 -0
  40. package/dist/packages/mysql/src/parser/index.js +4 -0
  41. package/dist/packages/mysql/src/parser/index.js.map +1 -0
  42. package/dist/packages/mysql/src/parser/parser.d.ts +14 -0
  43. package/dist/packages/mysql/src/parser/parser.d.ts.map +1 -0
  44. package/dist/packages/mysql/src/parser/parser.js +1276 -0
  45. package/dist/packages/mysql/src/parser/parser.js.map +1 -0
  46. package/dist/packages/mysql/src/parser/tokenizer.d.ts +18 -0
  47. package/dist/packages/mysql/src/parser/tokenizer.d.ts.map +1 -0
  48. package/dist/packages/mysql/src/parser/tokenizer.js +385 -0
  49. package/dist/packages/mysql/src/parser/tokenizer.js.map +1 -0
  50. package/dist/packages/mysql/src/parser/types.d.ts +313 -0
  51. package/dist/packages/mysql/src/parser/types.d.ts.map +1 -0
  52. package/dist/packages/mysql/src/parser/types.js +9 -0
  53. package/dist/packages/mysql/src/parser/types.js.map +1 -0
  54. package/dist/packages/mysql/src/parser/walk.d.ts +13 -0
  55. package/dist/packages/mysql/src/parser/walk.d.ts.map +1 -0
  56. package/dist/packages/mysql/src/parser/walk.js +190 -0
  57. package/dist/packages/mysql/src/parser/walk.js.map +1 -0
  58. package/dist/packages/mysql/src/prepared.d.ts +6 -3
  59. package/dist/packages/mysql/src/prepared.d.ts.map +1 -1
  60. package/dist/packages/mysql/src/prepared.js +15 -14
  61. package/dist/packages/mysql/src/prepared.js.map +1 -1
  62. package/dist/packages/mysql/src/provider.d.ts +21 -5
  63. package/dist/packages/mysql/src/provider.d.ts.map +1 -1
  64. package/dist/packages/mysql/src/provider.js +647 -44
  65. package/dist/packages/mysql/src/provider.js.map +1 -1
  66. package/dist/packages/mysql/src/resolver.d.ts +5 -1
  67. package/dist/packages/mysql/src/resolver.d.ts.map +1 -1
  68. package/dist/packages/mysql/src/resolver.js +1207 -95
  69. package/dist/packages/mysql/src/resolver.js.map +1 -1
  70. package/dist/packages/mysql/src/routing.d.ts.map +1 -1
  71. package/dist/packages/mysql/src/routing.js +3 -2
  72. package/dist/packages/mysql/src/routing.js.map +1 -1
  73. package/dist/packages/mysql/src/runtime.d.ts +33 -0
  74. package/dist/packages/mysql/src/runtime.d.ts.map +1 -1
  75. package/dist/packages/mysql/src/runtime.js +166 -12
  76. package/dist/packages/mysql/src/runtime.js.map +1 -1
  77. package/dist/packages/mysql/src/semantics.d.ts +1 -1
  78. package/dist/packages/mysql/src/semantics.d.ts.map +1 -1
  79. package/dist/packages/mysql/src/semantics.js +27 -3
  80. package/dist/packages/mysql/src/semantics.js.map +1 -1
  81. package/dist/packages/mysql/src/support.d.ts +43 -0
  82. package/dist/packages/mysql/src/support.d.ts.map +1 -0
  83. package/dist/packages/mysql/src/support.js +102 -0
  84. package/dist/packages/mysql/src/support.js.map +1 -0
  85. package/dist/packages/mysql/src/type-policy.d.ts +2 -0
  86. package/dist/packages/mysql/src/type-policy.d.ts.map +1 -1
  87. package/dist/packages/mysql/src/type-policy.js +24 -50
  88. package/dist/packages/mysql/src/type-policy.js.map +1 -1
  89. 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
- .trim()
41
- .toLowerCase()
42
- .replace(/\s+unsigned$/u, "")
43
- .replace(/\(.*/u, "");
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 = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
73
- const target = this.#relation(statement.table, false, scope, ctes);
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 targets = statement.columns.length === 0
76
- ? Object.values(target?.table.columns ?? {})
77
- : statement.columns.map((column) => this.#findColumn(target?.table, column));
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.#findColumn(target?.table, assignment.column);
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
- for (const reference of statement.using)
112
- this.#relation(reference, false, scope, ctes);
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
- const result = this.#statement(query.statement, outer, ctes);
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
- #select(statement, outer, ctes) {
149
- if (statement.compounds.length > 0) {
150
- this.#unsupported("MySQL set-operation inference is not supported safely", statement.compounds[0].range);
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
- const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
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
- for (const window of statement.windows) {
182
- for (const value of window.specification.partitionBy)
183
- this.#expression(value, scope, ctes);
184
- for (const item of window.specification.orderBy)
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
- return this.#items(statement.columns, scope, ctes);
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: Number.isInteger(expression.value) ? "int" : "decimal",
1060
+ databaseType,
1061
+ unsigned: false,
1062
+ coercibility: 5,
334
1063
  };
335
- return { tsType: "string", nullable: false, databaseType: "varchar" };
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
- : operand;
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
- if (comparisonOperators.has(expression.operator))
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") ? false : left.nullable || right.nullable,
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
- const leftType = left.databaseType === undefined ? undefined : normalized(left.databaseType);
447
- const rightType = right.databaseType === undefined ? undefined : normalized(right.databaseType);
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
- numericTypes.has(leftType) &&
451
- numericTypes.has(rightType)) {
452
- const decimal = leftType === "decimal" || rightType === "decimal" || expression.operator === "/";
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 : "number",
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: decimal ? "decimal" : "double",
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
- for (const value of expression.over.partitionBy)
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 expression.over.orderBy)
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
- return { tsType: column.tsType, nullable: column.nullable || relation.nullable, databaseType: column.databaseType };
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 { tsType: column.tsType, nullable: column.nullable, databaseType: column.databaseType };
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
- return this.#parameters.record(index, expected);
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
  }