altimate-code 0.5.1 → 0.5.3

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 (102) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +1 -5
  3. package/bin/altimate +6 -0
  4. package/bin/altimate-code +6 -0
  5. package/dbt-tools/bin/altimate-dbt +2 -0
  6. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/__init__.py +0 -0
  7. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/fetch_schema.py +35 -0
  8. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/utils.py +353 -0
  9. package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/validate_sql.py +114 -0
  10. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/__init__.py +178 -0
  11. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/__main__.py +96 -0
  12. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/_typing.py +17 -0
  13. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/__init__.py +3 -0
  14. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/__init__.py +18 -0
  15. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/_typing.py +18 -0
  16. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/column.py +332 -0
  17. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/dataframe.py +866 -0
  18. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/functions.py +1267 -0
  19. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/group.py +59 -0
  20. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/normalize.py +78 -0
  21. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/operations.py +53 -0
  22. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/readwriter.py +108 -0
  23. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/session.py +190 -0
  24. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/transforms.py +9 -0
  25. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/types.py +212 -0
  26. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/util.py +32 -0
  27. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/window.py +134 -0
  28. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/__init__.py +118 -0
  29. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/athena.py +166 -0
  30. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/bigquery.py +1331 -0
  31. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/clickhouse.py +1393 -0
  32. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/databricks.py +131 -0
  33. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/dialect.py +1915 -0
  34. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/doris.py +561 -0
  35. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/drill.py +157 -0
  36. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/druid.py +20 -0
  37. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/duckdb.py +1159 -0
  38. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/dune.py +16 -0
  39. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/hive.py +787 -0
  40. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/materialize.py +94 -0
  41. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/mysql.py +1324 -0
  42. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/oracle.py +378 -0
  43. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/postgres.py +778 -0
  44. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/presto.py +788 -0
  45. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/prql.py +203 -0
  46. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/redshift.py +448 -0
  47. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/risingwave.py +78 -0
  48. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/snowflake.py +1464 -0
  49. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/spark.py +202 -0
  50. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/spark2.py +349 -0
  51. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/sqlite.py +320 -0
  52. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/starrocks.py +343 -0
  53. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/tableau.py +61 -0
  54. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/teradata.py +356 -0
  55. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/trino.py +115 -0
  56. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/tsql.py +1403 -0
  57. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/diff.py +456 -0
  58. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/errors.py +93 -0
  59. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/__init__.py +95 -0
  60. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/context.py +101 -0
  61. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/env.py +246 -0
  62. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/python.py +460 -0
  63. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/table.py +155 -0
  64. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/expressions.py +8870 -0
  65. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/generator.py +4993 -0
  66. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/helper.py +582 -0
  67. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/jsonpath.py +227 -0
  68. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/lineage.py +423 -0
  69. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/__init__.py +11 -0
  70. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/annotate_types.py +589 -0
  71. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/canonicalize.py +222 -0
  72. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_ctes.py +43 -0
  73. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_joins.py +181 -0
  74. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_subqueries.py +189 -0
  75. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/isolate_table_selects.py +50 -0
  76. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/merge_subqueries.py +415 -0
  77. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/normalize.py +200 -0
  78. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/normalize_identifiers.py +64 -0
  79. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/optimize_joins.py +91 -0
  80. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/optimizer.py +94 -0
  81. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/pushdown_predicates.py +222 -0
  82. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/pushdown_projections.py +172 -0
  83. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify.py +104 -0
  84. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify_columns.py +1024 -0
  85. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify_tables.py +155 -0
  86. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/scope.py +904 -0
  87. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/simplify.py +1587 -0
  88. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/unnest_subqueries.py +302 -0
  89. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/parser.py +8501 -0
  90. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/planner.py +463 -0
  91. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/schema.py +588 -0
  92. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/serde.py +68 -0
  93. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/time.py +687 -0
  94. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/tokens.py +1520 -0
  95. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/transforms.py +1020 -0
  96. package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/trie.py +81 -0
  97. package/dbt-tools/dist/altimate_python_packages/dbt_core_integration.py +825 -0
  98. package/dbt-tools/dist/altimate_python_packages/dbt_utils.py +157 -0
  99. package/dbt-tools/dist/index.js +23859 -0
  100. package/package.json +13 -13
  101. package/postinstall.mjs +42 -0
  102. package/skills/altimate-setup/SKILL.md +31 -0
@@ -0,0 +1,1324 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot import exp, generator, parser, tokens, transforms
6
+ from sqlglot.dialects.dialect import (
7
+ Dialect,
8
+ NormalizationStrategy,
9
+ arrow_json_extract_sql,
10
+ date_add_interval_sql,
11
+ datestrtodate_sql,
12
+ build_formatted_time,
13
+ isnull_to_is_null,
14
+ length_or_char_length_sql,
15
+ max_or_greatest,
16
+ min_or_least,
17
+ no_ilike_sql,
18
+ no_paren_current_date_sql,
19
+ no_pivot_sql,
20
+ no_tablesample_sql,
21
+ no_trycast_sql,
22
+ build_date_delta,
23
+ build_date_delta_with_interval,
24
+ rename_func,
25
+ strposition_sql,
26
+ unit_to_var,
27
+ trim_sql,
28
+ timestrtotime_sql,
29
+ )
30
+ from sqlglot.generator import unsupported_args
31
+ from sqlglot.helper import seq_get
32
+ from sqlglot.tokens import TokenType
33
+
34
+
35
+ def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[MySQL.Parser], exp.Show]:
36
+ def _parse(self: MySQL.Parser) -> exp.Show:
37
+ return self._parse_show_mysql(*args, **kwargs)
38
+
39
+ return _parse
40
+
41
+
42
+ def _date_trunc_sql(self: MySQL.Generator, expression: exp.DateTrunc) -> str:
43
+ expr = self.sql(expression, "this")
44
+ unit = expression.text("unit").upper()
45
+
46
+ if unit == "WEEK":
47
+ concat = f"CONCAT(YEAR({expr}), ' ', WEEK({expr}, 1), ' 1')"
48
+ date_format = "%Y %u %w"
49
+ elif unit == "MONTH":
50
+ concat = f"CONCAT(YEAR({expr}), ' ', MONTH({expr}), ' 1')"
51
+ date_format = "%Y %c %e"
52
+ elif unit == "QUARTER":
53
+ concat = f"CONCAT(YEAR({expr}), ' ', QUARTER({expr}) * 3 - 2, ' 1')"
54
+ date_format = "%Y %c %e"
55
+ elif unit == "YEAR":
56
+ concat = f"CONCAT(YEAR({expr}), ' 1 1')"
57
+ date_format = "%Y %c %e"
58
+ else:
59
+ if unit != "DAY":
60
+ self.unsupported(f"Unexpected interval unit: {unit}")
61
+ return self.func("DATE", expr)
62
+
63
+ return self.func("STR_TO_DATE", concat, f"'{date_format}'")
64
+
65
+
66
+ # All specifiers for time parts (as opposed to date parts)
67
+ # https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format
68
+ TIME_SPECIFIERS = {"f", "H", "h", "I", "i", "k", "l", "p", "r", "S", "s", "T"}
69
+
70
+
71
+ def _has_time_specifier(date_format: str) -> bool:
72
+ i = 0
73
+ length = len(date_format)
74
+
75
+ while i < length:
76
+ if date_format[i] == "%":
77
+ i += 1
78
+ if i < length and date_format[i] in TIME_SPECIFIERS:
79
+ return True
80
+ i += 1
81
+ return False
82
+
83
+
84
+ def _str_to_date(args: t.List) -> exp.StrToDate | exp.StrToTime:
85
+ mysql_date_format = seq_get(args, 1)
86
+ date_format = MySQL.format_time(mysql_date_format)
87
+ this = seq_get(args, 0)
88
+
89
+ if mysql_date_format and _has_time_specifier(mysql_date_format.name):
90
+ return exp.StrToTime(this=this, format=date_format)
91
+
92
+ return exp.StrToDate(this=this, format=date_format)
93
+
94
+
95
+ def _str_to_date_sql(
96
+ self: MySQL.Generator, expression: exp.StrToDate | exp.StrToTime | exp.TsOrDsToDate
97
+ ) -> str:
98
+ return self.func("STR_TO_DATE", expression.this, self.format_time(expression))
99
+
100
+
101
+ def _unix_to_time_sql(self: MySQL.Generator, expression: exp.UnixToTime) -> str:
102
+ scale = expression.args.get("scale")
103
+ timestamp = expression.this
104
+
105
+ if scale in (None, exp.UnixToTime.SECONDS):
106
+ return self.func("FROM_UNIXTIME", timestamp, self.format_time(expression))
107
+
108
+ return self.func(
109
+ "FROM_UNIXTIME",
110
+ exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)),
111
+ self.format_time(expression),
112
+ )
113
+
114
+
115
+ def date_add_sql(
116
+ kind: str,
117
+ ) -> t.Callable[[generator.Generator, exp.Expression], str]:
118
+ def func(self: generator.Generator, expression: exp.Expression) -> str:
119
+ return self.func(
120
+ f"DATE_{kind}",
121
+ expression.this,
122
+ exp.Interval(this=expression.expression, unit=unit_to_var(expression)),
123
+ )
124
+
125
+ return func
126
+
127
+
128
+ def _ts_or_ds_to_date_sql(self: MySQL.Generator, expression: exp.TsOrDsToDate) -> str:
129
+ time_format = expression.args.get("format")
130
+ return _str_to_date_sql(self, expression) if time_format else self.func("DATE", expression.this)
131
+
132
+
133
+ def _remove_ts_or_ds_to_date(
134
+ to_sql: t.Optional[t.Callable[[MySQL.Generator, exp.Expression], str]] = None,
135
+ args: t.Tuple[str, ...] = ("this",),
136
+ ) -> t.Callable[[MySQL.Generator, exp.Func], str]:
137
+ def func(self: MySQL.Generator, expression: exp.Func) -> str:
138
+ for arg_key in args:
139
+ arg = expression.args.get(arg_key)
140
+ if isinstance(arg, exp.TsOrDsToDate) and not arg.args.get("format"):
141
+ expression.set(arg_key, arg.this)
142
+
143
+ return to_sql(self, expression) if to_sql else self.function_fallback_sql(expression)
144
+
145
+ return func
146
+
147
+
148
+ class MySQL(Dialect):
149
+ PROMOTE_TO_INFERRED_DATETIME_TYPE = True
150
+
151
+ # https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
152
+ IDENTIFIERS_CAN_START_WITH_DIGIT = True
153
+
154
+ # We default to treating all identifiers as case-sensitive, since it matches MySQL's
155
+ # behavior on Linux systems. For MacOS and Windows systems, one can override this
156
+ # setting by specifying `dialect="mysql, normalization_strategy = lowercase"`.
157
+ #
158
+ # See also https://dev.mysql.com/doc/refman/8.2/en/identifier-case-sensitivity.html
159
+ NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE
160
+
161
+ TIME_FORMAT = "'%Y-%m-%d %T'"
162
+ DPIPE_IS_STRING_CONCAT = False
163
+ SUPPORTS_USER_DEFINED_TYPES = False
164
+ SUPPORTS_SEMI_ANTI_JOIN = False
165
+ SAFE_DIVISION = True
166
+
167
+ # https://prestodb.io/docs/current/functions/datetime.html#mysql-date-functions
168
+ TIME_MAPPING = {
169
+ "%M": "%B",
170
+ "%c": "%-m",
171
+ "%e": "%-d",
172
+ "%h": "%I",
173
+ "%i": "%M",
174
+ "%s": "%S",
175
+ "%u": "%W",
176
+ "%k": "%-H",
177
+ "%l": "%-I",
178
+ "%T": "%H:%M:%S",
179
+ "%W": "%A",
180
+ }
181
+
182
+ class Tokenizer(tokens.Tokenizer):
183
+ QUOTES = ["'", '"']
184
+ COMMENTS = ["--", "#", ("/*", "*/")]
185
+ IDENTIFIERS = ["`"]
186
+ STRING_ESCAPES = ["'", '"', "\\"]
187
+ BIT_STRINGS = [("b'", "'"), ("B'", "'"), ("0b", "")]
188
+ HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", "")]
189
+
190
+ NESTED_COMMENTS = False
191
+
192
+ KEYWORDS = {
193
+ **tokens.Tokenizer.KEYWORDS,
194
+ "CHARSET": TokenType.CHARACTER_SET,
195
+ # The DESCRIBE and EXPLAIN statements are synonyms.
196
+ # https://dev.mysql.com/doc/refman/8.4/en/explain.html
197
+ "BLOB": TokenType.BLOB,
198
+ "EXPLAIN": TokenType.DESCRIBE,
199
+ "FORCE": TokenType.FORCE,
200
+ "IGNORE": TokenType.IGNORE,
201
+ "KEY": TokenType.KEY,
202
+ "LOCK TABLES": TokenType.COMMAND,
203
+ "LONGBLOB": TokenType.LONGBLOB,
204
+ "LONGTEXT": TokenType.LONGTEXT,
205
+ "MEDIUMBLOB": TokenType.MEDIUMBLOB,
206
+ "TINYBLOB": TokenType.TINYBLOB,
207
+ "TINYTEXT": TokenType.TINYTEXT,
208
+ "MEDIUMTEXT": TokenType.MEDIUMTEXT,
209
+ "MEDIUMINT": TokenType.MEDIUMINT,
210
+ "MEMBER OF": TokenType.MEMBER_OF,
211
+ "SEPARATOR": TokenType.SEPARATOR,
212
+ "SERIAL": TokenType.SERIAL,
213
+ "START": TokenType.BEGIN,
214
+ "SIGNED": TokenType.BIGINT,
215
+ "SIGNED INTEGER": TokenType.BIGINT,
216
+ "TIMESTAMP": TokenType.TIMESTAMPTZ,
217
+ "UNLOCK TABLES": TokenType.COMMAND,
218
+ "UNSIGNED": TokenType.UBIGINT,
219
+ "UNSIGNED INTEGER": TokenType.UBIGINT,
220
+ "YEAR": TokenType.YEAR,
221
+ "_ARMSCII8": TokenType.INTRODUCER,
222
+ "_ASCII": TokenType.INTRODUCER,
223
+ "_BIG5": TokenType.INTRODUCER,
224
+ "_BINARY": TokenType.INTRODUCER,
225
+ "_CP1250": TokenType.INTRODUCER,
226
+ "_CP1251": TokenType.INTRODUCER,
227
+ "_CP1256": TokenType.INTRODUCER,
228
+ "_CP1257": TokenType.INTRODUCER,
229
+ "_CP850": TokenType.INTRODUCER,
230
+ "_CP852": TokenType.INTRODUCER,
231
+ "_CP866": TokenType.INTRODUCER,
232
+ "_CP932": TokenType.INTRODUCER,
233
+ "_DEC8": TokenType.INTRODUCER,
234
+ "_EUCJPMS": TokenType.INTRODUCER,
235
+ "_EUCKR": TokenType.INTRODUCER,
236
+ "_GB18030": TokenType.INTRODUCER,
237
+ "_GB2312": TokenType.INTRODUCER,
238
+ "_GBK": TokenType.INTRODUCER,
239
+ "_GEOSTD8": TokenType.INTRODUCER,
240
+ "_GREEK": TokenType.INTRODUCER,
241
+ "_HEBREW": TokenType.INTRODUCER,
242
+ "_HP8": TokenType.INTRODUCER,
243
+ "_KEYBCS2": TokenType.INTRODUCER,
244
+ "_KOI8R": TokenType.INTRODUCER,
245
+ "_KOI8U": TokenType.INTRODUCER,
246
+ "_LATIN1": TokenType.INTRODUCER,
247
+ "_LATIN2": TokenType.INTRODUCER,
248
+ "_LATIN5": TokenType.INTRODUCER,
249
+ "_LATIN7": TokenType.INTRODUCER,
250
+ "_MACCE": TokenType.INTRODUCER,
251
+ "_MACROMAN": TokenType.INTRODUCER,
252
+ "_SJIS": TokenType.INTRODUCER,
253
+ "_SWE7": TokenType.INTRODUCER,
254
+ "_TIS620": TokenType.INTRODUCER,
255
+ "_UCS2": TokenType.INTRODUCER,
256
+ "_UJIS": TokenType.INTRODUCER,
257
+ # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html
258
+ "_UTF8": TokenType.INTRODUCER,
259
+ "_UTF16": TokenType.INTRODUCER,
260
+ "_UTF16LE": TokenType.INTRODUCER,
261
+ "_UTF32": TokenType.INTRODUCER,
262
+ "_UTF8MB3": TokenType.INTRODUCER,
263
+ "_UTF8MB4": TokenType.INTRODUCER,
264
+ "@@": TokenType.SESSION_PARAMETER,
265
+ }
266
+
267
+ COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE} - {TokenType.SHOW}
268
+
269
+ class Parser(parser.Parser):
270
+ FUNC_TOKENS = {
271
+ *parser.Parser.FUNC_TOKENS,
272
+ TokenType.DATABASE,
273
+ TokenType.SCHEMA,
274
+ TokenType.VALUES,
275
+ }
276
+
277
+ CONJUNCTION = {
278
+ **parser.Parser.CONJUNCTION,
279
+ TokenType.DAMP: exp.And,
280
+ TokenType.XOR: exp.Xor,
281
+ }
282
+
283
+ DISJUNCTION = {
284
+ **parser.Parser.DISJUNCTION,
285
+ TokenType.DPIPE: exp.Or,
286
+ }
287
+
288
+ TABLE_ALIAS_TOKENS = (
289
+ parser.Parser.TABLE_ALIAS_TOKENS - parser.Parser.TABLE_INDEX_HINT_TOKENS
290
+ )
291
+
292
+ RANGE_PARSERS = {
293
+ **parser.Parser.RANGE_PARSERS,
294
+ TokenType.MEMBER_OF: lambda self, this: self.expression(
295
+ exp.JSONArrayContains,
296
+ this=this,
297
+ expression=self._parse_wrapped(self._parse_expression),
298
+ ),
299
+ }
300
+
301
+ FUNCTIONS = {
302
+ **parser.Parser.FUNCTIONS,
303
+ "CONVERT_TZ": lambda args: exp.ConvertTimezone(
304
+ source_tz=seq_get(args, 1), target_tz=seq_get(args, 2), timestamp=seq_get(args, 0)
305
+ ),
306
+ "CURDATE": exp.CurrentDate.from_arg_list,
307
+ "DATE": lambda args: exp.TsOrDsToDate(this=seq_get(args, 0)),
308
+ "DATE_ADD": build_date_delta_with_interval(exp.DateAdd),
309
+ "DATE_FORMAT": build_formatted_time(exp.TimeToStr, "mysql"),
310
+ "DATE_SUB": build_date_delta_with_interval(exp.DateSub),
311
+ "DAY": lambda args: exp.Day(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
312
+ "DAYOFMONTH": lambda args: exp.DayOfMonth(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
313
+ "DAYOFWEEK": lambda args: exp.DayOfWeek(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
314
+ "DAYOFYEAR": lambda args: exp.DayOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
315
+ "FORMAT": exp.NumberToStr.from_arg_list,
316
+ "FROM_UNIXTIME": build_formatted_time(exp.UnixToTime, "mysql"),
317
+ "ISNULL": isnull_to_is_null,
318
+ "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True),
319
+ "MAKETIME": exp.TimeFromParts.from_arg_list,
320
+ "MONTH": lambda args: exp.Month(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
321
+ "MONTHNAME": lambda args: exp.TimeToStr(
322
+ this=exp.TsOrDsToDate(this=seq_get(args, 0)),
323
+ format=exp.Literal.string("%B"),
324
+ ),
325
+ "SCHEMA": exp.CurrentSchema.from_arg_list,
326
+ "DATABASE": exp.CurrentSchema.from_arg_list,
327
+ "STR_TO_DATE": _str_to_date,
328
+ "TIMESTAMPDIFF": build_date_delta(exp.TimestampDiff),
329
+ "TO_DAYS": lambda args: exp.paren(
330
+ exp.DateDiff(
331
+ this=exp.TsOrDsToDate(this=seq_get(args, 0)),
332
+ expression=exp.TsOrDsToDate(this=exp.Literal.string("0000-01-01")),
333
+ unit=exp.var("DAY"),
334
+ )
335
+ + 1
336
+ ),
337
+ "WEEK": lambda args: exp.Week(
338
+ this=exp.TsOrDsToDate(this=seq_get(args, 0)), mode=seq_get(args, 1)
339
+ ),
340
+ "WEEKOFYEAR": lambda args: exp.WeekOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
341
+ "YEAR": lambda args: exp.Year(this=exp.TsOrDsToDate(this=seq_get(args, 0))),
342
+ }
343
+
344
+ FUNCTION_PARSERS = {
345
+ **parser.Parser.FUNCTION_PARSERS,
346
+ "CHAR": lambda self: self.expression(
347
+ exp.Chr,
348
+ expressions=self._parse_csv(self._parse_assignment),
349
+ charset=self._match(TokenType.USING) and self._parse_var(),
350
+ ),
351
+ "GROUP_CONCAT": lambda self: self._parse_group_concat(),
352
+ # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values
353
+ "VALUES": lambda self: self.expression(
354
+ exp.Anonymous, this="VALUES", expressions=[self._parse_id_var()]
355
+ ),
356
+ "JSON_VALUE": lambda self: self._parse_json_value(),
357
+ }
358
+
359
+ STATEMENT_PARSERS = {
360
+ **parser.Parser.STATEMENT_PARSERS,
361
+ TokenType.SHOW: lambda self: self._parse_show(),
362
+ }
363
+
364
+ SHOW_PARSERS = {
365
+ "BINARY LOGS": _show_parser("BINARY LOGS"),
366
+ "MASTER LOGS": _show_parser("BINARY LOGS"),
367
+ "BINLOG EVENTS": _show_parser("BINLOG EVENTS"),
368
+ "CHARACTER SET": _show_parser("CHARACTER SET"),
369
+ "CHARSET": _show_parser("CHARACTER SET"),
370
+ "COLLATION": _show_parser("COLLATION"),
371
+ "FULL COLUMNS": _show_parser("COLUMNS", target="FROM", full=True),
372
+ "COLUMNS": _show_parser("COLUMNS", target="FROM"),
373
+ "CREATE DATABASE": _show_parser("CREATE DATABASE", target=True),
374
+ "CREATE EVENT": _show_parser("CREATE EVENT", target=True),
375
+ "CREATE FUNCTION": _show_parser("CREATE FUNCTION", target=True),
376
+ "CREATE PROCEDURE": _show_parser("CREATE PROCEDURE", target=True),
377
+ "CREATE TABLE": _show_parser("CREATE TABLE", target=True),
378
+ "CREATE TRIGGER": _show_parser("CREATE TRIGGER", target=True),
379
+ "CREATE VIEW": _show_parser("CREATE VIEW", target=True),
380
+ "DATABASES": _show_parser("DATABASES"),
381
+ "SCHEMAS": _show_parser("DATABASES"),
382
+ "ENGINE": _show_parser("ENGINE", target=True),
383
+ "STORAGE ENGINES": _show_parser("ENGINES"),
384
+ "ENGINES": _show_parser("ENGINES"),
385
+ "ERRORS": _show_parser("ERRORS"),
386
+ "EVENTS": _show_parser("EVENTS"),
387
+ "FUNCTION CODE": _show_parser("FUNCTION CODE", target=True),
388
+ "FUNCTION STATUS": _show_parser("FUNCTION STATUS"),
389
+ "GRANTS": _show_parser("GRANTS", target="FOR"),
390
+ "INDEX": _show_parser("INDEX", target="FROM"),
391
+ "MASTER STATUS": _show_parser("MASTER STATUS"),
392
+ "OPEN TABLES": _show_parser("OPEN TABLES"),
393
+ "PLUGINS": _show_parser("PLUGINS"),
394
+ "PROCEDURE CODE": _show_parser("PROCEDURE CODE", target=True),
395
+ "PROCEDURE STATUS": _show_parser("PROCEDURE STATUS"),
396
+ "PRIVILEGES": _show_parser("PRIVILEGES"),
397
+ "FULL PROCESSLIST": _show_parser("PROCESSLIST", full=True),
398
+ "PROCESSLIST": _show_parser("PROCESSLIST"),
399
+ "PROFILE": _show_parser("PROFILE"),
400
+ "PROFILES": _show_parser("PROFILES"),
401
+ "RELAYLOG EVENTS": _show_parser("RELAYLOG EVENTS"),
402
+ "REPLICAS": _show_parser("REPLICAS"),
403
+ "SLAVE HOSTS": _show_parser("REPLICAS"),
404
+ "REPLICA STATUS": _show_parser("REPLICA STATUS"),
405
+ "SLAVE STATUS": _show_parser("REPLICA STATUS"),
406
+ "GLOBAL STATUS": _show_parser("STATUS", global_=True),
407
+ "SESSION STATUS": _show_parser("STATUS"),
408
+ "STATUS": _show_parser("STATUS"),
409
+ "TABLE STATUS": _show_parser("TABLE STATUS"),
410
+ "FULL TABLES": _show_parser("TABLES", full=True),
411
+ "TABLES": _show_parser("TABLES"),
412
+ "TRIGGERS": _show_parser("TRIGGERS"),
413
+ "GLOBAL VARIABLES": _show_parser("VARIABLES", global_=True),
414
+ "SESSION VARIABLES": _show_parser("VARIABLES"),
415
+ "VARIABLES": _show_parser("VARIABLES"),
416
+ "WARNINGS": _show_parser("WARNINGS"),
417
+ }
418
+
419
+ PROPERTY_PARSERS = {
420
+ **parser.Parser.PROPERTY_PARSERS,
421
+ "LOCK": lambda self: self._parse_property_assignment(exp.LockProperty),
422
+ }
423
+
424
+ SET_PARSERS = {
425
+ **parser.Parser.SET_PARSERS,
426
+ "PERSIST": lambda self: self._parse_set_item_assignment("PERSIST"),
427
+ "PERSIST_ONLY": lambda self: self._parse_set_item_assignment("PERSIST_ONLY"),
428
+ "CHARACTER SET": lambda self: self._parse_set_item_charset("CHARACTER SET"),
429
+ "CHARSET": lambda self: self._parse_set_item_charset("CHARACTER SET"),
430
+ "NAMES": lambda self: self._parse_set_item_names(),
431
+ }
432
+
433
+ CONSTRAINT_PARSERS = {
434
+ **parser.Parser.CONSTRAINT_PARSERS,
435
+ "FULLTEXT": lambda self: self._parse_index_constraint(kind="FULLTEXT"),
436
+ "INDEX": lambda self: self._parse_index_constraint(),
437
+ "KEY": lambda self: self._parse_index_constraint(),
438
+ "SPATIAL": lambda self: self._parse_index_constraint(kind="SPATIAL"),
439
+ }
440
+
441
+ ALTER_PARSERS = {
442
+ **parser.Parser.ALTER_PARSERS,
443
+ "MODIFY": lambda self: self._parse_alter_table_alter(),
444
+ }
445
+
446
+ ALTER_ALTER_PARSERS = {
447
+ **parser.Parser.ALTER_ALTER_PARSERS,
448
+ "INDEX": lambda self: self._parse_alter_table_alter_index(),
449
+ }
450
+
451
+ SCHEMA_UNNAMED_CONSTRAINTS = {
452
+ *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS,
453
+ "FULLTEXT",
454
+ "INDEX",
455
+ "KEY",
456
+ "SPATIAL",
457
+ }
458
+
459
+ PROFILE_TYPES: parser.OPTIONS_TYPE = {
460
+ **dict.fromkeys(("ALL", "CPU", "IPC", "MEMORY", "SOURCE", "SWAPS"), tuple()),
461
+ "BLOCK": ("IO",),
462
+ "CONTEXT": ("SWITCHES",),
463
+ "PAGE": ("FAULTS",),
464
+ }
465
+
466
+ TYPE_TOKENS = {
467
+ *parser.Parser.TYPE_TOKENS,
468
+ TokenType.SET,
469
+ }
470
+
471
+ ENUM_TYPE_TOKENS = {
472
+ *parser.Parser.ENUM_TYPE_TOKENS,
473
+ TokenType.SET,
474
+ }
475
+
476
+ # SELECT [ ALL | DISTINCT | DISTINCTROW ] [ <OPERATION_MODIFIERS> ]
477
+ OPERATION_MODIFIERS = {
478
+ "HIGH_PRIORITY",
479
+ "STRAIGHT_JOIN",
480
+ "SQL_SMALL_RESULT",
481
+ "SQL_BIG_RESULT",
482
+ "SQL_BUFFER_RESULT",
483
+ "SQL_NO_CACHE",
484
+ "SQL_CALC_FOUND_ROWS",
485
+ }
486
+
487
+ LOG_DEFAULTS_TO_LN = True
488
+ STRING_ALIASES = True
489
+ VALUES_FOLLOWED_BY_PAREN = False
490
+ SUPPORTS_PARTITION_SELECTION = True
491
+
492
+ def _parse_generated_as_identity(
493
+ self,
494
+ ) -> (
495
+ exp.GeneratedAsIdentityColumnConstraint
496
+ | exp.ComputedColumnConstraint
497
+ | exp.GeneratedAsRowColumnConstraint
498
+ ):
499
+ this = super()._parse_generated_as_identity()
500
+
501
+ if self._match_texts(("STORED", "VIRTUAL")):
502
+ persisted = self._prev.text.upper() == "STORED"
503
+
504
+ if isinstance(this, exp.ComputedColumnConstraint):
505
+ this.set("persisted", persisted)
506
+ elif isinstance(this, exp.GeneratedAsIdentityColumnConstraint):
507
+ this = self.expression(
508
+ exp.ComputedColumnConstraint, this=this.expression, persisted=persisted
509
+ )
510
+
511
+ return this
512
+
513
+ def _parse_primary_key_part(self) -> t.Optional[exp.Expression]:
514
+ this = self._parse_id_var()
515
+ if not self._match(TokenType.L_PAREN):
516
+ return this
517
+
518
+ expression = self._parse_number()
519
+ self._match_r_paren()
520
+ return self.expression(exp.ColumnPrefix, this=this, expression=expression)
521
+
522
+ def _parse_index_constraint(
523
+ self, kind: t.Optional[str] = None
524
+ ) -> exp.IndexColumnConstraint:
525
+ if kind:
526
+ self._match_texts(("INDEX", "KEY"))
527
+
528
+ this = self._parse_id_var(any_token=False)
529
+ index_type = self._match(TokenType.USING) and self._advance_any() and self._prev.text
530
+ expressions = self._parse_wrapped_csv(self._parse_ordered)
531
+
532
+ options = []
533
+ while True:
534
+ if self._match_text_seq("KEY_BLOCK_SIZE"):
535
+ self._match(TokenType.EQ)
536
+ opt = exp.IndexConstraintOption(key_block_size=self._parse_number())
537
+ elif self._match(TokenType.USING):
538
+ opt = exp.IndexConstraintOption(using=self._advance_any() and self._prev.text)
539
+ elif self._match_text_seq("WITH", "PARSER"):
540
+ opt = exp.IndexConstraintOption(parser=self._parse_var(any_token=True))
541
+ elif self._match(TokenType.COMMENT):
542
+ opt = exp.IndexConstraintOption(comment=self._parse_string())
543
+ elif self._match_text_seq("VISIBLE"):
544
+ opt = exp.IndexConstraintOption(visible=True)
545
+ elif self._match_text_seq("INVISIBLE"):
546
+ opt = exp.IndexConstraintOption(visible=False)
547
+ elif self._match_text_seq("ENGINE_ATTRIBUTE"):
548
+ self._match(TokenType.EQ)
549
+ opt = exp.IndexConstraintOption(engine_attr=self._parse_string())
550
+ elif self._match_text_seq("SECONDARY_ENGINE_ATTRIBUTE"):
551
+ self._match(TokenType.EQ)
552
+ opt = exp.IndexConstraintOption(secondary_engine_attr=self._parse_string())
553
+ else:
554
+ opt = None
555
+
556
+ if not opt:
557
+ break
558
+
559
+ options.append(opt)
560
+
561
+ return self.expression(
562
+ exp.IndexColumnConstraint,
563
+ this=this,
564
+ expressions=expressions,
565
+ kind=kind,
566
+ index_type=index_type,
567
+ options=options,
568
+ )
569
+
570
+ def _parse_show_mysql(
571
+ self,
572
+ this: str,
573
+ target: bool | str = False,
574
+ full: t.Optional[bool] = None,
575
+ global_: t.Optional[bool] = None,
576
+ ) -> exp.Show:
577
+ if target:
578
+ if isinstance(target, str):
579
+ self._match_text_seq(target)
580
+ target_id = self._parse_id_var()
581
+ else:
582
+ target_id = None
583
+
584
+ log = self._parse_string() if self._match_text_seq("IN") else None
585
+
586
+ if this in ("BINLOG EVENTS", "RELAYLOG EVENTS"):
587
+ position = self._parse_number() if self._match_text_seq("FROM") else None
588
+ db = None
589
+ else:
590
+ position = None
591
+ db = None
592
+
593
+ if self._match(TokenType.FROM):
594
+ db = self._parse_id_var()
595
+ elif self._match(TokenType.DOT):
596
+ db = target_id
597
+ target_id = self._parse_id_var()
598
+
599
+ channel = self._parse_id_var() if self._match_text_seq("FOR", "CHANNEL") else None
600
+
601
+ like = self._parse_string() if self._match_text_seq("LIKE") else None
602
+ where = self._parse_where()
603
+
604
+ if this == "PROFILE":
605
+ types = self._parse_csv(lambda: self._parse_var_from_options(self.PROFILE_TYPES))
606
+ query = self._parse_number() if self._match_text_seq("FOR", "QUERY") else None
607
+ offset = self._parse_number() if self._match_text_seq("OFFSET") else None
608
+ limit = self._parse_number() if self._match_text_seq("LIMIT") else None
609
+ else:
610
+ types, query = None, None
611
+ offset, limit = self._parse_oldstyle_limit()
612
+
613
+ mutex = True if self._match_text_seq("MUTEX") else None
614
+ mutex = False if self._match_text_seq("STATUS") else mutex
615
+
616
+ return self.expression(
617
+ exp.Show,
618
+ this=this,
619
+ target=target_id,
620
+ full=full,
621
+ log=log,
622
+ position=position,
623
+ db=db,
624
+ channel=channel,
625
+ like=like,
626
+ where=where,
627
+ types=types,
628
+ query=query,
629
+ offset=offset,
630
+ limit=limit,
631
+ mutex=mutex,
632
+ **{"global": global_}, # type: ignore
633
+ )
634
+
635
+ def _parse_oldstyle_limit(
636
+ self,
637
+ ) -> t.Tuple[t.Optional[exp.Expression], t.Optional[exp.Expression]]:
638
+ limit = None
639
+ offset = None
640
+ if self._match_text_seq("LIMIT"):
641
+ parts = self._parse_csv(self._parse_number)
642
+ if len(parts) == 1:
643
+ limit = parts[0]
644
+ elif len(parts) == 2:
645
+ limit = parts[1]
646
+ offset = parts[0]
647
+
648
+ return offset, limit
649
+
650
+ def _parse_set_item_charset(self, kind: str) -> exp.Expression:
651
+ this = self._parse_string() or self._parse_unquoted_field()
652
+ return self.expression(exp.SetItem, this=this, kind=kind)
653
+
654
+ def _parse_set_item_names(self) -> exp.Expression:
655
+ charset = self._parse_string() or self._parse_unquoted_field()
656
+ if self._match_text_seq("COLLATE"):
657
+ collate = self._parse_string() or self._parse_unquoted_field()
658
+ else:
659
+ collate = None
660
+
661
+ return self.expression(exp.SetItem, this=charset, collate=collate, kind="NAMES")
662
+
663
+ def _parse_type(
664
+ self, parse_interval: bool = True, fallback_to_identifier: bool = False
665
+ ) -> t.Optional[exp.Expression]:
666
+ # mysql binary is special and can work anywhere, even in order by operations
667
+ # it operates like a no paren func
668
+ if self._match(TokenType.BINARY, advance=False):
669
+ data_type = self._parse_types(check_func=True, allow_identifiers=False)
670
+
671
+ if isinstance(data_type, exp.DataType):
672
+ return self.expression(exp.Cast, this=self._parse_column(), to=data_type)
673
+
674
+ return super()._parse_type(
675
+ parse_interval=parse_interval, fallback_to_identifier=fallback_to_identifier
676
+ )
677
+
678
+ def _parse_group_concat(self) -> t.Optional[exp.Expression]:
679
+ def concat_exprs(
680
+ node: t.Optional[exp.Expression], exprs: t.List[exp.Expression]
681
+ ) -> exp.Expression:
682
+ if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
683
+ concat_exprs = [
684
+ self.expression(exp.Concat, expressions=node.expressions, safe=True)
685
+ ]
686
+ node.set("expressions", concat_exprs)
687
+ return node
688
+ if len(exprs) == 1:
689
+ return exprs[0]
690
+ return self.expression(exp.Concat, expressions=args, safe=True)
691
+
692
+ args = self._parse_csv(self._parse_lambda)
693
+
694
+ if args:
695
+ order = args[-1] if isinstance(args[-1], exp.Order) else None
696
+
697
+ if order:
698
+ # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
699
+ # remove 'expr' from exp.Order and add it back to args
700
+ args[-1] = order.this
701
+ order.set("this", concat_exprs(order.this, args))
702
+
703
+ this = order or concat_exprs(args[0], args)
704
+ else:
705
+ this = None
706
+
707
+ separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
708
+
709
+ return self.expression(exp.GroupConcat, this=this, separator=separator)
710
+
711
+ def _parse_json_value(self) -> exp.JSONValue:
712
+ this = self._parse_bitwise()
713
+ self._match(TokenType.COMMA)
714
+ path = self._parse_bitwise()
715
+
716
+ returning = self._match(TokenType.RETURNING) and self._parse_type()
717
+
718
+ return self.expression(
719
+ exp.JSONValue,
720
+ this=this,
721
+ path=self.dialect.to_json_path(path),
722
+ returning=returning,
723
+ on_condition=self._parse_on_condition(),
724
+ )
725
+
726
+ def _parse_alter_table_alter_index(self) -> exp.AlterIndex:
727
+ index = self._parse_field(any_token=True)
728
+
729
+ if self._match_text_seq("VISIBLE"):
730
+ visible = True
731
+ elif self._match_text_seq("INVISIBLE"):
732
+ visible = False
733
+ else:
734
+ visible = None
735
+
736
+ return self.expression(exp.AlterIndex, this=index, visible=visible)
737
+
738
+ class Generator(generator.Generator):
739
+ INTERVAL_ALLOWS_PLURAL_FORM = False
740
+ LOCKING_READS_SUPPORTED = True
741
+ NULL_ORDERING_SUPPORTED = None
742
+ JOIN_HINTS = False
743
+ TABLE_HINTS = True
744
+ DUPLICATE_KEY_UPDATE_WITH_SET = False
745
+ QUERY_HINT_SEP = " "
746
+ VALUES_AS_TABLE = False
747
+ NVL2_SUPPORTED = False
748
+ LAST_DAY_SUPPORTS_DATE_PART = False
749
+ JSON_TYPE_REQUIRED_FOR_EXTRACTION = True
750
+ JSON_PATH_BRACKETED_KEY_SUPPORTED = False
751
+ JSON_KEY_VALUE_PAIR_SEP = ","
752
+ SUPPORTS_TO_NUMBER = False
753
+ PARSE_JSON_NAME: t.Optional[str] = None
754
+ PAD_FILL_PATTERN_IS_REQUIRED = True
755
+ WRAP_DERIVED_VALUES = False
756
+ VARCHAR_REQUIRES_SIZE = True
757
+ SUPPORTS_MEDIAN = False
758
+
759
+ TRANSFORMS = {
760
+ **generator.Generator.TRANSFORMS,
761
+ exp.ArrayAgg: rename_func("GROUP_CONCAT"),
762
+ exp.CurrentDate: no_paren_current_date_sql,
763
+ exp.DateDiff: _remove_ts_or_ds_to_date(
764
+ lambda self, e: self.func("DATEDIFF", e.this, e.expression), ("this", "expression")
765
+ ),
766
+ exp.DateAdd: _remove_ts_or_ds_to_date(date_add_sql("ADD")),
767
+ exp.DateStrToDate: datestrtodate_sql,
768
+ exp.DateSub: _remove_ts_or_ds_to_date(date_add_sql("SUB")),
769
+ exp.DateTrunc: _date_trunc_sql,
770
+ exp.Day: _remove_ts_or_ds_to_date(),
771
+ exp.DayOfMonth: _remove_ts_or_ds_to_date(rename_func("DAYOFMONTH")),
772
+ exp.DayOfWeek: _remove_ts_or_ds_to_date(rename_func("DAYOFWEEK")),
773
+ exp.DayOfYear: _remove_ts_or_ds_to_date(rename_func("DAYOFYEAR")),
774
+ exp.GroupConcat: lambda self,
775
+ e: f"""GROUP_CONCAT({self.sql(e, "this")} SEPARATOR {self.sql(e, "separator") or "','"})""",
776
+ exp.ILike: no_ilike_sql,
777
+ exp.JSONExtractScalar: arrow_json_extract_sql,
778
+ exp.Length: length_or_char_length_sql,
779
+ exp.LogicalOr: rename_func("MAX"),
780
+ exp.LogicalAnd: rename_func("MIN"),
781
+ exp.Max: max_or_greatest,
782
+ exp.Min: min_or_least,
783
+ exp.Month: _remove_ts_or_ds_to_date(),
784
+ exp.NullSafeEQ: lambda self, e: self.binary(e, "<=>"),
785
+ exp.NullSafeNEQ: lambda self, e: f"NOT {self.binary(e, '<=>')}",
786
+ exp.NumberToStr: rename_func("FORMAT"),
787
+ exp.Pivot: no_pivot_sql,
788
+ exp.Select: transforms.preprocess(
789
+ [
790
+ transforms.eliminate_distinct_on,
791
+ transforms.eliminate_semi_and_anti_joins,
792
+ transforms.eliminate_qualify,
793
+ transforms.eliminate_full_outer_join,
794
+ transforms.unnest_generate_date_array_using_recursive_cte,
795
+ ]
796
+ ),
797
+ exp.StrPosition: lambda self, e: strposition_sql(
798
+ self, e, func_name="LOCATE", supports_position=True
799
+ ),
800
+ exp.StrToDate: _str_to_date_sql,
801
+ exp.StrToTime: _str_to_date_sql,
802
+ exp.Stuff: rename_func("INSERT"),
803
+ exp.TableSample: no_tablesample_sql,
804
+ exp.TimeFromParts: rename_func("MAKETIME"),
805
+ exp.TimestampAdd: date_add_interval_sql("DATE", "ADD"),
806
+ exp.TimestampDiff: lambda self, e: self.func(
807
+ "TIMESTAMPDIFF", unit_to_var(e), e.expression, e.this
808
+ ),
809
+ exp.TimestampSub: date_add_interval_sql("DATE", "SUB"),
810
+ exp.TimeStrToUnix: rename_func("UNIX_TIMESTAMP"),
811
+ exp.TimeStrToTime: lambda self, e: timestrtotime_sql(
812
+ self,
813
+ e,
814
+ include_precision=not e.args.get("zone"),
815
+ ),
816
+ exp.TimeToStr: _remove_ts_or_ds_to_date(
817
+ lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e))
818
+ ),
819
+ exp.Trim: trim_sql,
820
+ exp.TryCast: no_trycast_sql,
821
+ exp.TsOrDsAdd: date_add_sql("ADD"),
822
+ exp.TsOrDsDiff: lambda self, e: self.func("DATEDIFF", e.this, e.expression),
823
+ exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
824
+ exp.Unicode: lambda self, e: f"ORD(CONVERT({self.sql(e.this)} USING utf32))",
825
+ exp.UnixToTime: _unix_to_time_sql,
826
+ exp.Week: _remove_ts_or_ds_to_date(),
827
+ exp.WeekOfYear: _remove_ts_or_ds_to_date(rename_func("WEEKOFYEAR")),
828
+ exp.Year: _remove_ts_or_ds_to_date(),
829
+ }
830
+
831
+ UNSIGNED_TYPE_MAPPING = {
832
+ exp.DataType.Type.UBIGINT: "BIGINT",
833
+ exp.DataType.Type.UINT: "INT",
834
+ exp.DataType.Type.UMEDIUMINT: "MEDIUMINT",
835
+ exp.DataType.Type.USMALLINT: "SMALLINT",
836
+ exp.DataType.Type.UTINYINT: "TINYINT",
837
+ exp.DataType.Type.UDECIMAL: "DECIMAL",
838
+ exp.DataType.Type.UDOUBLE: "DOUBLE",
839
+ }
840
+
841
+ TIMESTAMP_TYPE_MAPPING = {
842
+ exp.DataType.Type.DATETIME2: "DATETIME",
843
+ exp.DataType.Type.SMALLDATETIME: "DATETIME",
844
+ exp.DataType.Type.TIMESTAMP: "DATETIME",
845
+ exp.DataType.Type.TIMESTAMPNTZ: "DATETIME",
846
+ exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
847
+ exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP",
848
+ }
849
+
850
+ TYPE_MAPPING = {
851
+ **generator.Generator.TYPE_MAPPING,
852
+ **UNSIGNED_TYPE_MAPPING,
853
+ **TIMESTAMP_TYPE_MAPPING,
854
+ }
855
+
856
+ TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMTEXT)
857
+ TYPE_MAPPING.pop(exp.DataType.Type.LONGTEXT)
858
+ TYPE_MAPPING.pop(exp.DataType.Type.TINYTEXT)
859
+ TYPE_MAPPING.pop(exp.DataType.Type.BLOB)
860
+ TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMBLOB)
861
+ TYPE_MAPPING.pop(exp.DataType.Type.LONGBLOB)
862
+ TYPE_MAPPING.pop(exp.DataType.Type.TINYBLOB)
863
+
864
+ PROPERTIES_LOCATION = {
865
+ **generator.Generator.PROPERTIES_LOCATION,
866
+ exp.TransientProperty: exp.Properties.Location.UNSUPPORTED,
867
+ exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
868
+ }
869
+
870
+ LIMIT_FETCH = "LIMIT"
871
+
872
+ LIMIT_ONLY_LITERALS = True
873
+
874
+ CHAR_CAST_MAPPING = dict.fromkeys(
875
+ (
876
+ exp.DataType.Type.LONGTEXT,
877
+ exp.DataType.Type.LONGBLOB,
878
+ exp.DataType.Type.MEDIUMBLOB,
879
+ exp.DataType.Type.MEDIUMTEXT,
880
+ exp.DataType.Type.TEXT,
881
+ exp.DataType.Type.TINYBLOB,
882
+ exp.DataType.Type.TINYTEXT,
883
+ exp.DataType.Type.VARCHAR,
884
+ ),
885
+ "CHAR",
886
+ )
887
+ SIGNED_CAST_MAPPING = dict.fromkeys(
888
+ (
889
+ exp.DataType.Type.BIGINT,
890
+ exp.DataType.Type.BOOLEAN,
891
+ exp.DataType.Type.INT,
892
+ exp.DataType.Type.SMALLINT,
893
+ exp.DataType.Type.TINYINT,
894
+ exp.DataType.Type.MEDIUMINT,
895
+ ),
896
+ "SIGNED",
897
+ )
898
+
899
+ # MySQL doesn't support many datatypes in cast.
900
+ # https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast
901
+ CAST_MAPPING = {
902
+ **CHAR_CAST_MAPPING,
903
+ **SIGNED_CAST_MAPPING,
904
+ exp.DataType.Type.UBIGINT: "UNSIGNED",
905
+ }
906
+
907
+ TIMESTAMP_FUNC_TYPES = {
908
+ exp.DataType.Type.TIMESTAMPTZ,
909
+ exp.DataType.Type.TIMESTAMPLTZ,
910
+ }
911
+
912
+ # https://dev.mysql.com/doc/refman/8.0/en/keywords.html
913
+ RESERVED_KEYWORDS = {
914
+ "accessible",
915
+ "add",
916
+ "all",
917
+ "alter",
918
+ "analyze",
919
+ "and",
920
+ "as",
921
+ "asc",
922
+ "asensitive",
923
+ "before",
924
+ "between",
925
+ "bigint",
926
+ "binary",
927
+ "blob",
928
+ "both",
929
+ "by",
930
+ "call",
931
+ "cascade",
932
+ "case",
933
+ "change",
934
+ "char",
935
+ "character",
936
+ "check",
937
+ "collate",
938
+ "column",
939
+ "condition",
940
+ "constraint",
941
+ "continue",
942
+ "convert",
943
+ "create",
944
+ "cross",
945
+ "cube",
946
+ "cume_dist",
947
+ "current_date",
948
+ "current_time",
949
+ "current_timestamp",
950
+ "current_user",
951
+ "cursor",
952
+ "database",
953
+ "databases",
954
+ "day_hour",
955
+ "day_microsecond",
956
+ "day_minute",
957
+ "day_second",
958
+ "dec",
959
+ "decimal",
960
+ "declare",
961
+ "default",
962
+ "delayed",
963
+ "delete",
964
+ "dense_rank",
965
+ "desc",
966
+ "describe",
967
+ "deterministic",
968
+ "distinct",
969
+ "distinctrow",
970
+ "div",
971
+ "double",
972
+ "drop",
973
+ "dual",
974
+ "each",
975
+ "else",
976
+ "elseif",
977
+ "empty",
978
+ "enclosed",
979
+ "escaped",
980
+ "except",
981
+ "exists",
982
+ "exit",
983
+ "explain",
984
+ "false",
985
+ "fetch",
986
+ "first_value",
987
+ "float",
988
+ "float4",
989
+ "float8",
990
+ "for",
991
+ "force",
992
+ "foreign",
993
+ "from",
994
+ "fulltext",
995
+ "function",
996
+ "generated",
997
+ "get",
998
+ "grant",
999
+ "group",
1000
+ "grouping",
1001
+ "groups",
1002
+ "having",
1003
+ "high_priority",
1004
+ "hour_microsecond",
1005
+ "hour_minute",
1006
+ "hour_second",
1007
+ "if",
1008
+ "ignore",
1009
+ "in",
1010
+ "index",
1011
+ "infile",
1012
+ "inner",
1013
+ "inout",
1014
+ "insensitive",
1015
+ "insert",
1016
+ "int",
1017
+ "int1",
1018
+ "int2",
1019
+ "int3",
1020
+ "int4",
1021
+ "int8",
1022
+ "integer",
1023
+ "intersect",
1024
+ "interval",
1025
+ "into",
1026
+ "io_after_gtids",
1027
+ "io_before_gtids",
1028
+ "is",
1029
+ "iterate",
1030
+ "join",
1031
+ "json_table",
1032
+ "key",
1033
+ "keys",
1034
+ "kill",
1035
+ "lag",
1036
+ "last_value",
1037
+ "lateral",
1038
+ "lead",
1039
+ "leading",
1040
+ "leave",
1041
+ "left",
1042
+ "like",
1043
+ "limit",
1044
+ "linear",
1045
+ "lines",
1046
+ "load",
1047
+ "localtime",
1048
+ "localtimestamp",
1049
+ "lock",
1050
+ "long",
1051
+ "longblob",
1052
+ "longtext",
1053
+ "loop",
1054
+ "low_priority",
1055
+ "master_bind",
1056
+ "master_ssl_verify_server_cert",
1057
+ "match",
1058
+ "maxvalue",
1059
+ "mediumblob",
1060
+ "mediumint",
1061
+ "mediumtext",
1062
+ "middleint",
1063
+ "minute_microsecond",
1064
+ "minute_second",
1065
+ "mod",
1066
+ "modifies",
1067
+ "natural",
1068
+ "not",
1069
+ "no_write_to_binlog",
1070
+ "nth_value",
1071
+ "ntile",
1072
+ "null",
1073
+ "numeric",
1074
+ "of",
1075
+ "on",
1076
+ "optimize",
1077
+ "optimizer_costs",
1078
+ "option",
1079
+ "optionally",
1080
+ "or",
1081
+ "order",
1082
+ "out",
1083
+ "outer",
1084
+ "outfile",
1085
+ "over",
1086
+ "partition",
1087
+ "percent_rank",
1088
+ "precision",
1089
+ "primary",
1090
+ "procedure",
1091
+ "purge",
1092
+ "range",
1093
+ "rank",
1094
+ "read",
1095
+ "reads",
1096
+ "read_write",
1097
+ "real",
1098
+ "recursive",
1099
+ "references",
1100
+ "regexp",
1101
+ "release",
1102
+ "rename",
1103
+ "repeat",
1104
+ "replace",
1105
+ "require",
1106
+ "resignal",
1107
+ "restrict",
1108
+ "return",
1109
+ "revoke",
1110
+ "right",
1111
+ "rlike",
1112
+ "row",
1113
+ "rows",
1114
+ "row_number",
1115
+ "schema",
1116
+ "schemas",
1117
+ "second_microsecond",
1118
+ "select",
1119
+ "sensitive",
1120
+ "separator",
1121
+ "set",
1122
+ "show",
1123
+ "signal",
1124
+ "smallint",
1125
+ "spatial",
1126
+ "specific",
1127
+ "sql",
1128
+ "sqlexception",
1129
+ "sqlstate",
1130
+ "sqlwarning",
1131
+ "sql_big_result",
1132
+ "sql_calc_found_rows",
1133
+ "sql_small_result",
1134
+ "ssl",
1135
+ "starting",
1136
+ "stored",
1137
+ "straight_join",
1138
+ "system",
1139
+ "table",
1140
+ "terminated",
1141
+ "then",
1142
+ "tinyblob",
1143
+ "tinyint",
1144
+ "tinytext",
1145
+ "to",
1146
+ "trailing",
1147
+ "trigger",
1148
+ "true",
1149
+ "undo",
1150
+ "union",
1151
+ "unique",
1152
+ "unlock",
1153
+ "unsigned",
1154
+ "update",
1155
+ "usage",
1156
+ "use",
1157
+ "using",
1158
+ "utc_date",
1159
+ "utc_time",
1160
+ "utc_timestamp",
1161
+ "values",
1162
+ "varbinary",
1163
+ "varchar",
1164
+ "varcharacter",
1165
+ "varying",
1166
+ "virtual",
1167
+ "when",
1168
+ "where",
1169
+ "while",
1170
+ "window",
1171
+ "with",
1172
+ "write",
1173
+ "xor",
1174
+ "year_month",
1175
+ "zerofill",
1176
+ }
1177
+
1178
+ def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1179
+ persisted = "STORED" if expression.args.get("persisted") else "VIRTUAL"
1180
+ return f"GENERATED ALWAYS AS ({self.sql(expression.this.unnest())}) {persisted}"
1181
+
1182
+ def array_sql(self, expression: exp.Array) -> str:
1183
+ self.unsupported("Arrays are not supported by MySQL")
1184
+ return self.function_fallback_sql(expression)
1185
+
1186
+ def arraycontainsall_sql(self, expression: exp.ArrayContainsAll) -> str:
1187
+ self.unsupported("Array operations are not supported by MySQL")
1188
+ return self.function_fallback_sql(expression)
1189
+
1190
+ def dpipe_sql(self, expression: exp.DPipe) -> str:
1191
+ return self.func("CONCAT", *expression.flatten())
1192
+
1193
+ def extract_sql(self, expression: exp.Extract) -> str:
1194
+ unit = expression.name
1195
+ if unit and unit.lower() == "epoch":
1196
+ return self.func("UNIX_TIMESTAMP", expression.expression)
1197
+
1198
+ return super().extract_sql(expression)
1199
+
1200
+ def datatype_sql(self, expression: exp.DataType) -> str:
1201
+ if (
1202
+ self.VARCHAR_REQUIRES_SIZE
1203
+ and expression.is_type(exp.DataType.Type.VARCHAR)
1204
+ and not expression.expressions
1205
+ ):
1206
+ # `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT`
1207
+ return "TEXT"
1208
+
1209
+ # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html
1210
+ result = super().datatype_sql(expression)
1211
+ if expression.this in self.UNSIGNED_TYPE_MAPPING:
1212
+ result = f"{result} UNSIGNED"
1213
+
1214
+ return result
1215
+
1216
+ def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str:
1217
+ return f"{self.sql(expression, 'this')} MEMBER OF({self.sql(expression, 'expression')})"
1218
+
1219
+ def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
1220
+ if expression.to.this in self.TIMESTAMP_FUNC_TYPES:
1221
+ return self.func("TIMESTAMP", expression.this)
1222
+
1223
+ to = self.CAST_MAPPING.get(expression.to.this)
1224
+
1225
+ if to:
1226
+ expression.to.set("this", to)
1227
+ return super().cast_sql(expression)
1228
+
1229
+ def show_sql(self, expression: exp.Show) -> str:
1230
+ this = f" {expression.name}"
1231
+ full = " FULL" if expression.args.get("full") else ""
1232
+ global_ = " GLOBAL" if expression.args.get("global") else ""
1233
+
1234
+ target = self.sql(expression, "target")
1235
+ target = f" {target}" if target else ""
1236
+ if expression.name in ("COLUMNS", "INDEX"):
1237
+ target = f" FROM{target}"
1238
+ elif expression.name == "GRANTS":
1239
+ target = f" FOR{target}"
1240
+
1241
+ db = self._prefixed_sql("FROM", expression, "db")
1242
+
1243
+ like = self._prefixed_sql("LIKE", expression, "like")
1244
+ where = self.sql(expression, "where")
1245
+
1246
+ types = self.expressions(expression, key="types")
1247
+ types = f" {types}" if types else types
1248
+ query = self._prefixed_sql("FOR QUERY", expression, "query")
1249
+
1250
+ if expression.name == "PROFILE":
1251
+ offset = self._prefixed_sql("OFFSET", expression, "offset")
1252
+ limit = self._prefixed_sql("LIMIT", expression, "limit")
1253
+ else:
1254
+ offset = ""
1255
+ limit = self._oldstyle_limit_sql(expression)
1256
+
1257
+ log = self._prefixed_sql("IN", expression, "log")
1258
+ position = self._prefixed_sql("FROM", expression, "position")
1259
+
1260
+ channel = self._prefixed_sql("FOR CHANNEL", expression, "channel")
1261
+
1262
+ if expression.name == "ENGINE":
1263
+ mutex_or_status = " MUTEX" if expression.args.get("mutex") else " STATUS"
1264
+ else:
1265
+ mutex_or_status = ""
1266
+
1267
+ return f"SHOW{full}{global_}{this}{target}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}"
1268
+
1269
+ def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
1270
+ dtype = self.sql(expression, "dtype")
1271
+ if not dtype:
1272
+ return super().altercolumn_sql(expression)
1273
+
1274
+ this = self.sql(expression, "this")
1275
+ return f"MODIFY COLUMN {this} {dtype}"
1276
+
1277
+ def _prefixed_sql(self, prefix: str, expression: exp.Expression, arg: str) -> str:
1278
+ sql = self.sql(expression, arg)
1279
+ return f" {prefix} {sql}" if sql else ""
1280
+
1281
+ def _oldstyle_limit_sql(self, expression: exp.Show) -> str:
1282
+ limit = self.sql(expression, "limit")
1283
+ offset = self.sql(expression, "offset")
1284
+ if limit:
1285
+ limit_offset = f"{offset}, {limit}" if offset else limit
1286
+ return f" LIMIT {limit_offset}"
1287
+ return ""
1288
+
1289
+ def chr_sql(self, expression: exp.Chr) -> str:
1290
+ this = self.expressions(sqls=[expression.this] + expression.expressions)
1291
+ charset = expression.args.get("charset")
1292
+ using = f" USING {self.sql(charset)}" if charset else ""
1293
+ return f"CHAR({this}{using})"
1294
+
1295
+ def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str:
1296
+ unit = expression.args.get("unit")
1297
+
1298
+ # Pick an old-enough date to avoid negative timestamp diffs
1299
+ start_ts = "'0000-01-01 00:00:00'"
1300
+
1301
+ # Source: https://stackoverflow.com/a/32955740
1302
+ timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this])
1303
+ interval = exp.Interval(this=timestamp_diff, unit=unit)
1304
+ dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval])
1305
+
1306
+ return self.sql(dateadd)
1307
+
1308
+ def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
1309
+ from_tz = expression.args.get("source_tz")
1310
+ to_tz = expression.args.get("target_tz")
1311
+ dt = expression.args.get("timestamp")
1312
+
1313
+ return self.func("CONVERT_TZ", dt, from_tz, to_tz)
1314
+
1315
+ def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
1316
+ self.unsupported("AT TIME ZONE is not supported by MySQL")
1317
+ return self.sql(expression.this)
1318
+
1319
+ def isascii_sql(self, expression: exp.IsAscii) -> str:
1320
+ return f"REGEXP_LIKE({self.sql(expression.this)}, '^[[:ascii:]]*$')"
1321
+
1322
+ @unsupported_args("this")
1323
+ def currentschema_sql(self, expression: exp.CurrentSchema) -> str:
1324
+ return self.func("SCHEMA")