sqlglot.dialects.mysql
1from __future__ import annotations 2 3import typing as t 4 5from sqlglot import exp, generator, parser, tokens, transforms 6from 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) 30from sqlglot.generator import unsupported_args 31from sqlglot.helper import seq_get 32from sqlglot.tokens import TokenType 33from sqlglot.typing.mysql import EXPRESSION_METADATA 34 35 36def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[MySQL.Parser], exp.Show]: 37 def _parse(self: MySQL.Parser) -> exp.Show: 38 return self._parse_show_mysql(*args, **kwargs) 39 40 return _parse 41 42 43def _date_trunc_sql(self: MySQL.Generator, expression: exp.DateTrunc) -> str: 44 expr = self.sql(expression, "this") 45 unit = expression.text("unit").upper() 46 47 if unit == "WEEK": 48 concat = f"CONCAT(YEAR({expr}), ' ', WEEK({expr}, 1), ' 1')" 49 date_format = "%Y %u %w" 50 elif unit == "MONTH": 51 concat = f"CONCAT(YEAR({expr}), ' ', MONTH({expr}), ' 1')" 52 date_format = "%Y %c %e" 53 elif unit == "QUARTER": 54 concat = f"CONCAT(YEAR({expr}), ' ', QUARTER({expr}) * 3 - 2, ' 1')" 55 date_format = "%Y %c %e" 56 elif unit == "YEAR": 57 concat = f"CONCAT(YEAR({expr}), ' 1 1')" 58 date_format = "%Y %c %e" 59 else: 60 if unit != "DAY": 61 self.unsupported(f"Unexpected interval unit: {unit}") 62 return self.func("DATE", expr) 63 64 return self.func("STR_TO_DATE", concat, f"'{date_format}'") 65 66 67# All specifiers for time parts (as opposed to date parts) 68# https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format 69TIME_SPECIFIERS = {"f", "H", "h", "I", "i", "k", "l", "p", "r", "S", "s", "T"} 70 71 72def _has_time_specifier(date_format: str) -> bool: 73 i = 0 74 length = len(date_format) 75 76 while i < length: 77 if date_format[i] == "%": 78 i += 1 79 if i < length and date_format[i] in TIME_SPECIFIERS: 80 return True 81 i += 1 82 return False 83 84 85def _str_to_date(args: t.List) -> exp.StrToDate | exp.StrToTime: 86 mysql_date_format = seq_get(args, 1) 87 date_format = MySQL.format_time(mysql_date_format) 88 this = seq_get(args, 0) 89 90 if mysql_date_format and _has_time_specifier(mysql_date_format.name): 91 return exp.StrToTime(this=this, format=date_format) 92 93 return exp.StrToDate(this=this, format=date_format) 94 95 96def _str_to_date_sql( 97 self: MySQL.Generator, expression: exp.StrToDate | exp.StrToTime | exp.TsOrDsToDate 98) -> str: 99 return self.func("STR_TO_DATE", expression.this, self.format_time(expression)) 100 101 102def _unix_to_time_sql(self: MySQL.Generator, expression: exp.UnixToTime) -> str: 103 scale = expression.args.get("scale") 104 timestamp = expression.this 105 106 if scale in (None, exp.UnixToTime.SECONDS): 107 return self.func("FROM_UNIXTIME", timestamp, self.format_time(expression)) 108 109 return self.func( 110 "FROM_UNIXTIME", 111 exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)), 112 self.format_time(expression), 113 ) 114 115 116def date_add_sql( 117 kind: str, 118) -> t.Callable[[generator.Generator, exp.Expression], str]: 119 def func(self: generator.Generator, expression: exp.Expression) -> str: 120 return self.func( 121 f"DATE_{kind}", 122 expression.this, 123 exp.Interval(this=expression.expression, unit=unit_to_var(expression)), 124 ) 125 126 return func 127 128 129def _ts_or_ds_to_date_sql(self: MySQL.Generator, expression: exp.TsOrDsToDate) -> str: 130 time_format = expression.args.get("format") 131 return _str_to_date_sql(self, expression) if time_format else self.func("DATE", expression.this) 132 133 134def _remove_ts_or_ds_to_date( 135 to_sql: t.Optional[t.Callable[[MySQL.Generator, exp.Expression], str]] = None, 136 args: t.Tuple[str, ...] = ("this",), 137) -> t.Callable[[MySQL.Generator, exp.Func], str]: 138 def func(self: MySQL.Generator, expression: exp.Func) -> str: 139 for arg_key in args: 140 arg = expression.args.get(arg_key) 141 if isinstance(arg, exp.TsOrDsToDate) and not arg.args.get("format"): 142 expression.set(arg_key, arg.this) 143 144 return to_sql(self, expression) if to_sql else self.function_fallback_sql(expression) 145 146 return func 147 148 149class MySQL(Dialect): 150 PROMOTE_TO_INFERRED_DATETIME_TYPE = True 151 152 # https://dev.mysql.com/doc/refman/8.0/en/identifiers.html 153 IDENTIFIERS_CAN_START_WITH_DIGIT = True 154 155 # We default to treating all identifiers as case-sensitive, since it matches MySQL's 156 # behavior on Linux systems. For MacOS and Windows systems, one can override this 157 # setting by specifying `dialect="mysql, normalization_strategy = lowercase"`. 158 # 159 # See also https://dev.mysql.com/doc/refman/8.2/en/identifier-case-sensitivity.html 160 NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE 161 162 TIME_FORMAT = "'%Y-%m-%d %T'" 163 DPIPE_IS_STRING_CONCAT = False 164 SUPPORTS_USER_DEFINED_TYPES = False 165 SUPPORTS_SEMI_ANTI_JOIN = False 166 SAFE_DIVISION = True 167 SAFE_TO_ELIMINATE_DOUBLE_NEGATION = False 168 LEAST_GREATEST_IGNORES_NULLS = False 169 170 EXPRESSION_METADATA = EXPRESSION_METADATA.copy() 171 172 # https://prestodb.io/docs/current/functions/datetime.html#mysql-date-functions 173 TIME_MAPPING = { 174 "%M": "%B", 175 "%c": "%-m", 176 "%e": "%-d", 177 "%h": "%I", 178 "%i": "%M", 179 "%s": "%S", 180 "%u": "%W", 181 "%k": "%-H", 182 "%l": "%-I", 183 "%T": "%H:%M:%S", 184 "%W": "%A", 185 } 186 187 VALID_INTERVAL_UNITS = { 188 *Dialect.VALID_INTERVAL_UNITS, 189 "SECOND_MICROSECOND", 190 "MINUTE_MICROSECOND", 191 "MINUTE_SECOND", 192 "HOUR_MICROSECOND", 193 "HOUR_SECOND", 194 "HOUR_MINUTE", 195 "DAY_MICROSECOND", 196 "DAY_SECOND", 197 "DAY_MINUTE", 198 "DAY_HOUR", 199 "YEAR_MONTH", 200 } 201 202 class Tokenizer(tokens.Tokenizer): 203 QUOTES = ["'", '"'] 204 COMMENTS = ["--", "#", ("/*", "*/")] 205 IDENTIFIERS = ["`"] 206 STRING_ESCAPES = ["'", '"', "\\"] 207 BIT_STRINGS = [("b'", "'"), ("B'", "'"), ("0b", "")] 208 HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", "")] 209 # https://dev.mysql.com/doc/refman/8.4/en/string-literals.html 210 ESCAPE_FOLLOW_CHARS = ["0", "b", "n", "r", "t", "Z", "%", "_"] 211 212 NESTED_COMMENTS = False 213 214 KEYWORDS = { 215 **tokens.Tokenizer.KEYWORDS, 216 "BLOB": TokenType.BLOB, 217 "CHARSET": TokenType.CHARACTER_SET, 218 "DISTINCTROW": TokenType.DISTINCT, 219 "EXPLAIN": TokenType.DESCRIBE, 220 "FORCE": TokenType.FORCE, 221 "IGNORE": TokenType.IGNORE, 222 "KEY": TokenType.KEY, 223 "LOCK TABLES": TokenType.COMMAND, 224 "LONGBLOB": TokenType.LONGBLOB, 225 "LONGTEXT": TokenType.LONGTEXT, 226 "MEDIUMBLOB": TokenType.MEDIUMBLOB, 227 "MEDIUMINT": TokenType.MEDIUMINT, 228 "MEDIUMTEXT": TokenType.MEDIUMTEXT, 229 "MEMBER OF": TokenType.MEMBER_OF, 230 "MOD": TokenType.MOD, 231 "SEPARATOR": TokenType.SEPARATOR, 232 "SERIAL": TokenType.SERIAL, 233 "SIGNED": TokenType.BIGINT, 234 "SIGNED INTEGER": TokenType.BIGINT, 235 "SOUNDS LIKE": TokenType.SOUNDS_LIKE, 236 "START": TokenType.BEGIN, 237 "TIMESTAMP": TokenType.TIMESTAMPTZ, 238 "TINYBLOB": TokenType.TINYBLOB, 239 "TINYTEXT": TokenType.TINYTEXT, 240 "UNLOCK TABLES": TokenType.COMMAND, 241 "UNSIGNED": TokenType.UBIGINT, 242 "UNSIGNED INTEGER": TokenType.UBIGINT, 243 "YEAR": TokenType.YEAR, 244 "_ARMSCII8": TokenType.INTRODUCER, 245 "_ASCII": TokenType.INTRODUCER, 246 "_BIG5": TokenType.INTRODUCER, 247 "_BINARY": TokenType.INTRODUCER, 248 "_CP1250": TokenType.INTRODUCER, 249 "_CP1251": TokenType.INTRODUCER, 250 "_CP1256": TokenType.INTRODUCER, 251 "_CP1257": TokenType.INTRODUCER, 252 "_CP850": TokenType.INTRODUCER, 253 "_CP852": TokenType.INTRODUCER, 254 "_CP866": TokenType.INTRODUCER, 255 "_CP932": TokenType.INTRODUCER, 256 "_DEC8": TokenType.INTRODUCER, 257 "_EUCJPMS": TokenType.INTRODUCER, 258 "_EUCKR": TokenType.INTRODUCER, 259 "_GB18030": TokenType.INTRODUCER, 260 "_GB2312": TokenType.INTRODUCER, 261 "_GBK": TokenType.INTRODUCER, 262 "_GEOSTD8": TokenType.INTRODUCER, 263 "_GREEK": TokenType.INTRODUCER, 264 "_HEBREW": TokenType.INTRODUCER, 265 "_HP8": TokenType.INTRODUCER, 266 "_KEYBCS2": TokenType.INTRODUCER, 267 "_KOI8R": TokenType.INTRODUCER, 268 "_KOI8U": TokenType.INTRODUCER, 269 "_LATIN1": TokenType.INTRODUCER, 270 "_LATIN2": TokenType.INTRODUCER, 271 "_LATIN5": TokenType.INTRODUCER, 272 "_LATIN7": TokenType.INTRODUCER, 273 "_MACCE": TokenType.INTRODUCER, 274 "_MACROMAN": TokenType.INTRODUCER, 275 "_SJIS": TokenType.INTRODUCER, 276 "_SWE7": TokenType.INTRODUCER, 277 "_TIS620": TokenType.INTRODUCER, 278 "_UCS2": TokenType.INTRODUCER, 279 "_UJIS": TokenType.INTRODUCER, 280 # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html 281 "_UTF8": TokenType.INTRODUCER, 282 "_UTF16": TokenType.INTRODUCER, 283 "_UTF16LE": TokenType.INTRODUCER, 284 "_UTF32": TokenType.INTRODUCER, 285 "_UTF8MB3": TokenType.INTRODUCER, 286 "_UTF8MB4": TokenType.INTRODUCER, 287 "@@": TokenType.SESSION_PARAMETER, 288 } 289 290 COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE} - {TokenType.SHOW} 291 292 class Parser(parser.Parser): 293 FUNC_TOKENS = { 294 *parser.Parser.FUNC_TOKENS, 295 TokenType.DATABASE, 296 TokenType.MOD, 297 TokenType.SCHEMA, 298 TokenType.VALUES, 299 TokenType.CHARACTER_SET, 300 } 301 302 CONJUNCTION = { 303 **parser.Parser.CONJUNCTION, 304 TokenType.DAMP: exp.And, 305 TokenType.XOR: exp.Xor, 306 } 307 308 DISJUNCTION = { 309 **parser.Parser.DISJUNCTION, 310 TokenType.DPIPE: exp.Or, 311 } 312 313 TABLE_ALIAS_TOKENS = ( 314 parser.Parser.TABLE_ALIAS_TOKENS - parser.Parser.TABLE_INDEX_HINT_TOKENS 315 ) 316 317 RANGE_PARSERS = { 318 **parser.Parser.RANGE_PARSERS, 319 TokenType.SOUNDS_LIKE: lambda self, this: self.expression( 320 exp.EQ, 321 this=self.expression(exp.Soundex, this=this), 322 expression=self.expression(exp.Soundex, this=self._parse_term()), 323 ), 324 TokenType.MEMBER_OF: lambda self, this: self.expression( 325 exp.JSONArrayContains, 326 this=this, 327 expression=self._parse_wrapped(self._parse_expression), 328 ), 329 } 330 331 FUNCTIONS = { 332 **parser.Parser.FUNCTIONS, 333 "BIT_AND": exp.BitwiseAndAgg.from_arg_list, 334 "BIT_OR": exp.BitwiseOrAgg.from_arg_list, 335 "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, 336 "BIT_COUNT": exp.BitwiseCount.from_arg_list, 337 "CONVERT_TZ": lambda args: exp.ConvertTimezone( 338 source_tz=seq_get(args, 1), target_tz=seq_get(args, 2), timestamp=seq_get(args, 0) 339 ), 340 "CURDATE": exp.CurrentDate.from_arg_list, 341 "DATE": lambda args: exp.TsOrDsToDate(this=seq_get(args, 0)), 342 "DATE_ADD": build_date_delta_with_interval(exp.DateAdd), 343 "DATE_FORMAT": build_formatted_time(exp.TimeToStr, "mysql"), 344 "DATE_SUB": build_date_delta_with_interval(exp.DateSub), 345 "DAY": lambda args: exp.Day(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 346 "DAYOFMONTH": lambda args: exp.DayOfMonth(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 347 "DAYOFWEEK": lambda args: exp.DayOfWeek(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 348 "DAYOFYEAR": lambda args: exp.DayOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 349 "FORMAT": exp.NumberToStr.from_arg_list, 350 "FROM_UNIXTIME": build_formatted_time(exp.UnixToTime, "mysql"), 351 "ISNULL": isnull_to_is_null, 352 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 353 "MAKETIME": exp.TimeFromParts.from_arg_list, 354 "MONTH": lambda args: exp.Month(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 355 "MONTHNAME": lambda args: exp.TimeToStr( 356 this=exp.TsOrDsToDate(this=seq_get(args, 0)), 357 format=exp.Literal.string("%B"), 358 ), 359 "SCHEMA": exp.CurrentSchema.from_arg_list, 360 "DATABASE": exp.CurrentSchema.from_arg_list, 361 "STR_TO_DATE": _str_to_date, 362 "TIMESTAMPDIFF": build_date_delta(exp.TimestampDiff), 363 "TO_DAYS": lambda args: exp.paren( 364 exp.DateDiff( 365 this=exp.TsOrDsToDate(this=seq_get(args, 0)), 366 expression=exp.TsOrDsToDate(this=exp.Literal.string("0000-01-01")), 367 unit=exp.var("DAY"), 368 ) 369 + 1 370 ), 371 "WEEK": lambda args: exp.Week( 372 this=exp.TsOrDsToDate(this=seq_get(args, 0)), mode=seq_get(args, 1) 373 ), 374 "WEEKOFYEAR": lambda args: exp.WeekOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 375 "YEAR": lambda args: exp.Year(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 376 } 377 378 FUNCTION_PARSERS = { 379 **parser.Parser.FUNCTION_PARSERS, 380 "GROUP_CONCAT": lambda self: self._parse_group_concat(), 381 # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values 382 "VALUES": lambda self: self.expression( 383 exp.Anonymous, this="VALUES", expressions=[self._parse_id_var()] 384 ), 385 "JSON_VALUE": lambda self: self._parse_json_value(), 386 "SUBSTR": lambda self: self._parse_substring(), 387 } 388 389 STATEMENT_PARSERS = { 390 **parser.Parser.STATEMENT_PARSERS, 391 TokenType.SHOW: lambda self: self._parse_show(), 392 } 393 394 SHOW_PARSERS = { 395 "BINARY LOGS": _show_parser("BINARY LOGS"), 396 "MASTER LOGS": _show_parser("BINARY LOGS"), 397 "BINLOG EVENTS": _show_parser("BINLOG EVENTS"), 398 "CHARACTER SET": _show_parser("CHARACTER SET"), 399 "CHARSET": _show_parser("CHARACTER SET"), 400 "COLLATION": _show_parser("COLLATION"), 401 "FULL COLUMNS": _show_parser("COLUMNS", target="FROM", full=True), 402 "COLUMNS": _show_parser("COLUMNS", target="FROM"), 403 "CREATE DATABASE": _show_parser("CREATE DATABASE", target=True), 404 "CREATE EVENT": _show_parser("CREATE EVENT", target=True), 405 "CREATE FUNCTION": _show_parser("CREATE FUNCTION", target=True), 406 "CREATE PROCEDURE": _show_parser("CREATE PROCEDURE", target=True), 407 "CREATE TABLE": _show_parser("CREATE TABLE", target=True), 408 "CREATE TRIGGER": _show_parser("CREATE TRIGGER", target=True), 409 "CREATE VIEW": _show_parser("CREATE VIEW", target=True), 410 "DATABASES": _show_parser("DATABASES"), 411 "SCHEMAS": _show_parser("DATABASES"), 412 "ENGINE": _show_parser("ENGINE", target=True), 413 "STORAGE ENGINES": _show_parser("ENGINES"), 414 "ENGINES": _show_parser("ENGINES"), 415 "ERRORS": _show_parser("ERRORS"), 416 "EVENTS": _show_parser("EVENTS"), 417 "FUNCTION CODE": _show_parser("FUNCTION CODE", target=True), 418 "FUNCTION STATUS": _show_parser("FUNCTION STATUS"), 419 "GRANTS": _show_parser("GRANTS", target="FOR"), 420 "INDEX": _show_parser("INDEX", target="FROM"), 421 "MASTER STATUS": _show_parser("MASTER STATUS"), 422 "OPEN TABLES": _show_parser("OPEN TABLES"), 423 "PLUGINS": _show_parser("PLUGINS"), 424 "PROCEDURE CODE": _show_parser("PROCEDURE CODE", target=True), 425 "PROCEDURE STATUS": _show_parser("PROCEDURE STATUS"), 426 "PRIVILEGES": _show_parser("PRIVILEGES"), 427 "FULL PROCESSLIST": _show_parser("PROCESSLIST", full=True), 428 "PROCESSLIST": _show_parser("PROCESSLIST"), 429 "PROFILE": _show_parser("PROFILE"), 430 "PROFILES": _show_parser("PROFILES"), 431 "RELAYLOG EVENTS": _show_parser("RELAYLOG EVENTS"), 432 "REPLICAS": _show_parser("REPLICAS"), 433 "SLAVE HOSTS": _show_parser("REPLICAS"), 434 "REPLICA STATUS": _show_parser("REPLICA STATUS"), 435 "SLAVE STATUS": _show_parser("REPLICA STATUS"), 436 "GLOBAL STATUS": _show_parser("STATUS", global_=True), 437 "SESSION STATUS": _show_parser("STATUS"), 438 "STATUS": _show_parser("STATUS"), 439 "TABLE STATUS": _show_parser("TABLE STATUS"), 440 "FULL TABLES": _show_parser("TABLES", full=True), 441 "TABLES": _show_parser("TABLES"), 442 "TRIGGERS": _show_parser("TRIGGERS"), 443 "GLOBAL VARIABLES": _show_parser("VARIABLES", global_=True), 444 "SESSION VARIABLES": _show_parser("VARIABLES"), 445 "VARIABLES": _show_parser("VARIABLES"), 446 "WARNINGS": _show_parser("WARNINGS"), 447 } 448 449 PROPERTY_PARSERS = { 450 **parser.Parser.PROPERTY_PARSERS, 451 "LOCK": lambda self: self._parse_property_assignment(exp.LockProperty), 452 "PARTITION BY": lambda self: self._parse_partition_property(), 453 } 454 455 SET_PARSERS = { 456 **parser.Parser.SET_PARSERS, 457 "PERSIST": lambda self: self._parse_set_item_assignment("PERSIST"), 458 "PERSIST_ONLY": lambda self: self._parse_set_item_assignment("PERSIST_ONLY"), 459 "CHARACTER SET": lambda self: self._parse_set_item_charset("CHARACTER SET"), 460 "CHARSET": lambda self: self._parse_set_item_charset("CHARACTER SET"), 461 "NAMES": lambda self: self._parse_set_item_names(), 462 } 463 464 CONSTRAINT_PARSERS = { 465 **parser.Parser.CONSTRAINT_PARSERS, 466 "FULLTEXT": lambda self: self._parse_index_constraint(kind="FULLTEXT"), 467 "INDEX": lambda self: self._parse_index_constraint(), 468 "KEY": lambda self: self._parse_index_constraint(), 469 "SPATIAL": lambda self: self._parse_index_constraint(kind="SPATIAL"), 470 "ZEROFILL": lambda self: self.expression(exp.ZeroFillColumnConstraint), 471 } 472 473 ALTER_PARSERS = { 474 **parser.Parser.ALTER_PARSERS, 475 "MODIFY": lambda self: self._parse_alter_table_alter(), 476 } 477 478 ALTER_ALTER_PARSERS = { 479 **parser.Parser.ALTER_ALTER_PARSERS, 480 "INDEX": lambda self: self._parse_alter_table_alter_index(), 481 } 482 483 SCHEMA_UNNAMED_CONSTRAINTS = { 484 *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS, 485 "FULLTEXT", 486 "INDEX", 487 "KEY", 488 "SPATIAL", 489 } 490 491 PROFILE_TYPES: parser.OPTIONS_TYPE = { 492 **dict.fromkeys(("ALL", "CPU", "IPC", "MEMORY", "SOURCE", "SWAPS"), tuple()), 493 "BLOCK": ("IO",), 494 "CONTEXT": ("SWITCHES",), 495 "PAGE": ("FAULTS",), 496 } 497 498 TYPE_TOKENS = { 499 *parser.Parser.TYPE_TOKENS, 500 TokenType.SET, 501 } 502 503 ENUM_TYPE_TOKENS = { 504 *parser.Parser.ENUM_TYPE_TOKENS, 505 TokenType.SET, 506 } 507 508 # SELECT [ ALL | DISTINCT | DISTINCTROW ] [ <OPERATION_MODIFIERS> ] 509 OPERATION_MODIFIERS = { 510 "HIGH_PRIORITY", 511 "STRAIGHT_JOIN", 512 "SQL_SMALL_RESULT", 513 "SQL_BIG_RESULT", 514 "SQL_BUFFER_RESULT", 515 "SQL_NO_CACHE", 516 "SQL_CALC_FOUND_ROWS", 517 } 518 519 LOG_DEFAULTS_TO_LN = True 520 STRING_ALIASES = True 521 VALUES_FOLLOWED_BY_PAREN = False 522 SUPPORTS_PARTITION_SELECTION = True 523 524 def _parse_generated_as_identity( 525 self, 526 ) -> ( 527 exp.GeneratedAsIdentityColumnConstraint 528 | exp.ComputedColumnConstraint 529 | exp.GeneratedAsRowColumnConstraint 530 ): 531 this = super()._parse_generated_as_identity() 532 533 if self._match_texts(("STORED", "VIRTUAL")): 534 persisted = self._prev.text.upper() == "STORED" 535 536 if isinstance(this, exp.ComputedColumnConstraint): 537 this.set("persisted", persisted) 538 elif isinstance(this, exp.GeneratedAsIdentityColumnConstraint): 539 this = self.expression( 540 exp.ComputedColumnConstraint, this=this.expression, persisted=persisted 541 ) 542 543 return this 544 545 def _parse_primary_key_part(self) -> t.Optional[exp.Expression]: 546 this = self._parse_id_var() 547 if not self._match(TokenType.L_PAREN): 548 return this 549 550 expression = self._parse_number() 551 self._match_r_paren() 552 return self.expression(exp.ColumnPrefix, this=this, expression=expression) 553 554 def _parse_index_constraint( 555 self, kind: t.Optional[str] = None 556 ) -> exp.IndexColumnConstraint: 557 if kind: 558 self._match_texts(("INDEX", "KEY")) 559 560 this = self._parse_id_var(any_token=False) 561 index_type = self._match(TokenType.USING) and self._advance_any() and self._prev.text 562 expressions = self._parse_wrapped_csv(self._parse_ordered) 563 564 options = [] 565 while True: 566 if self._match_text_seq("KEY_BLOCK_SIZE"): 567 self._match(TokenType.EQ) 568 opt = exp.IndexConstraintOption(key_block_size=self._parse_number()) 569 elif self._match(TokenType.USING): 570 opt = exp.IndexConstraintOption(using=self._advance_any() and self._prev.text) 571 elif self._match_text_seq("WITH", "PARSER"): 572 opt = exp.IndexConstraintOption(parser=self._parse_var(any_token=True)) 573 elif self._match(TokenType.COMMENT): 574 opt = exp.IndexConstraintOption(comment=self._parse_string()) 575 elif self._match_text_seq("VISIBLE"): 576 opt = exp.IndexConstraintOption(visible=True) 577 elif self._match_text_seq("INVISIBLE"): 578 opt = exp.IndexConstraintOption(visible=False) 579 elif self._match_text_seq("ENGINE_ATTRIBUTE"): 580 self._match(TokenType.EQ) 581 opt = exp.IndexConstraintOption(engine_attr=self._parse_string()) 582 elif self._match_text_seq("SECONDARY_ENGINE_ATTRIBUTE"): 583 self._match(TokenType.EQ) 584 opt = exp.IndexConstraintOption(secondary_engine_attr=self._parse_string()) 585 else: 586 opt = None 587 588 if not opt: 589 break 590 591 options.append(opt) 592 593 return self.expression( 594 exp.IndexColumnConstraint, 595 this=this, 596 expressions=expressions, 597 kind=kind, 598 index_type=index_type, 599 options=options, 600 ) 601 602 def _parse_show_mysql( 603 self, 604 this: str, 605 target: bool | str = False, 606 full: t.Optional[bool] = None, 607 global_: t.Optional[bool] = None, 608 ) -> exp.Show: 609 json = self._match_text_seq("JSON") 610 611 if target: 612 if isinstance(target, str): 613 self._match_text_seq(*target.split(" ")) 614 target_id = self._parse_id_var() 615 else: 616 target_id = None 617 618 log = self._parse_string() if self._match_text_seq("IN") else None 619 620 if this in ("BINLOG EVENTS", "RELAYLOG EVENTS"): 621 position = self._parse_number() if self._match_text_seq("FROM") else None 622 db = None 623 else: 624 position = None 625 db = None 626 627 if self._match(TokenType.FROM): 628 db = self._parse_id_var() 629 elif self._match(TokenType.DOT): 630 db = target_id 631 target_id = self._parse_id_var() 632 633 channel = self._parse_id_var() if self._match_text_seq("FOR", "CHANNEL") else None 634 635 like = self._parse_string() if self._match_text_seq("LIKE") else None 636 where = self._parse_where() 637 638 if this == "PROFILE": 639 types = self._parse_csv(lambda: self._parse_var_from_options(self.PROFILE_TYPES)) 640 query = self._parse_number() if self._match_text_seq("FOR", "QUERY") else None 641 offset = self._parse_number() if self._match_text_seq("OFFSET") else None 642 limit = self._parse_number() if self._match_text_seq("LIMIT") else None 643 else: 644 types, query = None, None 645 offset, limit = self._parse_oldstyle_limit() 646 647 mutex = True if self._match_text_seq("MUTEX") else None 648 mutex = False if self._match_text_seq("STATUS") else mutex 649 650 for_table = self._parse_id_var() if self._match_text_seq("FOR", "TABLE") else None 651 for_group = self._parse_string() if self._match_text_seq("FOR", "GROUP") else None 652 for_user = self._parse_string() if self._match_text_seq("FOR", "USER") else None 653 for_role = self._parse_string() if self._match_text_seq("FOR", "ROLE") else None 654 into_outfile = self._parse_string() if self._match_text_seq("INTO", "OUTFILE") else None 655 656 return self.expression( 657 exp.Show, 658 this=this, 659 target=target_id, 660 full=full, 661 log=log, 662 position=position, 663 db=db, 664 channel=channel, 665 like=like, 666 where=where, 667 types=types, 668 query=query, 669 offset=offset, 670 limit=limit, 671 mutex=mutex, 672 for_table=for_table, 673 for_group=for_group, 674 for_user=for_user, 675 for_role=for_role, 676 into_outfile=into_outfile, 677 json=json, 678 global_=global_, 679 ) 680 681 def _parse_oldstyle_limit( 682 self, 683 ) -> t.Tuple[t.Optional[exp.Expression], t.Optional[exp.Expression]]: 684 limit = None 685 offset = None 686 if self._match_text_seq("LIMIT"): 687 parts = self._parse_csv(self._parse_number) 688 if len(parts) == 1: 689 limit = parts[0] 690 elif len(parts) == 2: 691 limit = parts[1] 692 offset = parts[0] 693 694 return offset, limit 695 696 def _parse_set_item_charset(self, kind: str) -> exp.Expression: 697 this = self._parse_string() or self._parse_unquoted_field() 698 return self.expression(exp.SetItem, this=this, kind=kind) 699 700 def _parse_set_item_names(self) -> exp.Expression: 701 charset = self._parse_string() or self._parse_unquoted_field() 702 if self._match_text_seq("COLLATE"): 703 collate = self._parse_string() or self._parse_unquoted_field() 704 else: 705 collate = None 706 707 return self.expression(exp.SetItem, this=charset, collate=collate, kind="NAMES") 708 709 def _parse_type( 710 self, parse_interval: bool = True, fallback_to_identifier: bool = False 711 ) -> t.Optional[exp.Expression]: 712 # mysql binary is special and can work anywhere, even in order by operations 713 # it operates like a no paren func 714 if self._match(TokenType.BINARY, advance=False): 715 data_type = self._parse_types(check_func=True, allow_identifiers=False) 716 717 if isinstance(data_type, exp.DataType): 718 return self.expression(exp.Cast, this=self._parse_column(), to=data_type) 719 720 return super()._parse_type( 721 parse_interval=parse_interval, fallback_to_identifier=fallback_to_identifier 722 ) 723 724 def _parse_alter_table_alter_index(self) -> exp.AlterIndex: 725 index = self._parse_field(any_token=True) 726 727 if self._match_text_seq("VISIBLE"): 728 visible = True 729 elif self._match_text_seq("INVISIBLE"): 730 visible = False 731 else: 732 visible = None 733 734 return self.expression(exp.AlterIndex, this=index, visible=visible) 735 736 def _parse_partition_property( 737 self, 738 ) -> t.Optional[exp.Expression] | t.List[exp.Expression]: 739 partition_cls: t.Optional[t.Type[exp.Expression]] = None 740 value_parser = None 741 742 if self._match_text_seq("RANGE"): 743 partition_cls = exp.PartitionByRangeProperty 744 value_parser = self._parse_partition_range_value 745 elif self._match_text_seq("LIST"): 746 partition_cls = exp.PartitionByListProperty 747 value_parser = self._parse_partition_list_value 748 749 if not partition_cls or not value_parser: 750 return None 751 752 partition_expressions = self._parse_wrapped_csv(self._parse_assignment) 753 754 # For Doris and Starrocks 755 if not self._match_text_seq("(", "PARTITION", advance=False): 756 return partition_expressions 757 758 create_expressions = self._parse_wrapped_csv(value_parser) 759 760 return self.expression( 761 partition_cls, 762 partition_expressions=partition_expressions, 763 create_expressions=create_expressions, 764 ) 765 766 def _parse_partition_range_value(self) -> t.Optional[exp.Expression]: 767 self._match_text_seq("PARTITION") 768 name = self._parse_id_var() 769 770 if not self._match_text_seq("VALUES", "LESS", "THAN"): 771 return name 772 773 values = self._parse_wrapped_csv(self._parse_expression) 774 775 if ( 776 len(values) == 1 777 and isinstance(values[0], exp.Column) 778 and values[0].name.upper() == "MAXVALUE" 779 ): 780 values = [exp.var("MAXVALUE")] 781 782 part_range = self.expression(exp.PartitionRange, this=name, expressions=values) 783 return self.expression(exp.Partition, expressions=[part_range]) 784 785 def _parse_partition_list_value(self) -> exp.Partition: 786 self._match_text_seq("PARTITION") 787 name = self._parse_id_var() 788 self._match_text_seq("VALUES", "IN") 789 values = self._parse_wrapped_csv(self._parse_expression) 790 part_list = self.expression(exp.PartitionList, this=name, expressions=values) 791 return self.expression(exp.Partition, expressions=[part_list]) 792 793 class Generator(generator.Generator): 794 INTERVAL_ALLOWS_PLURAL_FORM = False 795 LOCKING_READS_SUPPORTED = True 796 NULL_ORDERING_SUPPORTED: t.Optional[bool] = None 797 JOIN_HINTS = False 798 TABLE_HINTS = True 799 DUPLICATE_KEY_UPDATE_WITH_SET = False 800 QUERY_HINT_SEP = " " 801 VALUES_AS_TABLE = False 802 NVL2_SUPPORTED = False 803 LAST_DAY_SUPPORTS_DATE_PART = False 804 JSON_TYPE_REQUIRED_FOR_EXTRACTION = True 805 JSON_PATH_BRACKETED_KEY_SUPPORTED = False 806 JSON_KEY_VALUE_PAIR_SEP = "," 807 SUPPORTS_TO_NUMBER = False 808 PARSE_JSON_NAME: t.Optional[str] = None 809 PAD_FILL_PATTERN_IS_REQUIRED = True 810 WRAP_DERIVED_VALUES = False 811 VARCHAR_REQUIRES_SIZE = True 812 SUPPORTS_MEDIAN = False 813 UPDATE_STATEMENT_SUPPORTS_FROM = False 814 815 TRANSFORMS = { 816 **generator.Generator.TRANSFORMS, 817 exp.ArrayAgg: rename_func("GROUP_CONCAT"), 818 exp.BitwiseAndAgg: rename_func("BIT_AND"), 819 exp.BitwiseOrAgg: rename_func("BIT_OR"), 820 exp.BitwiseXorAgg: rename_func("BIT_XOR"), 821 exp.BitwiseCount: rename_func("BIT_COUNT"), 822 exp.Chr: lambda self, e: self.chr_sql(e, "CHAR"), 823 exp.CurrentDate: no_paren_current_date_sql, 824 exp.DateDiff: _remove_ts_or_ds_to_date( 825 lambda self, e: self.func("DATEDIFF", e.this, e.expression), ("this", "expression") 826 ), 827 exp.DateAdd: _remove_ts_or_ds_to_date(date_add_sql("ADD")), 828 exp.DateStrToDate: datestrtodate_sql, 829 exp.DateSub: _remove_ts_or_ds_to_date(date_add_sql("SUB")), 830 exp.DateTrunc: _date_trunc_sql, 831 exp.Day: _remove_ts_or_ds_to_date(), 832 exp.DayOfMonth: _remove_ts_or_ds_to_date(rename_func("DAYOFMONTH")), 833 exp.DayOfWeek: _remove_ts_or_ds_to_date(rename_func("DAYOFWEEK")), 834 exp.DayOfYear: _remove_ts_or_ds_to_date(rename_func("DAYOFYEAR")), 835 exp.GroupConcat: lambda self, 836 e: f"""GROUP_CONCAT({self.sql(e, "this")} SEPARATOR {self.sql(e, "separator") or "','"})""", 837 exp.ILike: no_ilike_sql, 838 exp.JSONExtractScalar: arrow_json_extract_sql, 839 exp.Length: length_or_char_length_sql, 840 exp.LogicalOr: rename_func("MAX"), 841 exp.LogicalAnd: rename_func("MIN"), 842 exp.Max: max_or_greatest, 843 exp.Min: min_or_least, 844 exp.Month: _remove_ts_or_ds_to_date(), 845 exp.NullSafeEQ: lambda self, e: self.binary(e, "<=>"), 846 exp.NullSafeNEQ: lambda self, e: f"NOT {self.binary(e, '<=>')}", 847 exp.NumberToStr: rename_func("FORMAT"), 848 exp.Pivot: no_pivot_sql, 849 exp.Select: transforms.preprocess( 850 [ 851 transforms.eliminate_distinct_on, 852 transforms.eliminate_semi_and_anti_joins, 853 transforms.eliminate_qualify, 854 transforms.eliminate_full_outer_join, 855 transforms.unnest_generate_date_array_using_recursive_cte, 856 ] 857 ), 858 exp.StrPosition: lambda self, e: strposition_sql( 859 self, e, func_name="LOCATE", supports_position=True 860 ), 861 exp.StrToDate: _str_to_date_sql, 862 exp.StrToTime: _str_to_date_sql, 863 exp.Stuff: rename_func("INSERT"), 864 exp.SessionUser: lambda *_: "SESSION_USER()", 865 exp.TableSample: no_tablesample_sql, 866 exp.TimeFromParts: rename_func("MAKETIME"), 867 exp.TimestampAdd: date_add_interval_sql("DATE", "ADD"), 868 exp.TimestampDiff: lambda self, e: self.func( 869 "TIMESTAMPDIFF", unit_to_var(e), e.expression, e.this 870 ), 871 exp.TimestampSub: date_add_interval_sql("DATE", "SUB"), 872 exp.TimeStrToUnix: rename_func("UNIX_TIMESTAMP"), 873 exp.TimeStrToTime: lambda self, e: timestrtotime_sql( 874 self, 875 e, 876 include_precision=not e.args.get("zone"), 877 ), 878 exp.TimeToStr: _remove_ts_or_ds_to_date( 879 lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)) 880 ), 881 exp.Trim: trim_sql, 882 exp.TryCast: no_trycast_sql, 883 exp.TsOrDsAdd: date_add_sql("ADD"), 884 exp.TsOrDsDiff: lambda self, e: self.func("DATEDIFF", e.this, e.expression), 885 exp.TsOrDsToDate: _ts_or_ds_to_date_sql, 886 exp.Unicode: lambda self, e: f"ORD(CONVERT({self.sql(e.this)} USING utf32))", 887 exp.UnixToTime: _unix_to_time_sql, 888 exp.Week: _remove_ts_or_ds_to_date(), 889 exp.WeekOfYear: _remove_ts_or_ds_to_date(rename_func("WEEKOFYEAR")), 890 exp.Year: _remove_ts_or_ds_to_date(), 891 exp.UtcTimestamp: rename_func("UTC_TIMESTAMP"), 892 exp.UtcTime: rename_func("UTC_TIME"), 893 } 894 895 UNSIGNED_TYPE_MAPPING = { 896 exp.DataType.Type.UBIGINT: "BIGINT", 897 exp.DataType.Type.UINT: "INT", 898 exp.DataType.Type.UMEDIUMINT: "MEDIUMINT", 899 exp.DataType.Type.USMALLINT: "SMALLINT", 900 exp.DataType.Type.UTINYINT: "TINYINT", 901 exp.DataType.Type.UDECIMAL: "DECIMAL", 902 exp.DataType.Type.UDOUBLE: "DOUBLE", 903 } 904 905 TIMESTAMP_TYPE_MAPPING = { 906 exp.DataType.Type.DATETIME2: "DATETIME", 907 exp.DataType.Type.SMALLDATETIME: "DATETIME", 908 exp.DataType.Type.TIMESTAMP: "DATETIME", 909 exp.DataType.Type.TIMESTAMPNTZ: "DATETIME", 910 exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP", 911 exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP", 912 } 913 914 TYPE_MAPPING = { 915 **generator.Generator.TYPE_MAPPING, 916 **UNSIGNED_TYPE_MAPPING, 917 **TIMESTAMP_TYPE_MAPPING, 918 } 919 920 TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMTEXT) 921 TYPE_MAPPING.pop(exp.DataType.Type.LONGTEXT) 922 TYPE_MAPPING.pop(exp.DataType.Type.TINYTEXT) 923 TYPE_MAPPING.pop(exp.DataType.Type.BLOB) 924 TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMBLOB) 925 TYPE_MAPPING.pop(exp.DataType.Type.LONGBLOB) 926 TYPE_MAPPING.pop(exp.DataType.Type.TINYBLOB) 927 928 PROPERTIES_LOCATION = { 929 **generator.Generator.PROPERTIES_LOCATION, 930 exp.TransientProperty: exp.Properties.Location.UNSUPPORTED, 931 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 932 exp.PartitionedByProperty: exp.Properties.Location.UNSUPPORTED, 933 exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA, 934 exp.PartitionByListProperty: exp.Properties.Location.POST_SCHEMA, 935 } 936 937 LIMIT_FETCH = "LIMIT" 938 939 LIMIT_ONLY_LITERALS = True 940 941 CHAR_CAST_MAPPING = dict.fromkeys( 942 ( 943 exp.DataType.Type.LONGTEXT, 944 exp.DataType.Type.LONGBLOB, 945 exp.DataType.Type.MEDIUMBLOB, 946 exp.DataType.Type.MEDIUMTEXT, 947 exp.DataType.Type.TEXT, 948 exp.DataType.Type.TINYBLOB, 949 exp.DataType.Type.TINYTEXT, 950 exp.DataType.Type.VARCHAR, 951 ), 952 "CHAR", 953 ) 954 SIGNED_CAST_MAPPING = dict.fromkeys( 955 ( 956 exp.DataType.Type.BIGINT, 957 exp.DataType.Type.BOOLEAN, 958 exp.DataType.Type.INT, 959 exp.DataType.Type.SMALLINT, 960 exp.DataType.Type.TINYINT, 961 exp.DataType.Type.MEDIUMINT, 962 ), 963 "SIGNED", 964 ) 965 966 # MySQL doesn't support many datatypes in cast. 967 # https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast 968 CAST_MAPPING = { 969 **CHAR_CAST_MAPPING, 970 **SIGNED_CAST_MAPPING, 971 exp.DataType.Type.UBIGINT: "UNSIGNED", 972 } 973 974 TIMESTAMP_FUNC_TYPES = { 975 exp.DataType.Type.TIMESTAMPTZ, 976 exp.DataType.Type.TIMESTAMPLTZ, 977 } 978 979 # https://dev.mysql.com/doc/refman/8.0/en/keywords.html 980 RESERVED_KEYWORDS = { 981 "accessible", 982 "add", 983 "all", 984 "alter", 985 "analyze", 986 "and", 987 "as", 988 "asc", 989 "asensitive", 990 "before", 991 "between", 992 "bigint", 993 "binary", 994 "blob", 995 "both", 996 "by", 997 "call", 998 "cascade", 999 "case", 1000 "change", 1001 "char", 1002 "character", 1003 "check", 1004 "collate", 1005 "column", 1006 "condition", 1007 "constraint", 1008 "continue", 1009 "convert", 1010 "create", 1011 "cross", 1012 "cube", 1013 "cume_dist", 1014 "current_date", 1015 "current_time", 1016 "current_timestamp", 1017 "current_user", 1018 "cursor", 1019 "database", 1020 "databases", 1021 "day_hour", 1022 "day_microsecond", 1023 "day_minute", 1024 "day_second", 1025 "dec", 1026 "decimal", 1027 "declare", 1028 "default", 1029 "delayed", 1030 "delete", 1031 "dense_rank", 1032 "desc", 1033 "describe", 1034 "deterministic", 1035 "distinct", 1036 "distinctrow", 1037 "div", 1038 "double", 1039 "drop", 1040 "dual", 1041 "each", 1042 "else", 1043 "elseif", 1044 "empty", 1045 "enclosed", 1046 "escaped", 1047 "except", 1048 "exists", 1049 "exit", 1050 "explain", 1051 "false", 1052 "fetch", 1053 "first_value", 1054 "float", 1055 "float4", 1056 "float8", 1057 "for", 1058 "force", 1059 "foreign", 1060 "from", 1061 "fulltext", 1062 "function", 1063 "generated", 1064 "get", 1065 "grant", 1066 "group", 1067 "grouping", 1068 "groups", 1069 "having", 1070 "high_priority", 1071 "hour_microsecond", 1072 "hour_minute", 1073 "hour_second", 1074 "if", 1075 "ignore", 1076 "in", 1077 "index", 1078 "infile", 1079 "inner", 1080 "inout", 1081 "insensitive", 1082 "insert", 1083 "int", 1084 "int1", 1085 "int2", 1086 "int3", 1087 "int4", 1088 "int8", 1089 "integer", 1090 "intersect", 1091 "interval", 1092 "into", 1093 "io_after_gtids", 1094 "io_before_gtids", 1095 "is", 1096 "iterate", 1097 "join", 1098 "json_table", 1099 "key", 1100 "keys", 1101 "kill", 1102 "lag", 1103 "last_value", 1104 "lateral", 1105 "lead", 1106 "leading", 1107 "leave", 1108 "left", 1109 "like", 1110 "limit", 1111 "linear", 1112 "lines", 1113 "load", 1114 "localtime", 1115 "localtimestamp", 1116 "lock", 1117 "long", 1118 "longblob", 1119 "longtext", 1120 "loop", 1121 "low_priority", 1122 "master_bind", 1123 "master_ssl_verify_server_cert", 1124 "match", 1125 "maxvalue", 1126 "mediumblob", 1127 "mediumint", 1128 "mediumtext", 1129 "middleint", 1130 "minute_microsecond", 1131 "minute_second", 1132 "mod", 1133 "modifies", 1134 "natural", 1135 "not", 1136 "no_write_to_binlog", 1137 "nth_value", 1138 "ntile", 1139 "null", 1140 "numeric", 1141 "of", 1142 "on", 1143 "optimize", 1144 "optimizer_costs", 1145 "option", 1146 "optionally", 1147 "or", 1148 "order", 1149 "out", 1150 "outer", 1151 "outfile", 1152 "over", 1153 "partition", 1154 "percent_rank", 1155 "precision", 1156 "primary", 1157 "procedure", 1158 "purge", 1159 "range", 1160 "rank", 1161 "read", 1162 "reads", 1163 "read_write", 1164 "real", 1165 "recursive", 1166 "references", 1167 "regexp", 1168 "release", 1169 "rename", 1170 "repeat", 1171 "replace", 1172 "require", 1173 "resignal", 1174 "restrict", 1175 "return", 1176 "revoke", 1177 "right", 1178 "rlike", 1179 "row", 1180 "rows", 1181 "row_number", 1182 "schema", 1183 "schemas", 1184 "second_microsecond", 1185 "select", 1186 "sensitive", 1187 "separator", 1188 "set", 1189 "show", 1190 "signal", 1191 "smallint", 1192 "spatial", 1193 "specific", 1194 "sql", 1195 "sqlexception", 1196 "sqlstate", 1197 "sqlwarning", 1198 "sql_big_result", 1199 "sql_calc_found_rows", 1200 "sql_small_result", 1201 "ssl", 1202 "starting", 1203 "stored", 1204 "straight_join", 1205 "system", 1206 "table", 1207 "terminated", 1208 "then", 1209 "tinyblob", 1210 "tinyint", 1211 "tinytext", 1212 "to", 1213 "trailing", 1214 "trigger", 1215 "true", 1216 "undo", 1217 "union", 1218 "unique", 1219 "unlock", 1220 "unsigned", 1221 "update", 1222 "usage", 1223 "use", 1224 "using", 1225 "utc_date", 1226 "utc_time", 1227 "utc_timestamp", 1228 "values", 1229 "varbinary", 1230 "varchar", 1231 "varcharacter", 1232 "varying", 1233 "virtual", 1234 "when", 1235 "where", 1236 "while", 1237 "window", 1238 "with", 1239 "write", 1240 "xor", 1241 "year_month", 1242 "zerofill", 1243 } 1244 1245 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1246 persisted = "STORED" if expression.args.get("persisted") else "VIRTUAL" 1247 return f"GENERATED ALWAYS AS ({self.sql(expression.this.unnest())}) {persisted}" 1248 1249 def array_sql(self, expression: exp.Array) -> str: 1250 self.unsupported("Arrays are not supported by MySQL") 1251 return self.function_fallback_sql(expression) 1252 1253 def arraycontainsall_sql(self, expression: exp.ArrayContainsAll) -> str: 1254 self.unsupported("Array operations are not supported by MySQL") 1255 return self.function_fallback_sql(expression) 1256 1257 def dpipe_sql(self, expression: exp.DPipe) -> str: 1258 return self.func("CONCAT", *expression.flatten()) 1259 1260 def extract_sql(self, expression: exp.Extract) -> str: 1261 unit = expression.name 1262 if unit and unit.lower() == "epoch": 1263 return self.func("UNIX_TIMESTAMP", expression.expression) 1264 1265 return super().extract_sql(expression) 1266 1267 def datatype_sql(self, expression: exp.DataType) -> str: 1268 if ( 1269 self.VARCHAR_REQUIRES_SIZE 1270 and expression.is_type(exp.DataType.Type.VARCHAR) 1271 and not expression.expressions 1272 ): 1273 # `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT` 1274 return "TEXT" 1275 1276 # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html 1277 result = super().datatype_sql(expression) 1278 if expression.this in self.UNSIGNED_TYPE_MAPPING: 1279 result = f"{result} UNSIGNED" 1280 1281 return result 1282 1283 def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str: 1284 return f"{self.sql(expression, 'this')} MEMBER OF({self.sql(expression, 'expression')})" 1285 1286 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1287 if expression.to.this in self.TIMESTAMP_FUNC_TYPES: 1288 return self.func("TIMESTAMP", expression.this) 1289 1290 to = self.CAST_MAPPING.get(expression.to.this) 1291 1292 if to: 1293 expression.to.set("this", to) 1294 return super().cast_sql(expression) 1295 1296 def show_sql(self, expression: exp.Show) -> str: 1297 this = f" {expression.name}" 1298 full = " FULL" if expression.args.get("full") else "" 1299 global_ = " GLOBAL" if expression.args.get("global_") else "" 1300 1301 target = self.sql(expression, "target") 1302 target = f" {target}" if target else "" 1303 if expression.name in ("COLUMNS", "INDEX"): 1304 target = f" FROM{target}" 1305 elif expression.name == "GRANTS": 1306 target = f" FOR{target}" 1307 elif expression.name in ("LINKS", "PARTITIONS"): 1308 target = f" ON{target}" if target else "" 1309 elif expression.name == "PROJECTIONS": 1310 target = f" ON TABLE{target}" if target else "" 1311 1312 db = self._prefixed_sql("FROM", expression, "db") 1313 1314 like = self._prefixed_sql("LIKE", expression, "like") 1315 where = self.sql(expression, "where") 1316 1317 types = self.expressions(expression, key="types") 1318 types = f" {types}" if types else types 1319 query = self._prefixed_sql("FOR QUERY", expression, "query") 1320 1321 if expression.name == "PROFILE": 1322 offset = self._prefixed_sql("OFFSET", expression, "offset") 1323 limit = self._prefixed_sql("LIMIT", expression, "limit") 1324 else: 1325 offset = "" 1326 limit = self._oldstyle_limit_sql(expression) 1327 1328 log = self._prefixed_sql("IN", expression, "log") 1329 position = self._prefixed_sql("FROM", expression, "position") 1330 1331 channel = self._prefixed_sql("FOR CHANNEL", expression, "channel") 1332 1333 if expression.name == "ENGINE": 1334 mutex_or_status = " MUTEX" if expression.args.get("mutex") else " STATUS" 1335 else: 1336 mutex_or_status = "" 1337 1338 for_table = self._prefixed_sql("FOR TABLE", expression, "for_table") 1339 for_group = self._prefixed_sql("FOR GROUP", expression, "for_group") 1340 for_user = self._prefixed_sql("FOR USER", expression, "for_user") 1341 for_role = self._prefixed_sql("FOR ROLE", expression, "for_role") 1342 into_outfile = self._prefixed_sql("INTO OUTFILE", expression, "into_outfile") 1343 json = " JSON" if expression.args.get("json") else "" 1344 1345 return f"SHOW{full}{global_}{this}{json}{target}{for_table}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}{for_group}{for_user}{for_role}{into_outfile}" 1346 1347 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 1348 """To avoid TO keyword in ALTER ... RENAME statements. 1349 It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks. 1350 """ 1351 return super().alterrename_sql(expression, include_to=False) 1352 1353 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 1354 dtype = self.sql(expression, "dtype") 1355 if not dtype: 1356 return super().altercolumn_sql(expression) 1357 1358 this = self.sql(expression, "this") 1359 return f"MODIFY COLUMN {this} {dtype}" 1360 1361 def _prefixed_sql(self, prefix: str, expression: exp.Expression, arg: str) -> str: 1362 sql = self.sql(expression, arg) 1363 return f" {prefix} {sql}" if sql else "" 1364 1365 def _oldstyle_limit_sql(self, expression: exp.Show) -> str: 1366 limit = self.sql(expression, "limit") 1367 offset = self.sql(expression, "offset") 1368 if limit: 1369 limit_offset = f"{offset}, {limit}" if offset else limit 1370 return f" LIMIT {limit_offset}" 1371 return "" 1372 1373 def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str: 1374 unit = expression.args.get("unit") 1375 1376 # Pick an old-enough date to avoid negative timestamp diffs 1377 start_ts = "'0000-01-01 00:00:00'" 1378 1379 # Source: https://stackoverflow.com/a/32955740 1380 timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this]) 1381 interval = exp.Interval(this=timestamp_diff, unit=unit) 1382 dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval]) 1383 1384 return self.sql(dateadd) 1385 1386 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 1387 from_tz = expression.args.get("source_tz") 1388 to_tz = expression.args.get("target_tz") 1389 dt = expression.args.get("timestamp") 1390 1391 return self.func("CONVERT_TZ", dt, from_tz, to_tz) 1392 1393 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 1394 self.unsupported("AT TIME ZONE is not supported by MySQL") 1395 return self.sql(expression.this) 1396 1397 def isascii_sql(self, expression: exp.IsAscii) -> str: 1398 return f"REGEXP_LIKE({self.sql(expression.this)}, '^[[:ascii:]]*$')" 1399 1400 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 1401 # https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html 1402 self.unsupported("MySQL does not support IGNORE NULLS.") 1403 return self.sql(expression.this) 1404 1405 @unsupported_args("this") 1406 def currentschema_sql(self, expression: exp.CurrentSchema) -> str: 1407 return self.func("SCHEMA") 1408 1409 def partition_sql(self, expression: exp.Partition) -> str: 1410 parent = expression.parent 1411 if isinstance(parent, (exp.PartitionByRangeProperty, exp.PartitionByListProperty)): 1412 return self.expressions(expression, flat=True) 1413 return super().partition_sql(expression) 1414 1415 def _partition_by_sql( 1416 self, expression: exp.PartitionByRangeProperty | exp.PartitionByListProperty, kind: str 1417 ) -> str: 1418 partitions = self.expressions(expression, key="partition_expressions", flat=True) 1419 create = self.expressions(expression, key="create_expressions", flat=True) 1420 return f"PARTITION BY {kind} ({partitions}) ({create})" 1421 1422 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 1423 return self._partition_by_sql(expression, "RANGE") 1424 1425 def partitionbylistproperty_sql(self, expression: exp.PartitionByListProperty) -> str: 1426 return self._partition_by_sql(expression, "LIST") 1427 1428 def partitionlist_sql(self, expression: exp.PartitionList) -> str: 1429 name = self.sql(expression, "this") 1430 values = self.expressions(expression, flat=True) 1431 return f"PARTITION {name} VALUES IN ({values})" 1432 1433 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 1434 name = self.sql(expression, "this") 1435 values = self.expressions(expression, flat=True) 1436 return f"PARTITION {name} VALUES LESS THAN ({values})"
117def date_add_sql( 118 kind: str, 119) -> t.Callable[[generator.Generator, exp.Expression], str]: 120 def func(self: generator.Generator, expression: exp.Expression) -> str: 121 return self.func( 122 f"DATE_{kind}", 123 expression.this, 124 exp.Interval(this=expression.expression, unit=unit_to_var(expression)), 125 ) 126 127 return func
150class MySQL(Dialect): 151 PROMOTE_TO_INFERRED_DATETIME_TYPE = True 152 153 # https://dev.mysql.com/doc/refman/8.0/en/identifiers.html 154 IDENTIFIERS_CAN_START_WITH_DIGIT = True 155 156 # We default to treating all identifiers as case-sensitive, since it matches MySQL's 157 # behavior on Linux systems. For MacOS and Windows systems, one can override this 158 # setting by specifying `dialect="mysql, normalization_strategy = lowercase"`. 159 # 160 # See also https://dev.mysql.com/doc/refman/8.2/en/identifier-case-sensitivity.html 161 NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE 162 163 TIME_FORMAT = "'%Y-%m-%d %T'" 164 DPIPE_IS_STRING_CONCAT = False 165 SUPPORTS_USER_DEFINED_TYPES = False 166 SUPPORTS_SEMI_ANTI_JOIN = False 167 SAFE_DIVISION = True 168 SAFE_TO_ELIMINATE_DOUBLE_NEGATION = False 169 LEAST_GREATEST_IGNORES_NULLS = False 170 171 EXPRESSION_METADATA = EXPRESSION_METADATA.copy() 172 173 # https://prestodb.io/docs/current/functions/datetime.html#mysql-date-functions 174 TIME_MAPPING = { 175 "%M": "%B", 176 "%c": "%-m", 177 "%e": "%-d", 178 "%h": "%I", 179 "%i": "%M", 180 "%s": "%S", 181 "%u": "%W", 182 "%k": "%-H", 183 "%l": "%-I", 184 "%T": "%H:%M:%S", 185 "%W": "%A", 186 } 187 188 VALID_INTERVAL_UNITS = { 189 *Dialect.VALID_INTERVAL_UNITS, 190 "SECOND_MICROSECOND", 191 "MINUTE_MICROSECOND", 192 "MINUTE_SECOND", 193 "HOUR_MICROSECOND", 194 "HOUR_SECOND", 195 "HOUR_MINUTE", 196 "DAY_MICROSECOND", 197 "DAY_SECOND", 198 "DAY_MINUTE", 199 "DAY_HOUR", 200 "YEAR_MONTH", 201 } 202 203 class Tokenizer(tokens.Tokenizer): 204 QUOTES = ["'", '"'] 205 COMMENTS = ["--", "#", ("/*", "*/")] 206 IDENTIFIERS = ["`"] 207 STRING_ESCAPES = ["'", '"', "\\"] 208 BIT_STRINGS = [("b'", "'"), ("B'", "'"), ("0b", "")] 209 HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", "")] 210 # https://dev.mysql.com/doc/refman/8.4/en/string-literals.html 211 ESCAPE_FOLLOW_CHARS = ["0", "b", "n", "r", "t", "Z", "%", "_"] 212 213 NESTED_COMMENTS = False 214 215 KEYWORDS = { 216 **tokens.Tokenizer.KEYWORDS, 217 "BLOB": TokenType.BLOB, 218 "CHARSET": TokenType.CHARACTER_SET, 219 "DISTINCTROW": TokenType.DISTINCT, 220 "EXPLAIN": TokenType.DESCRIBE, 221 "FORCE": TokenType.FORCE, 222 "IGNORE": TokenType.IGNORE, 223 "KEY": TokenType.KEY, 224 "LOCK TABLES": TokenType.COMMAND, 225 "LONGBLOB": TokenType.LONGBLOB, 226 "LONGTEXT": TokenType.LONGTEXT, 227 "MEDIUMBLOB": TokenType.MEDIUMBLOB, 228 "MEDIUMINT": TokenType.MEDIUMINT, 229 "MEDIUMTEXT": TokenType.MEDIUMTEXT, 230 "MEMBER OF": TokenType.MEMBER_OF, 231 "MOD": TokenType.MOD, 232 "SEPARATOR": TokenType.SEPARATOR, 233 "SERIAL": TokenType.SERIAL, 234 "SIGNED": TokenType.BIGINT, 235 "SIGNED INTEGER": TokenType.BIGINT, 236 "SOUNDS LIKE": TokenType.SOUNDS_LIKE, 237 "START": TokenType.BEGIN, 238 "TIMESTAMP": TokenType.TIMESTAMPTZ, 239 "TINYBLOB": TokenType.TINYBLOB, 240 "TINYTEXT": TokenType.TINYTEXT, 241 "UNLOCK TABLES": TokenType.COMMAND, 242 "UNSIGNED": TokenType.UBIGINT, 243 "UNSIGNED INTEGER": TokenType.UBIGINT, 244 "YEAR": TokenType.YEAR, 245 "_ARMSCII8": TokenType.INTRODUCER, 246 "_ASCII": TokenType.INTRODUCER, 247 "_BIG5": TokenType.INTRODUCER, 248 "_BINARY": TokenType.INTRODUCER, 249 "_CP1250": TokenType.INTRODUCER, 250 "_CP1251": TokenType.INTRODUCER, 251 "_CP1256": TokenType.INTRODUCER, 252 "_CP1257": TokenType.INTRODUCER, 253 "_CP850": TokenType.INTRODUCER, 254 "_CP852": TokenType.INTRODUCER, 255 "_CP866": TokenType.INTRODUCER, 256 "_CP932": TokenType.INTRODUCER, 257 "_DEC8": TokenType.INTRODUCER, 258 "_EUCJPMS": TokenType.INTRODUCER, 259 "_EUCKR": TokenType.INTRODUCER, 260 "_GB18030": TokenType.INTRODUCER, 261 "_GB2312": TokenType.INTRODUCER, 262 "_GBK": TokenType.INTRODUCER, 263 "_GEOSTD8": TokenType.INTRODUCER, 264 "_GREEK": TokenType.INTRODUCER, 265 "_HEBREW": TokenType.INTRODUCER, 266 "_HP8": TokenType.INTRODUCER, 267 "_KEYBCS2": TokenType.INTRODUCER, 268 "_KOI8R": TokenType.INTRODUCER, 269 "_KOI8U": TokenType.INTRODUCER, 270 "_LATIN1": TokenType.INTRODUCER, 271 "_LATIN2": TokenType.INTRODUCER, 272 "_LATIN5": TokenType.INTRODUCER, 273 "_LATIN7": TokenType.INTRODUCER, 274 "_MACCE": TokenType.INTRODUCER, 275 "_MACROMAN": TokenType.INTRODUCER, 276 "_SJIS": TokenType.INTRODUCER, 277 "_SWE7": TokenType.INTRODUCER, 278 "_TIS620": TokenType.INTRODUCER, 279 "_UCS2": TokenType.INTRODUCER, 280 "_UJIS": TokenType.INTRODUCER, 281 # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html 282 "_UTF8": TokenType.INTRODUCER, 283 "_UTF16": TokenType.INTRODUCER, 284 "_UTF16LE": TokenType.INTRODUCER, 285 "_UTF32": TokenType.INTRODUCER, 286 "_UTF8MB3": TokenType.INTRODUCER, 287 "_UTF8MB4": TokenType.INTRODUCER, 288 "@@": TokenType.SESSION_PARAMETER, 289 } 290 291 COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE} - {TokenType.SHOW} 292 293 class Parser(parser.Parser): 294 FUNC_TOKENS = { 295 *parser.Parser.FUNC_TOKENS, 296 TokenType.DATABASE, 297 TokenType.MOD, 298 TokenType.SCHEMA, 299 TokenType.VALUES, 300 TokenType.CHARACTER_SET, 301 } 302 303 CONJUNCTION = { 304 **parser.Parser.CONJUNCTION, 305 TokenType.DAMP: exp.And, 306 TokenType.XOR: exp.Xor, 307 } 308 309 DISJUNCTION = { 310 **parser.Parser.DISJUNCTION, 311 TokenType.DPIPE: exp.Or, 312 } 313 314 TABLE_ALIAS_TOKENS = ( 315 parser.Parser.TABLE_ALIAS_TOKENS - parser.Parser.TABLE_INDEX_HINT_TOKENS 316 ) 317 318 RANGE_PARSERS = { 319 **parser.Parser.RANGE_PARSERS, 320 TokenType.SOUNDS_LIKE: lambda self, this: self.expression( 321 exp.EQ, 322 this=self.expression(exp.Soundex, this=this), 323 expression=self.expression(exp.Soundex, this=self._parse_term()), 324 ), 325 TokenType.MEMBER_OF: lambda self, this: self.expression( 326 exp.JSONArrayContains, 327 this=this, 328 expression=self._parse_wrapped(self._parse_expression), 329 ), 330 } 331 332 FUNCTIONS = { 333 **parser.Parser.FUNCTIONS, 334 "BIT_AND": exp.BitwiseAndAgg.from_arg_list, 335 "BIT_OR": exp.BitwiseOrAgg.from_arg_list, 336 "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, 337 "BIT_COUNT": exp.BitwiseCount.from_arg_list, 338 "CONVERT_TZ": lambda args: exp.ConvertTimezone( 339 source_tz=seq_get(args, 1), target_tz=seq_get(args, 2), timestamp=seq_get(args, 0) 340 ), 341 "CURDATE": exp.CurrentDate.from_arg_list, 342 "DATE": lambda args: exp.TsOrDsToDate(this=seq_get(args, 0)), 343 "DATE_ADD": build_date_delta_with_interval(exp.DateAdd), 344 "DATE_FORMAT": build_formatted_time(exp.TimeToStr, "mysql"), 345 "DATE_SUB": build_date_delta_with_interval(exp.DateSub), 346 "DAY": lambda args: exp.Day(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 347 "DAYOFMONTH": lambda args: exp.DayOfMonth(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 348 "DAYOFWEEK": lambda args: exp.DayOfWeek(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 349 "DAYOFYEAR": lambda args: exp.DayOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 350 "FORMAT": exp.NumberToStr.from_arg_list, 351 "FROM_UNIXTIME": build_formatted_time(exp.UnixToTime, "mysql"), 352 "ISNULL": isnull_to_is_null, 353 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 354 "MAKETIME": exp.TimeFromParts.from_arg_list, 355 "MONTH": lambda args: exp.Month(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 356 "MONTHNAME": lambda args: exp.TimeToStr( 357 this=exp.TsOrDsToDate(this=seq_get(args, 0)), 358 format=exp.Literal.string("%B"), 359 ), 360 "SCHEMA": exp.CurrentSchema.from_arg_list, 361 "DATABASE": exp.CurrentSchema.from_arg_list, 362 "STR_TO_DATE": _str_to_date, 363 "TIMESTAMPDIFF": build_date_delta(exp.TimestampDiff), 364 "TO_DAYS": lambda args: exp.paren( 365 exp.DateDiff( 366 this=exp.TsOrDsToDate(this=seq_get(args, 0)), 367 expression=exp.TsOrDsToDate(this=exp.Literal.string("0000-01-01")), 368 unit=exp.var("DAY"), 369 ) 370 + 1 371 ), 372 "WEEK": lambda args: exp.Week( 373 this=exp.TsOrDsToDate(this=seq_get(args, 0)), mode=seq_get(args, 1) 374 ), 375 "WEEKOFYEAR": lambda args: exp.WeekOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 376 "YEAR": lambda args: exp.Year(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 377 } 378 379 FUNCTION_PARSERS = { 380 **parser.Parser.FUNCTION_PARSERS, 381 "GROUP_CONCAT": lambda self: self._parse_group_concat(), 382 # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values 383 "VALUES": lambda self: self.expression( 384 exp.Anonymous, this="VALUES", expressions=[self._parse_id_var()] 385 ), 386 "JSON_VALUE": lambda self: self._parse_json_value(), 387 "SUBSTR": lambda self: self._parse_substring(), 388 } 389 390 STATEMENT_PARSERS = { 391 **parser.Parser.STATEMENT_PARSERS, 392 TokenType.SHOW: lambda self: self._parse_show(), 393 } 394 395 SHOW_PARSERS = { 396 "BINARY LOGS": _show_parser("BINARY LOGS"), 397 "MASTER LOGS": _show_parser("BINARY LOGS"), 398 "BINLOG EVENTS": _show_parser("BINLOG EVENTS"), 399 "CHARACTER SET": _show_parser("CHARACTER SET"), 400 "CHARSET": _show_parser("CHARACTER SET"), 401 "COLLATION": _show_parser("COLLATION"), 402 "FULL COLUMNS": _show_parser("COLUMNS", target="FROM", full=True), 403 "COLUMNS": _show_parser("COLUMNS", target="FROM"), 404 "CREATE DATABASE": _show_parser("CREATE DATABASE", target=True), 405 "CREATE EVENT": _show_parser("CREATE EVENT", target=True), 406 "CREATE FUNCTION": _show_parser("CREATE FUNCTION", target=True), 407 "CREATE PROCEDURE": _show_parser("CREATE PROCEDURE", target=True), 408 "CREATE TABLE": _show_parser("CREATE TABLE", target=True), 409 "CREATE TRIGGER": _show_parser("CREATE TRIGGER", target=True), 410 "CREATE VIEW": _show_parser("CREATE VIEW", target=True), 411 "DATABASES": _show_parser("DATABASES"), 412 "SCHEMAS": _show_parser("DATABASES"), 413 "ENGINE": _show_parser("ENGINE", target=True), 414 "STORAGE ENGINES": _show_parser("ENGINES"), 415 "ENGINES": _show_parser("ENGINES"), 416 "ERRORS": _show_parser("ERRORS"), 417 "EVENTS": _show_parser("EVENTS"), 418 "FUNCTION CODE": _show_parser("FUNCTION CODE", target=True), 419 "FUNCTION STATUS": _show_parser("FUNCTION STATUS"), 420 "GRANTS": _show_parser("GRANTS", target="FOR"), 421 "INDEX": _show_parser("INDEX", target="FROM"), 422 "MASTER STATUS": _show_parser("MASTER STATUS"), 423 "OPEN TABLES": _show_parser("OPEN TABLES"), 424 "PLUGINS": _show_parser("PLUGINS"), 425 "PROCEDURE CODE": _show_parser("PROCEDURE CODE", target=True), 426 "PROCEDURE STATUS": _show_parser("PROCEDURE STATUS"), 427 "PRIVILEGES": _show_parser("PRIVILEGES"), 428 "FULL PROCESSLIST": _show_parser("PROCESSLIST", full=True), 429 "PROCESSLIST": _show_parser("PROCESSLIST"), 430 "PROFILE": _show_parser("PROFILE"), 431 "PROFILES": _show_parser("PROFILES"), 432 "RELAYLOG EVENTS": _show_parser("RELAYLOG EVENTS"), 433 "REPLICAS": _show_parser("REPLICAS"), 434 "SLAVE HOSTS": _show_parser("REPLICAS"), 435 "REPLICA STATUS": _show_parser("REPLICA STATUS"), 436 "SLAVE STATUS": _show_parser("REPLICA STATUS"), 437 "GLOBAL STATUS": _show_parser("STATUS", global_=True), 438 "SESSION STATUS": _show_parser("STATUS"), 439 "STATUS": _show_parser("STATUS"), 440 "TABLE STATUS": _show_parser("TABLE STATUS"), 441 "FULL TABLES": _show_parser("TABLES", full=True), 442 "TABLES": _show_parser("TABLES"), 443 "TRIGGERS": _show_parser("TRIGGERS"), 444 "GLOBAL VARIABLES": _show_parser("VARIABLES", global_=True), 445 "SESSION VARIABLES": _show_parser("VARIABLES"), 446 "VARIABLES": _show_parser("VARIABLES"), 447 "WARNINGS": _show_parser("WARNINGS"), 448 } 449 450 PROPERTY_PARSERS = { 451 **parser.Parser.PROPERTY_PARSERS, 452 "LOCK": lambda self: self._parse_property_assignment(exp.LockProperty), 453 "PARTITION BY": lambda self: self._parse_partition_property(), 454 } 455 456 SET_PARSERS = { 457 **parser.Parser.SET_PARSERS, 458 "PERSIST": lambda self: self._parse_set_item_assignment("PERSIST"), 459 "PERSIST_ONLY": lambda self: self._parse_set_item_assignment("PERSIST_ONLY"), 460 "CHARACTER SET": lambda self: self._parse_set_item_charset("CHARACTER SET"), 461 "CHARSET": lambda self: self._parse_set_item_charset("CHARACTER SET"), 462 "NAMES": lambda self: self._parse_set_item_names(), 463 } 464 465 CONSTRAINT_PARSERS = { 466 **parser.Parser.CONSTRAINT_PARSERS, 467 "FULLTEXT": lambda self: self._parse_index_constraint(kind="FULLTEXT"), 468 "INDEX": lambda self: self._parse_index_constraint(), 469 "KEY": lambda self: self._parse_index_constraint(), 470 "SPATIAL": lambda self: self._parse_index_constraint(kind="SPATIAL"), 471 "ZEROFILL": lambda self: self.expression(exp.ZeroFillColumnConstraint), 472 } 473 474 ALTER_PARSERS = { 475 **parser.Parser.ALTER_PARSERS, 476 "MODIFY": lambda self: self._parse_alter_table_alter(), 477 } 478 479 ALTER_ALTER_PARSERS = { 480 **parser.Parser.ALTER_ALTER_PARSERS, 481 "INDEX": lambda self: self._parse_alter_table_alter_index(), 482 } 483 484 SCHEMA_UNNAMED_CONSTRAINTS = { 485 *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS, 486 "FULLTEXT", 487 "INDEX", 488 "KEY", 489 "SPATIAL", 490 } 491 492 PROFILE_TYPES: parser.OPTIONS_TYPE = { 493 **dict.fromkeys(("ALL", "CPU", "IPC", "MEMORY", "SOURCE", "SWAPS"), tuple()), 494 "BLOCK": ("IO",), 495 "CONTEXT": ("SWITCHES",), 496 "PAGE": ("FAULTS",), 497 } 498 499 TYPE_TOKENS = { 500 *parser.Parser.TYPE_TOKENS, 501 TokenType.SET, 502 } 503 504 ENUM_TYPE_TOKENS = { 505 *parser.Parser.ENUM_TYPE_TOKENS, 506 TokenType.SET, 507 } 508 509 # SELECT [ ALL | DISTINCT | DISTINCTROW ] [ <OPERATION_MODIFIERS> ] 510 OPERATION_MODIFIERS = { 511 "HIGH_PRIORITY", 512 "STRAIGHT_JOIN", 513 "SQL_SMALL_RESULT", 514 "SQL_BIG_RESULT", 515 "SQL_BUFFER_RESULT", 516 "SQL_NO_CACHE", 517 "SQL_CALC_FOUND_ROWS", 518 } 519 520 LOG_DEFAULTS_TO_LN = True 521 STRING_ALIASES = True 522 VALUES_FOLLOWED_BY_PAREN = False 523 SUPPORTS_PARTITION_SELECTION = True 524 525 def _parse_generated_as_identity( 526 self, 527 ) -> ( 528 exp.GeneratedAsIdentityColumnConstraint 529 | exp.ComputedColumnConstraint 530 | exp.GeneratedAsRowColumnConstraint 531 ): 532 this = super()._parse_generated_as_identity() 533 534 if self._match_texts(("STORED", "VIRTUAL")): 535 persisted = self._prev.text.upper() == "STORED" 536 537 if isinstance(this, exp.ComputedColumnConstraint): 538 this.set("persisted", persisted) 539 elif isinstance(this, exp.GeneratedAsIdentityColumnConstraint): 540 this = self.expression( 541 exp.ComputedColumnConstraint, this=this.expression, persisted=persisted 542 ) 543 544 return this 545 546 def _parse_primary_key_part(self) -> t.Optional[exp.Expression]: 547 this = self._parse_id_var() 548 if not self._match(TokenType.L_PAREN): 549 return this 550 551 expression = self._parse_number() 552 self._match_r_paren() 553 return self.expression(exp.ColumnPrefix, this=this, expression=expression) 554 555 def _parse_index_constraint( 556 self, kind: t.Optional[str] = None 557 ) -> exp.IndexColumnConstraint: 558 if kind: 559 self._match_texts(("INDEX", "KEY")) 560 561 this = self._parse_id_var(any_token=False) 562 index_type = self._match(TokenType.USING) and self._advance_any() and self._prev.text 563 expressions = self._parse_wrapped_csv(self._parse_ordered) 564 565 options = [] 566 while True: 567 if self._match_text_seq("KEY_BLOCK_SIZE"): 568 self._match(TokenType.EQ) 569 opt = exp.IndexConstraintOption(key_block_size=self._parse_number()) 570 elif self._match(TokenType.USING): 571 opt = exp.IndexConstraintOption(using=self._advance_any() and self._prev.text) 572 elif self._match_text_seq("WITH", "PARSER"): 573 opt = exp.IndexConstraintOption(parser=self._parse_var(any_token=True)) 574 elif self._match(TokenType.COMMENT): 575 opt = exp.IndexConstraintOption(comment=self._parse_string()) 576 elif self._match_text_seq("VISIBLE"): 577 opt = exp.IndexConstraintOption(visible=True) 578 elif self._match_text_seq("INVISIBLE"): 579 opt = exp.IndexConstraintOption(visible=False) 580 elif self._match_text_seq("ENGINE_ATTRIBUTE"): 581 self._match(TokenType.EQ) 582 opt = exp.IndexConstraintOption(engine_attr=self._parse_string()) 583 elif self._match_text_seq("SECONDARY_ENGINE_ATTRIBUTE"): 584 self._match(TokenType.EQ) 585 opt = exp.IndexConstraintOption(secondary_engine_attr=self._parse_string()) 586 else: 587 opt = None 588 589 if not opt: 590 break 591 592 options.append(opt) 593 594 return self.expression( 595 exp.IndexColumnConstraint, 596 this=this, 597 expressions=expressions, 598 kind=kind, 599 index_type=index_type, 600 options=options, 601 ) 602 603 def _parse_show_mysql( 604 self, 605 this: str, 606 target: bool | str = False, 607 full: t.Optional[bool] = None, 608 global_: t.Optional[bool] = None, 609 ) -> exp.Show: 610 json = self._match_text_seq("JSON") 611 612 if target: 613 if isinstance(target, str): 614 self._match_text_seq(*target.split(" ")) 615 target_id = self._parse_id_var() 616 else: 617 target_id = None 618 619 log = self._parse_string() if self._match_text_seq("IN") else None 620 621 if this in ("BINLOG EVENTS", "RELAYLOG EVENTS"): 622 position = self._parse_number() if self._match_text_seq("FROM") else None 623 db = None 624 else: 625 position = None 626 db = None 627 628 if self._match(TokenType.FROM): 629 db = self._parse_id_var() 630 elif self._match(TokenType.DOT): 631 db = target_id 632 target_id = self._parse_id_var() 633 634 channel = self._parse_id_var() if self._match_text_seq("FOR", "CHANNEL") else None 635 636 like = self._parse_string() if self._match_text_seq("LIKE") else None 637 where = self._parse_where() 638 639 if this == "PROFILE": 640 types = self._parse_csv(lambda: self._parse_var_from_options(self.PROFILE_TYPES)) 641 query = self._parse_number() if self._match_text_seq("FOR", "QUERY") else None 642 offset = self._parse_number() if self._match_text_seq("OFFSET") else None 643 limit = self._parse_number() if self._match_text_seq("LIMIT") else None 644 else: 645 types, query = None, None 646 offset, limit = self._parse_oldstyle_limit() 647 648 mutex = True if self._match_text_seq("MUTEX") else None 649 mutex = False if self._match_text_seq("STATUS") else mutex 650 651 for_table = self._parse_id_var() if self._match_text_seq("FOR", "TABLE") else None 652 for_group = self._parse_string() if self._match_text_seq("FOR", "GROUP") else None 653 for_user = self._parse_string() if self._match_text_seq("FOR", "USER") else None 654 for_role = self._parse_string() if self._match_text_seq("FOR", "ROLE") else None 655 into_outfile = self._parse_string() if self._match_text_seq("INTO", "OUTFILE") else None 656 657 return self.expression( 658 exp.Show, 659 this=this, 660 target=target_id, 661 full=full, 662 log=log, 663 position=position, 664 db=db, 665 channel=channel, 666 like=like, 667 where=where, 668 types=types, 669 query=query, 670 offset=offset, 671 limit=limit, 672 mutex=mutex, 673 for_table=for_table, 674 for_group=for_group, 675 for_user=for_user, 676 for_role=for_role, 677 into_outfile=into_outfile, 678 json=json, 679 global_=global_, 680 ) 681 682 def _parse_oldstyle_limit( 683 self, 684 ) -> t.Tuple[t.Optional[exp.Expression], t.Optional[exp.Expression]]: 685 limit = None 686 offset = None 687 if self._match_text_seq("LIMIT"): 688 parts = self._parse_csv(self._parse_number) 689 if len(parts) == 1: 690 limit = parts[0] 691 elif len(parts) == 2: 692 limit = parts[1] 693 offset = parts[0] 694 695 return offset, limit 696 697 def _parse_set_item_charset(self, kind: str) -> exp.Expression: 698 this = self._parse_string() or self._parse_unquoted_field() 699 return self.expression(exp.SetItem, this=this, kind=kind) 700 701 def _parse_set_item_names(self) -> exp.Expression: 702 charset = self._parse_string() or self._parse_unquoted_field() 703 if self._match_text_seq("COLLATE"): 704 collate = self._parse_string() or self._parse_unquoted_field() 705 else: 706 collate = None 707 708 return self.expression(exp.SetItem, this=charset, collate=collate, kind="NAMES") 709 710 def _parse_type( 711 self, parse_interval: bool = True, fallback_to_identifier: bool = False 712 ) -> t.Optional[exp.Expression]: 713 # mysql binary is special and can work anywhere, even in order by operations 714 # it operates like a no paren func 715 if self._match(TokenType.BINARY, advance=False): 716 data_type = self._parse_types(check_func=True, allow_identifiers=False) 717 718 if isinstance(data_type, exp.DataType): 719 return self.expression(exp.Cast, this=self._parse_column(), to=data_type) 720 721 return super()._parse_type( 722 parse_interval=parse_interval, fallback_to_identifier=fallback_to_identifier 723 ) 724 725 def _parse_alter_table_alter_index(self) -> exp.AlterIndex: 726 index = self._parse_field(any_token=True) 727 728 if self._match_text_seq("VISIBLE"): 729 visible = True 730 elif self._match_text_seq("INVISIBLE"): 731 visible = False 732 else: 733 visible = None 734 735 return self.expression(exp.AlterIndex, this=index, visible=visible) 736 737 def _parse_partition_property( 738 self, 739 ) -> t.Optional[exp.Expression] | t.List[exp.Expression]: 740 partition_cls: t.Optional[t.Type[exp.Expression]] = None 741 value_parser = None 742 743 if self._match_text_seq("RANGE"): 744 partition_cls = exp.PartitionByRangeProperty 745 value_parser = self._parse_partition_range_value 746 elif self._match_text_seq("LIST"): 747 partition_cls = exp.PartitionByListProperty 748 value_parser = self._parse_partition_list_value 749 750 if not partition_cls or not value_parser: 751 return None 752 753 partition_expressions = self._parse_wrapped_csv(self._parse_assignment) 754 755 # For Doris and Starrocks 756 if not self._match_text_seq("(", "PARTITION", advance=False): 757 return partition_expressions 758 759 create_expressions = self._parse_wrapped_csv(value_parser) 760 761 return self.expression( 762 partition_cls, 763 partition_expressions=partition_expressions, 764 create_expressions=create_expressions, 765 ) 766 767 def _parse_partition_range_value(self) -> t.Optional[exp.Expression]: 768 self._match_text_seq("PARTITION") 769 name = self._parse_id_var() 770 771 if not self._match_text_seq("VALUES", "LESS", "THAN"): 772 return name 773 774 values = self._parse_wrapped_csv(self._parse_expression) 775 776 if ( 777 len(values) == 1 778 and isinstance(values[0], exp.Column) 779 and values[0].name.upper() == "MAXVALUE" 780 ): 781 values = [exp.var("MAXVALUE")] 782 783 part_range = self.expression(exp.PartitionRange, this=name, expressions=values) 784 return self.expression(exp.Partition, expressions=[part_range]) 785 786 def _parse_partition_list_value(self) -> exp.Partition: 787 self._match_text_seq("PARTITION") 788 name = self._parse_id_var() 789 self._match_text_seq("VALUES", "IN") 790 values = self._parse_wrapped_csv(self._parse_expression) 791 part_list = self.expression(exp.PartitionList, this=name, expressions=values) 792 return self.expression(exp.Partition, expressions=[part_list]) 793 794 class Generator(generator.Generator): 795 INTERVAL_ALLOWS_PLURAL_FORM = False 796 LOCKING_READS_SUPPORTED = True 797 NULL_ORDERING_SUPPORTED: t.Optional[bool] = None 798 JOIN_HINTS = False 799 TABLE_HINTS = True 800 DUPLICATE_KEY_UPDATE_WITH_SET = False 801 QUERY_HINT_SEP = " " 802 VALUES_AS_TABLE = False 803 NVL2_SUPPORTED = False 804 LAST_DAY_SUPPORTS_DATE_PART = False 805 JSON_TYPE_REQUIRED_FOR_EXTRACTION = True 806 JSON_PATH_BRACKETED_KEY_SUPPORTED = False 807 JSON_KEY_VALUE_PAIR_SEP = "," 808 SUPPORTS_TO_NUMBER = False 809 PARSE_JSON_NAME: t.Optional[str] = None 810 PAD_FILL_PATTERN_IS_REQUIRED = True 811 WRAP_DERIVED_VALUES = False 812 VARCHAR_REQUIRES_SIZE = True 813 SUPPORTS_MEDIAN = False 814 UPDATE_STATEMENT_SUPPORTS_FROM = False 815 816 TRANSFORMS = { 817 **generator.Generator.TRANSFORMS, 818 exp.ArrayAgg: rename_func("GROUP_CONCAT"), 819 exp.BitwiseAndAgg: rename_func("BIT_AND"), 820 exp.BitwiseOrAgg: rename_func("BIT_OR"), 821 exp.BitwiseXorAgg: rename_func("BIT_XOR"), 822 exp.BitwiseCount: rename_func("BIT_COUNT"), 823 exp.Chr: lambda self, e: self.chr_sql(e, "CHAR"), 824 exp.CurrentDate: no_paren_current_date_sql, 825 exp.DateDiff: _remove_ts_or_ds_to_date( 826 lambda self, e: self.func("DATEDIFF", e.this, e.expression), ("this", "expression") 827 ), 828 exp.DateAdd: _remove_ts_or_ds_to_date(date_add_sql("ADD")), 829 exp.DateStrToDate: datestrtodate_sql, 830 exp.DateSub: _remove_ts_or_ds_to_date(date_add_sql("SUB")), 831 exp.DateTrunc: _date_trunc_sql, 832 exp.Day: _remove_ts_or_ds_to_date(), 833 exp.DayOfMonth: _remove_ts_or_ds_to_date(rename_func("DAYOFMONTH")), 834 exp.DayOfWeek: _remove_ts_or_ds_to_date(rename_func("DAYOFWEEK")), 835 exp.DayOfYear: _remove_ts_or_ds_to_date(rename_func("DAYOFYEAR")), 836 exp.GroupConcat: lambda self, 837 e: f"""GROUP_CONCAT({self.sql(e, "this")} SEPARATOR {self.sql(e, "separator") or "','"})""", 838 exp.ILike: no_ilike_sql, 839 exp.JSONExtractScalar: arrow_json_extract_sql, 840 exp.Length: length_or_char_length_sql, 841 exp.LogicalOr: rename_func("MAX"), 842 exp.LogicalAnd: rename_func("MIN"), 843 exp.Max: max_or_greatest, 844 exp.Min: min_or_least, 845 exp.Month: _remove_ts_or_ds_to_date(), 846 exp.NullSafeEQ: lambda self, e: self.binary(e, "<=>"), 847 exp.NullSafeNEQ: lambda self, e: f"NOT {self.binary(e, '<=>')}", 848 exp.NumberToStr: rename_func("FORMAT"), 849 exp.Pivot: no_pivot_sql, 850 exp.Select: transforms.preprocess( 851 [ 852 transforms.eliminate_distinct_on, 853 transforms.eliminate_semi_and_anti_joins, 854 transforms.eliminate_qualify, 855 transforms.eliminate_full_outer_join, 856 transforms.unnest_generate_date_array_using_recursive_cte, 857 ] 858 ), 859 exp.StrPosition: lambda self, e: strposition_sql( 860 self, e, func_name="LOCATE", supports_position=True 861 ), 862 exp.StrToDate: _str_to_date_sql, 863 exp.StrToTime: _str_to_date_sql, 864 exp.Stuff: rename_func("INSERT"), 865 exp.SessionUser: lambda *_: "SESSION_USER()", 866 exp.TableSample: no_tablesample_sql, 867 exp.TimeFromParts: rename_func("MAKETIME"), 868 exp.TimestampAdd: date_add_interval_sql("DATE", "ADD"), 869 exp.TimestampDiff: lambda self, e: self.func( 870 "TIMESTAMPDIFF", unit_to_var(e), e.expression, e.this 871 ), 872 exp.TimestampSub: date_add_interval_sql("DATE", "SUB"), 873 exp.TimeStrToUnix: rename_func("UNIX_TIMESTAMP"), 874 exp.TimeStrToTime: lambda self, e: timestrtotime_sql( 875 self, 876 e, 877 include_precision=not e.args.get("zone"), 878 ), 879 exp.TimeToStr: _remove_ts_or_ds_to_date( 880 lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)) 881 ), 882 exp.Trim: trim_sql, 883 exp.TryCast: no_trycast_sql, 884 exp.TsOrDsAdd: date_add_sql("ADD"), 885 exp.TsOrDsDiff: lambda self, e: self.func("DATEDIFF", e.this, e.expression), 886 exp.TsOrDsToDate: _ts_or_ds_to_date_sql, 887 exp.Unicode: lambda self, e: f"ORD(CONVERT({self.sql(e.this)} USING utf32))", 888 exp.UnixToTime: _unix_to_time_sql, 889 exp.Week: _remove_ts_or_ds_to_date(), 890 exp.WeekOfYear: _remove_ts_or_ds_to_date(rename_func("WEEKOFYEAR")), 891 exp.Year: _remove_ts_or_ds_to_date(), 892 exp.UtcTimestamp: rename_func("UTC_TIMESTAMP"), 893 exp.UtcTime: rename_func("UTC_TIME"), 894 } 895 896 UNSIGNED_TYPE_MAPPING = { 897 exp.DataType.Type.UBIGINT: "BIGINT", 898 exp.DataType.Type.UINT: "INT", 899 exp.DataType.Type.UMEDIUMINT: "MEDIUMINT", 900 exp.DataType.Type.USMALLINT: "SMALLINT", 901 exp.DataType.Type.UTINYINT: "TINYINT", 902 exp.DataType.Type.UDECIMAL: "DECIMAL", 903 exp.DataType.Type.UDOUBLE: "DOUBLE", 904 } 905 906 TIMESTAMP_TYPE_MAPPING = { 907 exp.DataType.Type.DATETIME2: "DATETIME", 908 exp.DataType.Type.SMALLDATETIME: "DATETIME", 909 exp.DataType.Type.TIMESTAMP: "DATETIME", 910 exp.DataType.Type.TIMESTAMPNTZ: "DATETIME", 911 exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP", 912 exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP", 913 } 914 915 TYPE_MAPPING = { 916 **generator.Generator.TYPE_MAPPING, 917 **UNSIGNED_TYPE_MAPPING, 918 **TIMESTAMP_TYPE_MAPPING, 919 } 920 921 TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMTEXT) 922 TYPE_MAPPING.pop(exp.DataType.Type.LONGTEXT) 923 TYPE_MAPPING.pop(exp.DataType.Type.TINYTEXT) 924 TYPE_MAPPING.pop(exp.DataType.Type.BLOB) 925 TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMBLOB) 926 TYPE_MAPPING.pop(exp.DataType.Type.LONGBLOB) 927 TYPE_MAPPING.pop(exp.DataType.Type.TINYBLOB) 928 929 PROPERTIES_LOCATION = { 930 **generator.Generator.PROPERTIES_LOCATION, 931 exp.TransientProperty: exp.Properties.Location.UNSUPPORTED, 932 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 933 exp.PartitionedByProperty: exp.Properties.Location.UNSUPPORTED, 934 exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA, 935 exp.PartitionByListProperty: exp.Properties.Location.POST_SCHEMA, 936 } 937 938 LIMIT_FETCH = "LIMIT" 939 940 LIMIT_ONLY_LITERALS = True 941 942 CHAR_CAST_MAPPING = dict.fromkeys( 943 ( 944 exp.DataType.Type.LONGTEXT, 945 exp.DataType.Type.LONGBLOB, 946 exp.DataType.Type.MEDIUMBLOB, 947 exp.DataType.Type.MEDIUMTEXT, 948 exp.DataType.Type.TEXT, 949 exp.DataType.Type.TINYBLOB, 950 exp.DataType.Type.TINYTEXT, 951 exp.DataType.Type.VARCHAR, 952 ), 953 "CHAR", 954 ) 955 SIGNED_CAST_MAPPING = dict.fromkeys( 956 ( 957 exp.DataType.Type.BIGINT, 958 exp.DataType.Type.BOOLEAN, 959 exp.DataType.Type.INT, 960 exp.DataType.Type.SMALLINT, 961 exp.DataType.Type.TINYINT, 962 exp.DataType.Type.MEDIUMINT, 963 ), 964 "SIGNED", 965 ) 966 967 # MySQL doesn't support many datatypes in cast. 968 # https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast 969 CAST_MAPPING = { 970 **CHAR_CAST_MAPPING, 971 **SIGNED_CAST_MAPPING, 972 exp.DataType.Type.UBIGINT: "UNSIGNED", 973 } 974 975 TIMESTAMP_FUNC_TYPES = { 976 exp.DataType.Type.TIMESTAMPTZ, 977 exp.DataType.Type.TIMESTAMPLTZ, 978 } 979 980 # https://dev.mysql.com/doc/refman/8.0/en/keywords.html 981 RESERVED_KEYWORDS = { 982 "accessible", 983 "add", 984 "all", 985 "alter", 986 "analyze", 987 "and", 988 "as", 989 "asc", 990 "asensitive", 991 "before", 992 "between", 993 "bigint", 994 "binary", 995 "blob", 996 "both", 997 "by", 998 "call", 999 "cascade", 1000 "case", 1001 "change", 1002 "char", 1003 "character", 1004 "check", 1005 "collate", 1006 "column", 1007 "condition", 1008 "constraint", 1009 "continue", 1010 "convert", 1011 "create", 1012 "cross", 1013 "cube", 1014 "cume_dist", 1015 "current_date", 1016 "current_time", 1017 "current_timestamp", 1018 "current_user", 1019 "cursor", 1020 "database", 1021 "databases", 1022 "day_hour", 1023 "day_microsecond", 1024 "day_minute", 1025 "day_second", 1026 "dec", 1027 "decimal", 1028 "declare", 1029 "default", 1030 "delayed", 1031 "delete", 1032 "dense_rank", 1033 "desc", 1034 "describe", 1035 "deterministic", 1036 "distinct", 1037 "distinctrow", 1038 "div", 1039 "double", 1040 "drop", 1041 "dual", 1042 "each", 1043 "else", 1044 "elseif", 1045 "empty", 1046 "enclosed", 1047 "escaped", 1048 "except", 1049 "exists", 1050 "exit", 1051 "explain", 1052 "false", 1053 "fetch", 1054 "first_value", 1055 "float", 1056 "float4", 1057 "float8", 1058 "for", 1059 "force", 1060 "foreign", 1061 "from", 1062 "fulltext", 1063 "function", 1064 "generated", 1065 "get", 1066 "grant", 1067 "group", 1068 "grouping", 1069 "groups", 1070 "having", 1071 "high_priority", 1072 "hour_microsecond", 1073 "hour_minute", 1074 "hour_second", 1075 "if", 1076 "ignore", 1077 "in", 1078 "index", 1079 "infile", 1080 "inner", 1081 "inout", 1082 "insensitive", 1083 "insert", 1084 "int", 1085 "int1", 1086 "int2", 1087 "int3", 1088 "int4", 1089 "int8", 1090 "integer", 1091 "intersect", 1092 "interval", 1093 "into", 1094 "io_after_gtids", 1095 "io_before_gtids", 1096 "is", 1097 "iterate", 1098 "join", 1099 "json_table", 1100 "key", 1101 "keys", 1102 "kill", 1103 "lag", 1104 "last_value", 1105 "lateral", 1106 "lead", 1107 "leading", 1108 "leave", 1109 "left", 1110 "like", 1111 "limit", 1112 "linear", 1113 "lines", 1114 "load", 1115 "localtime", 1116 "localtimestamp", 1117 "lock", 1118 "long", 1119 "longblob", 1120 "longtext", 1121 "loop", 1122 "low_priority", 1123 "master_bind", 1124 "master_ssl_verify_server_cert", 1125 "match", 1126 "maxvalue", 1127 "mediumblob", 1128 "mediumint", 1129 "mediumtext", 1130 "middleint", 1131 "minute_microsecond", 1132 "minute_second", 1133 "mod", 1134 "modifies", 1135 "natural", 1136 "not", 1137 "no_write_to_binlog", 1138 "nth_value", 1139 "ntile", 1140 "null", 1141 "numeric", 1142 "of", 1143 "on", 1144 "optimize", 1145 "optimizer_costs", 1146 "option", 1147 "optionally", 1148 "or", 1149 "order", 1150 "out", 1151 "outer", 1152 "outfile", 1153 "over", 1154 "partition", 1155 "percent_rank", 1156 "precision", 1157 "primary", 1158 "procedure", 1159 "purge", 1160 "range", 1161 "rank", 1162 "read", 1163 "reads", 1164 "read_write", 1165 "real", 1166 "recursive", 1167 "references", 1168 "regexp", 1169 "release", 1170 "rename", 1171 "repeat", 1172 "replace", 1173 "require", 1174 "resignal", 1175 "restrict", 1176 "return", 1177 "revoke", 1178 "right", 1179 "rlike", 1180 "row", 1181 "rows", 1182 "row_number", 1183 "schema", 1184 "schemas", 1185 "second_microsecond", 1186 "select", 1187 "sensitive", 1188 "separator", 1189 "set", 1190 "show", 1191 "signal", 1192 "smallint", 1193 "spatial", 1194 "specific", 1195 "sql", 1196 "sqlexception", 1197 "sqlstate", 1198 "sqlwarning", 1199 "sql_big_result", 1200 "sql_calc_found_rows", 1201 "sql_small_result", 1202 "ssl", 1203 "starting", 1204 "stored", 1205 "straight_join", 1206 "system", 1207 "table", 1208 "terminated", 1209 "then", 1210 "tinyblob", 1211 "tinyint", 1212 "tinytext", 1213 "to", 1214 "trailing", 1215 "trigger", 1216 "true", 1217 "undo", 1218 "union", 1219 "unique", 1220 "unlock", 1221 "unsigned", 1222 "update", 1223 "usage", 1224 "use", 1225 "using", 1226 "utc_date", 1227 "utc_time", 1228 "utc_timestamp", 1229 "values", 1230 "varbinary", 1231 "varchar", 1232 "varcharacter", 1233 "varying", 1234 "virtual", 1235 "when", 1236 "where", 1237 "while", 1238 "window", 1239 "with", 1240 "write", 1241 "xor", 1242 "year_month", 1243 "zerofill", 1244 } 1245 1246 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1247 persisted = "STORED" if expression.args.get("persisted") else "VIRTUAL" 1248 return f"GENERATED ALWAYS AS ({self.sql(expression.this.unnest())}) {persisted}" 1249 1250 def array_sql(self, expression: exp.Array) -> str: 1251 self.unsupported("Arrays are not supported by MySQL") 1252 return self.function_fallback_sql(expression) 1253 1254 def arraycontainsall_sql(self, expression: exp.ArrayContainsAll) -> str: 1255 self.unsupported("Array operations are not supported by MySQL") 1256 return self.function_fallback_sql(expression) 1257 1258 def dpipe_sql(self, expression: exp.DPipe) -> str: 1259 return self.func("CONCAT", *expression.flatten()) 1260 1261 def extract_sql(self, expression: exp.Extract) -> str: 1262 unit = expression.name 1263 if unit and unit.lower() == "epoch": 1264 return self.func("UNIX_TIMESTAMP", expression.expression) 1265 1266 return super().extract_sql(expression) 1267 1268 def datatype_sql(self, expression: exp.DataType) -> str: 1269 if ( 1270 self.VARCHAR_REQUIRES_SIZE 1271 and expression.is_type(exp.DataType.Type.VARCHAR) 1272 and not expression.expressions 1273 ): 1274 # `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT` 1275 return "TEXT" 1276 1277 # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html 1278 result = super().datatype_sql(expression) 1279 if expression.this in self.UNSIGNED_TYPE_MAPPING: 1280 result = f"{result} UNSIGNED" 1281 1282 return result 1283 1284 def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str: 1285 return f"{self.sql(expression, 'this')} MEMBER OF({self.sql(expression, 'expression')})" 1286 1287 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1288 if expression.to.this in self.TIMESTAMP_FUNC_TYPES: 1289 return self.func("TIMESTAMP", expression.this) 1290 1291 to = self.CAST_MAPPING.get(expression.to.this) 1292 1293 if to: 1294 expression.to.set("this", to) 1295 return super().cast_sql(expression) 1296 1297 def show_sql(self, expression: exp.Show) -> str: 1298 this = f" {expression.name}" 1299 full = " FULL" if expression.args.get("full") else "" 1300 global_ = " GLOBAL" if expression.args.get("global_") else "" 1301 1302 target = self.sql(expression, "target") 1303 target = f" {target}" if target else "" 1304 if expression.name in ("COLUMNS", "INDEX"): 1305 target = f" FROM{target}" 1306 elif expression.name == "GRANTS": 1307 target = f" FOR{target}" 1308 elif expression.name in ("LINKS", "PARTITIONS"): 1309 target = f" ON{target}" if target else "" 1310 elif expression.name == "PROJECTIONS": 1311 target = f" ON TABLE{target}" if target else "" 1312 1313 db = self._prefixed_sql("FROM", expression, "db") 1314 1315 like = self._prefixed_sql("LIKE", expression, "like") 1316 where = self.sql(expression, "where") 1317 1318 types = self.expressions(expression, key="types") 1319 types = f" {types}" if types else types 1320 query = self._prefixed_sql("FOR QUERY", expression, "query") 1321 1322 if expression.name == "PROFILE": 1323 offset = self._prefixed_sql("OFFSET", expression, "offset") 1324 limit = self._prefixed_sql("LIMIT", expression, "limit") 1325 else: 1326 offset = "" 1327 limit = self._oldstyle_limit_sql(expression) 1328 1329 log = self._prefixed_sql("IN", expression, "log") 1330 position = self._prefixed_sql("FROM", expression, "position") 1331 1332 channel = self._prefixed_sql("FOR CHANNEL", expression, "channel") 1333 1334 if expression.name == "ENGINE": 1335 mutex_or_status = " MUTEX" if expression.args.get("mutex") else " STATUS" 1336 else: 1337 mutex_or_status = "" 1338 1339 for_table = self._prefixed_sql("FOR TABLE", expression, "for_table") 1340 for_group = self._prefixed_sql("FOR GROUP", expression, "for_group") 1341 for_user = self._prefixed_sql("FOR USER", expression, "for_user") 1342 for_role = self._prefixed_sql("FOR ROLE", expression, "for_role") 1343 into_outfile = self._prefixed_sql("INTO OUTFILE", expression, "into_outfile") 1344 json = " JSON" if expression.args.get("json") else "" 1345 1346 return f"SHOW{full}{global_}{this}{json}{target}{for_table}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}{for_group}{for_user}{for_role}{into_outfile}" 1347 1348 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 1349 """To avoid TO keyword in ALTER ... RENAME statements. 1350 It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks. 1351 """ 1352 return super().alterrename_sql(expression, include_to=False) 1353 1354 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 1355 dtype = self.sql(expression, "dtype") 1356 if not dtype: 1357 return super().altercolumn_sql(expression) 1358 1359 this = self.sql(expression, "this") 1360 return f"MODIFY COLUMN {this} {dtype}" 1361 1362 def _prefixed_sql(self, prefix: str, expression: exp.Expression, arg: str) -> str: 1363 sql = self.sql(expression, arg) 1364 return f" {prefix} {sql}" if sql else "" 1365 1366 def _oldstyle_limit_sql(self, expression: exp.Show) -> str: 1367 limit = self.sql(expression, "limit") 1368 offset = self.sql(expression, "offset") 1369 if limit: 1370 limit_offset = f"{offset}, {limit}" if offset else limit 1371 return f" LIMIT {limit_offset}" 1372 return "" 1373 1374 def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str: 1375 unit = expression.args.get("unit") 1376 1377 # Pick an old-enough date to avoid negative timestamp diffs 1378 start_ts = "'0000-01-01 00:00:00'" 1379 1380 # Source: https://stackoverflow.com/a/32955740 1381 timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this]) 1382 interval = exp.Interval(this=timestamp_diff, unit=unit) 1383 dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval]) 1384 1385 return self.sql(dateadd) 1386 1387 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 1388 from_tz = expression.args.get("source_tz") 1389 to_tz = expression.args.get("target_tz") 1390 dt = expression.args.get("timestamp") 1391 1392 return self.func("CONVERT_TZ", dt, from_tz, to_tz) 1393 1394 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 1395 self.unsupported("AT TIME ZONE is not supported by MySQL") 1396 return self.sql(expression.this) 1397 1398 def isascii_sql(self, expression: exp.IsAscii) -> str: 1399 return f"REGEXP_LIKE({self.sql(expression.this)}, '^[[:ascii:]]*$')" 1400 1401 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 1402 # https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html 1403 self.unsupported("MySQL does not support IGNORE NULLS.") 1404 return self.sql(expression.this) 1405 1406 @unsupported_args("this") 1407 def currentschema_sql(self, expression: exp.CurrentSchema) -> str: 1408 return self.func("SCHEMA") 1409 1410 def partition_sql(self, expression: exp.Partition) -> str: 1411 parent = expression.parent 1412 if isinstance(parent, (exp.PartitionByRangeProperty, exp.PartitionByListProperty)): 1413 return self.expressions(expression, flat=True) 1414 return super().partition_sql(expression) 1415 1416 def _partition_by_sql( 1417 self, expression: exp.PartitionByRangeProperty | exp.PartitionByListProperty, kind: str 1418 ) -> str: 1419 partitions = self.expressions(expression, key="partition_expressions", flat=True) 1420 create = self.expressions(expression, key="create_expressions", flat=True) 1421 return f"PARTITION BY {kind} ({partitions}) ({create})" 1422 1423 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 1424 return self._partition_by_sql(expression, "RANGE") 1425 1426 def partitionbylistproperty_sql(self, expression: exp.PartitionByListProperty) -> str: 1427 return self._partition_by_sql(expression, "LIST") 1428 1429 def partitionlist_sql(self, expression: exp.PartitionList) -> str: 1430 name = self.sql(expression, "this") 1431 values = self.expressions(expression, flat=True) 1432 return f"PARTITION {name} VALUES IN ({values})" 1433 1434 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 1435 name = self.sql(expression, "this") 1436 values = self.expressions(expression, flat=True) 1437 return f"PARTITION {name} VALUES LESS THAN ({values})"
This flag is used in the optimizer's canonicalize rule and determines whether x will be promoted to the literal's type in x::DATE < '2020-01-01 12:05:03' (i.e., DATETIME). When false, the literal is cast to x's type to match it instead.
Specifies the strategy according to which identifiers should be normalized.
Whether LEAST/GREATEST functions ignore NULL values, e.g:
- BigQuery, Snowflake, MySQL, Presto/Trino: LEAST(1, NULL, 2) -> NULL
- Spark, Postgres, DuckDB, TSQL: LEAST(1, NULL, 2) -> 1
Associates this dialect's time formats with their equivalent Python strftime formats.
Mapping of an escaped sequence (\n) to its unescaped version (
).
203 class Tokenizer(tokens.Tokenizer): 204 QUOTES = ["'", '"'] 205 COMMENTS = ["--", "#", ("/*", "*/")] 206 IDENTIFIERS = ["`"] 207 STRING_ESCAPES = ["'", '"', "\\"] 208 BIT_STRINGS = [("b'", "'"), ("B'", "'"), ("0b", "")] 209 HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", "")] 210 # https://dev.mysql.com/doc/refman/8.4/en/string-literals.html 211 ESCAPE_FOLLOW_CHARS = ["0", "b", "n", "r", "t", "Z", "%", "_"] 212 213 NESTED_COMMENTS = False 214 215 KEYWORDS = { 216 **tokens.Tokenizer.KEYWORDS, 217 "BLOB": TokenType.BLOB, 218 "CHARSET": TokenType.CHARACTER_SET, 219 "DISTINCTROW": TokenType.DISTINCT, 220 "EXPLAIN": TokenType.DESCRIBE, 221 "FORCE": TokenType.FORCE, 222 "IGNORE": TokenType.IGNORE, 223 "KEY": TokenType.KEY, 224 "LOCK TABLES": TokenType.COMMAND, 225 "LONGBLOB": TokenType.LONGBLOB, 226 "LONGTEXT": TokenType.LONGTEXT, 227 "MEDIUMBLOB": TokenType.MEDIUMBLOB, 228 "MEDIUMINT": TokenType.MEDIUMINT, 229 "MEDIUMTEXT": TokenType.MEDIUMTEXT, 230 "MEMBER OF": TokenType.MEMBER_OF, 231 "MOD": TokenType.MOD, 232 "SEPARATOR": TokenType.SEPARATOR, 233 "SERIAL": TokenType.SERIAL, 234 "SIGNED": TokenType.BIGINT, 235 "SIGNED INTEGER": TokenType.BIGINT, 236 "SOUNDS LIKE": TokenType.SOUNDS_LIKE, 237 "START": TokenType.BEGIN, 238 "TIMESTAMP": TokenType.TIMESTAMPTZ, 239 "TINYBLOB": TokenType.TINYBLOB, 240 "TINYTEXT": TokenType.TINYTEXT, 241 "UNLOCK TABLES": TokenType.COMMAND, 242 "UNSIGNED": TokenType.UBIGINT, 243 "UNSIGNED INTEGER": TokenType.UBIGINT, 244 "YEAR": TokenType.YEAR, 245 "_ARMSCII8": TokenType.INTRODUCER, 246 "_ASCII": TokenType.INTRODUCER, 247 "_BIG5": TokenType.INTRODUCER, 248 "_BINARY": TokenType.INTRODUCER, 249 "_CP1250": TokenType.INTRODUCER, 250 "_CP1251": TokenType.INTRODUCER, 251 "_CP1256": TokenType.INTRODUCER, 252 "_CP1257": TokenType.INTRODUCER, 253 "_CP850": TokenType.INTRODUCER, 254 "_CP852": TokenType.INTRODUCER, 255 "_CP866": TokenType.INTRODUCER, 256 "_CP932": TokenType.INTRODUCER, 257 "_DEC8": TokenType.INTRODUCER, 258 "_EUCJPMS": TokenType.INTRODUCER, 259 "_EUCKR": TokenType.INTRODUCER, 260 "_GB18030": TokenType.INTRODUCER, 261 "_GB2312": TokenType.INTRODUCER, 262 "_GBK": TokenType.INTRODUCER, 263 "_GEOSTD8": TokenType.INTRODUCER, 264 "_GREEK": TokenType.INTRODUCER, 265 "_HEBREW": TokenType.INTRODUCER, 266 "_HP8": TokenType.INTRODUCER, 267 "_KEYBCS2": TokenType.INTRODUCER, 268 "_KOI8R": TokenType.INTRODUCER, 269 "_KOI8U": TokenType.INTRODUCER, 270 "_LATIN1": TokenType.INTRODUCER, 271 "_LATIN2": TokenType.INTRODUCER, 272 "_LATIN5": TokenType.INTRODUCER, 273 "_LATIN7": TokenType.INTRODUCER, 274 "_MACCE": TokenType.INTRODUCER, 275 "_MACROMAN": TokenType.INTRODUCER, 276 "_SJIS": TokenType.INTRODUCER, 277 "_SWE7": TokenType.INTRODUCER, 278 "_TIS620": TokenType.INTRODUCER, 279 "_UCS2": TokenType.INTRODUCER, 280 "_UJIS": TokenType.INTRODUCER, 281 # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html 282 "_UTF8": TokenType.INTRODUCER, 283 "_UTF16": TokenType.INTRODUCER, 284 "_UTF16LE": TokenType.INTRODUCER, 285 "_UTF32": TokenType.INTRODUCER, 286 "_UTF8MB3": TokenType.INTRODUCER, 287 "_UTF8MB4": TokenType.INTRODUCER, 288 "@@": TokenType.SESSION_PARAMETER, 289 } 290 291 COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE} - {TokenType.SHOW}
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- SINGLE_TOKENS
- BYTE_STRINGS
- RAW_STRINGS
- HEREDOC_STRINGS
- UNICODE_STRINGS
- VAR_SINGLE_TOKENS
- IDENTIFIER_ESCAPES
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- HINT_START
- TOKENS_PRECEDING_HINT
- WHITE_SPACE
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- dialect
- use_rs_tokenizer
- reset
- tokenize
- tokenize_rs
- size
- sql
- tokens
293 class Parser(parser.Parser): 294 FUNC_TOKENS = { 295 *parser.Parser.FUNC_TOKENS, 296 TokenType.DATABASE, 297 TokenType.MOD, 298 TokenType.SCHEMA, 299 TokenType.VALUES, 300 TokenType.CHARACTER_SET, 301 } 302 303 CONJUNCTION = { 304 **parser.Parser.CONJUNCTION, 305 TokenType.DAMP: exp.And, 306 TokenType.XOR: exp.Xor, 307 } 308 309 DISJUNCTION = { 310 **parser.Parser.DISJUNCTION, 311 TokenType.DPIPE: exp.Or, 312 } 313 314 TABLE_ALIAS_TOKENS = ( 315 parser.Parser.TABLE_ALIAS_TOKENS - parser.Parser.TABLE_INDEX_HINT_TOKENS 316 ) 317 318 RANGE_PARSERS = { 319 **parser.Parser.RANGE_PARSERS, 320 TokenType.SOUNDS_LIKE: lambda self, this: self.expression( 321 exp.EQ, 322 this=self.expression(exp.Soundex, this=this), 323 expression=self.expression(exp.Soundex, this=self._parse_term()), 324 ), 325 TokenType.MEMBER_OF: lambda self, this: self.expression( 326 exp.JSONArrayContains, 327 this=this, 328 expression=self._parse_wrapped(self._parse_expression), 329 ), 330 } 331 332 FUNCTIONS = { 333 **parser.Parser.FUNCTIONS, 334 "BIT_AND": exp.BitwiseAndAgg.from_arg_list, 335 "BIT_OR": exp.BitwiseOrAgg.from_arg_list, 336 "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, 337 "BIT_COUNT": exp.BitwiseCount.from_arg_list, 338 "CONVERT_TZ": lambda args: exp.ConvertTimezone( 339 source_tz=seq_get(args, 1), target_tz=seq_get(args, 2), timestamp=seq_get(args, 0) 340 ), 341 "CURDATE": exp.CurrentDate.from_arg_list, 342 "DATE": lambda args: exp.TsOrDsToDate(this=seq_get(args, 0)), 343 "DATE_ADD": build_date_delta_with_interval(exp.DateAdd), 344 "DATE_FORMAT": build_formatted_time(exp.TimeToStr, "mysql"), 345 "DATE_SUB": build_date_delta_with_interval(exp.DateSub), 346 "DAY": lambda args: exp.Day(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 347 "DAYOFMONTH": lambda args: exp.DayOfMonth(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 348 "DAYOFWEEK": lambda args: exp.DayOfWeek(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 349 "DAYOFYEAR": lambda args: exp.DayOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 350 "FORMAT": exp.NumberToStr.from_arg_list, 351 "FROM_UNIXTIME": build_formatted_time(exp.UnixToTime, "mysql"), 352 "ISNULL": isnull_to_is_null, 353 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 354 "MAKETIME": exp.TimeFromParts.from_arg_list, 355 "MONTH": lambda args: exp.Month(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 356 "MONTHNAME": lambda args: exp.TimeToStr( 357 this=exp.TsOrDsToDate(this=seq_get(args, 0)), 358 format=exp.Literal.string("%B"), 359 ), 360 "SCHEMA": exp.CurrentSchema.from_arg_list, 361 "DATABASE": exp.CurrentSchema.from_arg_list, 362 "STR_TO_DATE": _str_to_date, 363 "TIMESTAMPDIFF": build_date_delta(exp.TimestampDiff), 364 "TO_DAYS": lambda args: exp.paren( 365 exp.DateDiff( 366 this=exp.TsOrDsToDate(this=seq_get(args, 0)), 367 expression=exp.TsOrDsToDate(this=exp.Literal.string("0000-01-01")), 368 unit=exp.var("DAY"), 369 ) 370 + 1 371 ), 372 "WEEK": lambda args: exp.Week( 373 this=exp.TsOrDsToDate(this=seq_get(args, 0)), mode=seq_get(args, 1) 374 ), 375 "WEEKOFYEAR": lambda args: exp.WeekOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 376 "YEAR": lambda args: exp.Year(this=exp.TsOrDsToDate(this=seq_get(args, 0))), 377 } 378 379 FUNCTION_PARSERS = { 380 **parser.Parser.FUNCTION_PARSERS, 381 "GROUP_CONCAT": lambda self: self._parse_group_concat(), 382 # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values 383 "VALUES": lambda self: self.expression( 384 exp.Anonymous, this="VALUES", expressions=[self._parse_id_var()] 385 ), 386 "JSON_VALUE": lambda self: self._parse_json_value(), 387 "SUBSTR": lambda self: self._parse_substring(), 388 } 389 390 STATEMENT_PARSERS = { 391 **parser.Parser.STATEMENT_PARSERS, 392 TokenType.SHOW: lambda self: self._parse_show(), 393 } 394 395 SHOW_PARSERS = { 396 "BINARY LOGS": _show_parser("BINARY LOGS"), 397 "MASTER LOGS": _show_parser("BINARY LOGS"), 398 "BINLOG EVENTS": _show_parser("BINLOG EVENTS"), 399 "CHARACTER SET": _show_parser("CHARACTER SET"), 400 "CHARSET": _show_parser("CHARACTER SET"), 401 "COLLATION": _show_parser("COLLATION"), 402 "FULL COLUMNS": _show_parser("COLUMNS", target="FROM", full=True), 403 "COLUMNS": _show_parser("COLUMNS", target="FROM"), 404 "CREATE DATABASE": _show_parser("CREATE DATABASE", target=True), 405 "CREATE EVENT": _show_parser("CREATE EVENT", target=True), 406 "CREATE FUNCTION": _show_parser("CREATE FUNCTION", target=True), 407 "CREATE PROCEDURE": _show_parser("CREATE PROCEDURE", target=True), 408 "CREATE TABLE": _show_parser("CREATE TABLE", target=True), 409 "CREATE TRIGGER": _show_parser("CREATE TRIGGER", target=True), 410 "CREATE VIEW": _show_parser("CREATE VIEW", target=True), 411 "DATABASES": _show_parser("DATABASES"), 412 "SCHEMAS": _show_parser("DATABASES"), 413 "ENGINE": _show_parser("ENGINE", target=True), 414 "STORAGE ENGINES": _show_parser("ENGINES"), 415 "ENGINES": _show_parser("ENGINES"), 416 "ERRORS": _show_parser("ERRORS"), 417 "EVENTS": _show_parser("EVENTS"), 418 "FUNCTION CODE": _show_parser("FUNCTION CODE", target=True), 419 "FUNCTION STATUS": _show_parser("FUNCTION STATUS"), 420 "GRANTS": _show_parser("GRANTS", target="FOR"), 421 "INDEX": _show_parser("INDEX", target="FROM"), 422 "MASTER STATUS": _show_parser("MASTER STATUS"), 423 "OPEN TABLES": _show_parser("OPEN TABLES"), 424 "PLUGINS": _show_parser("PLUGINS"), 425 "PROCEDURE CODE": _show_parser("PROCEDURE CODE", target=True), 426 "PROCEDURE STATUS": _show_parser("PROCEDURE STATUS"), 427 "PRIVILEGES": _show_parser("PRIVILEGES"), 428 "FULL PROCESSLIST": _show_parser("PROCESSLIST", full=True), 429 "PROCESSLIST": _show_parser("PROCESSLIST"), 430 "PROFILE": _show_parser("PROFILE"), 431 "PROFILES": _show_parser("PROFILES"), 432 "RELAYLOG EVENTS": _show_parser("RELAYLOG EVENTS"), 433 "REPLICAS": _show_parser("REPLICAS"), 434 "SLAVE HOSTS": _show_parser("REPLICAS"), 435 "REPLICA STATUS": _show_parser("REPLICA STATUS"), 436 "SLAVE STATUS": _show_parser("REPLICA STATUS"), 437 "GLOBAL STATUS": _show_parser("STATUS", global_=True), 438 "SESSION STATUS": _show_parser("STATUS"), 439 "STATUS": _show_parser("STATUS"), 440 "TABLE STATUS": _show_parser("TABLE STATUS"), 441 "FULL TABLES": _show_parser("TABLES", full=True), 442 "TABLES": _show_parser("TABLES"), 443 "TRIGGERS": _show_parser("TRIGGERS"), 444 "GLOBAL VARIABLES": _show_parser("VARIABLES", global_=True), 445 "SESSION VARIABLES": _show_parser("VARIABLES"), 446 "VARIABLES": _show_parser("VARIABLES"), 447 "WARNINGS": _show_parser("WARNINGS"), 448 } 449 450 PROPERTY_PARSERS = { 451 **parser.Parser.PROPERTY_PARSERS, 452 "LOCK": lambda self: self._parse_property_assignment(exp.LockProperty), 453 "PARTITION BY": lambda self: self._parse_partition_property(), 454 } 455 456 SET_PARSERS = { 457 **parser.Parser.SET_PARSERS, 458 "PERSIST": lambda self: self._parse_set_item_assignment("PERSIST"), 459 "PERSIST_ONLY": lambda self: self._parse_set_item_assignment("PERSIST_ONLY"), 460 "CHARACTER SET": lambda self: self._parse_set_item_charset("CHARACTER SET"), 461 "CHARSET": lambda self: self._parse_set_item_charset("CHARACTER SET"), 462 "NAMES": lambda self: self._parse_set_item_names(), 463 } 464 465 CONSTRAINT_PARSERS = { 466 **parser.Parser.CONSTRAINT_PARSERS, 467 "FULLTEXT": lambda self: self._parse_index_constraint(kind="FULLTEXT"), 468 "INDEX": lambda self: self._parse_index_constraint(), 469 "KEY": lambda self: self._parse_index_constraint(), 470 "SPATIAL": lambda self: self._parse_index_constraint(kind="SPATIAL"), 471 "ZEROFILL": lambda self: self.expression(exp.ZeroFillColumnConstraint), 472 } 473 474 ALTER_PARSERS = { 475 **parser.Parser.ALTER_PARSERS, 476 "MODIFY": lambda self: self._parse_alter_table_alter(), 477 } 478 479 ALTER_ALTER_PARSERS = { 480 **parser.Parser.ALTER_ALTER_PARSERS, 481 "INDEX": lambda self: self._parse_alter_table_alter_index(), 482 } 483 484 SCHEMA_UNNAMED_CONSTRAINTS = { 485 *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS, 486 "FULLTEXT", 487 "INDEX", 488 "KEY", 489 "SPATIAL", 490 } 491 492 PROFILE_TYPES: parser.OPTIONS_TYPE = { 493 **dict.fromkeys(("ALL", "CPU", "IPC", "MEMORY", "SOURCE", "SWAPS"), tuple()), 494 "BLOCK": ("IO",), 495 "CONTEXT": ("SWITCHES",), 496 "PAGE": ("FAULTS",), 497 } 498 499 TYPE_TOKENS = { 500 *parser.Parser.TYPE_TOKENS, 501 TokenType.SET, 502 } 503 504 ENUM_TYPE_TOKENS = { 505 *parser.Parser.ENUM_TYPE_TOKENS, 506 TokenType.SET, 507 } 508 509 # SELECT [ ALL | DISTINCT | DISTINCTROW ] [ <OPERATION_MODIFIERS> ] 510 OPERATION_MODIFIERS = { 511 "HIGH_PRIORITY", 512 "STRAIGHT_JOIN", 513 "SQL_SMALL_RESULT", 514 "SQL_BIG_RESULT", 515 "SQL_BUFFER_RESULT", 516 "SQL_NO_CACHE", 517 "SQL_CALC_FOUND_ROWS", 518 } 519 520 LOG_DEFAULTS_TO_LN = True 521 STRING_ALIASES = True 522 VALUES_FOLLOWED_BY_PAREN = False 523 SUPPORTS_PARTITION_SELECTION = True 524 525 def _parse_generated_as_identity( 526 self, 527 ) -> ( 528 exp.GeneratedAsIdentityColumnConstraint 529 | exp.ComputedColumnConstraint 530 | exp.GeneratedAsRowColumnConstraint 531 ): 532 this = super()._parse_generated_as_identity() 533 534 if self._match_texts(("STORED", "VIRTUAL")): 535 persisted = self._prev.text.upper() == "STORED" 536 537 if isinstance(this, exp.ComputedColumnConstraint): 538 this.set("persisted", persisted) 539 elif isinstance(this, exp.GeneratedAsIdentityColumnConstraint): 540 this = self.expression( 541 exp.ComputedColumnConstraint, this=this.expression, persisted=persisted 542 ) 543 544 return this 545 546 def _parse_primary_key_part(self) -> t.Optional[exp.Expression]: 547 this = self._parse_id_var() 548 if not self._match(TokenType.L_PAREN): 549 return this 550 551 expression = self._parse_number() 552 self._match_r_paren() 553 return self.expression(exp.ColumnPrefix, this=this, expression=expression) 554 555 def _parse_index_constraint( 556 self, kind: t.Optional[str] = None 557 ) -> exp.IndexColumnConstraint: 558 if kind: 559 self._match_texts(("INDEX", "KEY")) 560 561 this = self._parse_id_var(any_token=False) 562 index_type = self._match(TokenType.USING) and self._advance_any() and self._prev.text 563 expressions = self._parse_wrapped_csv(self._parse_ordered) 564 565 options = [] 566 while True: 567 if self._match_text_seq("KEY_BLOCK_SIZE"): 568 self._match(TokenType.EQ) 569 opt = exp.IndexConstraintOption(key_block_size=self._parse_number()) 570 elif self._match(TokenType.USING): 571 opt = exp.IndexConstraintOption(using=self._advance_any() and self._prev.text) 572 elif self._match_text_seq("WITH", "PARSER"): 573 opt = exp.IndexConstraintOption(parser=self._parse_var(any_token=True)) 574 elif self._match(TokenType.COMMENT): 575 opt = exp.IndexConstraintOption(comment=self._parse_string()) 576 elif self._match_text_seq("VISIBLE"): 577 opt = exp.IndexConstraintOption(visible=True) 578 elif self._match_text_seq("INVISIBLE"): 579 opt = exp.IndexConstraintOption(visible=False) 580 elif self._match_text_seq("ENGINE_ATTRIBUTE"): 581 self._match(TokenType.EQ) 582 opt = exp.IndexConstraintOption(engine_attr=self._parse_string()) 583 elif self._match_text_seq("SECONDARY_ENGINE_ATTRIBUTE"): 584 self._match(TokenType.EQ) 585 opt = exp.IndexConstraintOption(secondary_engine_attr=self._parse_string()) 586 else: 587 opt = None 588 589 if not opt: 590 break 591 592 options.append(opt) 593 594 return self.expression( 595 exp.IndexColumnConstraint, 596 this=this, 597 expressions=expressions, 598 kind=kind, 599 index_type=index_type, 600 options=options, 601 ) 602 603 def _parse_show_mysql( 604 self, 605 this: str, 606 target: bool | str = False, 607 full: t.Optional[bool] = None, 608 global_: t.Optional[bool] = None, 609 ) -> exp.Show: 610 json = self._match_text_seq("JSON") 611 612 if target: 613 if isinstance(target, str): 614 self._match_text_seq(*target.split(" ")) 615 target_id = self._parse_id_var() 616 else: 617 target_id = None 618 619 log = self._parse_string() if self._match_text_seq("IN") else None 620 621 if this in ("BINLOG EVENTS", "RELAYLOG EVENTS"): 622 position = self._parse_number() if self._match_text_seq("FROM") else None 623 db = None 624 else: 625 position = None 626 db = None 627 628 if self._match(TokenType.FROM): 629 db = self._parse_id_var() 630 elif self._match(TokenType.DOT): 631 db = target_id 632 target_id = self._parse_id_var() 633 634 channel = self._parse_id_var() if self._match_text_seq("FOR", "CHANNEL") else None 635 636 like = self._parse_string() if self._match_text_seq("LIKE") else None 637 where = self._parse_where() 638 639 if this == "PROFILE": 640 types = self._parse_csv(lambda: self._parse_var_from_options(self.PROFILE_TYPES)) 641 query = self._parse_number() if self._match_text_seq("FOR", "QUERY") else None 642 offset = self._parse_number() if self._match_text_seq("OFFSET") else None 643 limit = self._parse_number() if self._match_text_seq("LIMIT") else None 644 else: 645 types, query = None, None 646 offset, limit = self._parse_oldstyle_limit() 647 648 mutex = True if self._match_text_seq("MUTEX") else None 649 mutex = False if self._match_text_seq("STATUS") else mutex 650 651 for_table = self._parse_id_var() if self._match_text_seq("FOR", "TABLE") else None 652 for_group = self._parse_string() if self._match_text_seq("FOR", "GROUP") else None 653 for_user = self._parse_string() if self._match_text_seq("FOR", "USER") else None 654 for_role = self._parse_string() if self._match_text_seq("FOR", "ROLE") else None 655 into_outfile = self._parse_string() if self._match_text_seq("INTO", "OUTFILE") else None 656 657 return self.expression( 658 exp.Show, 659 this=this, 660 target=target_id, 661 full=full, 662 log=log, 663 position=position, 664 db=db, 665 channel=channel, 666 like=like, 667 where=where, 668 types=types, 669 query=query, 670 offset=offset, 671 limit=limit, 672 mutex=mutex, 673 for_table=for_table, 674 for_group=for_group, 675 for_user=for_user, 676 for_role=for_role, 677 into_outfile=into_outfile, 678 json=json, 679 global_=global_, 680 ) 681 682 def _parse_oldstyle_limit( 683 self, 684 ) -> t.Tuple[t.Optional[exp.Expression], t.Optional[exp.Expression]]: 685 limit = None 686 offset = None 687 if self._match_text_seq("LIMIT"): 688 parts = self._parse_csv(self._parse_number) 689 if len(parts) == 1: 690 limit = parts[0] 691 elif len(parts) == 2: 692 limit = parts[1] 693 offset = parts[0] 694 695 return offset, limit 696 697 def _parse_set_item_charset(self, kind: str) -> exp.Expression: 698 this = self._parse_string() or self._parse_unquoted_field() 699 return self.expression(exp.SetItem, this=this, kind=kind) 700 701 def _parse_set_item_names(self) -> exp.Expression: 702 charset = self._parse_string() or self._parse_unquoted_field() 703 if self._match_text_seq("COLLATE"): 704 collate = self._parse_string() or self._parse_unquoted_field() 705 else: 706 collate = None 707 708 return self.expression(exp.SetItem, this=charset, collate=collate, kind="NAMES") 709 710 def _parse_type( 711 self, parse_interval: bool = True, fallback_to_identifier: bool = False 712 ) -> t.Optional[exp.Expression]: 713 # mysql binary is special and can work anywhere, even in order by operations 714 # it operates like a no paren func 715 if self._match(TokenType.BINARY, advance=False): 716 data_type = self._parse_types(check_func=True, allow_identifiers=False) 717 718 if isinstance(data_type, exp.DataType): 719 return self.expression(exp.Cast, this=self._parse_column(), to=data_type) 720 721 return super()._parse_type( 722 parse_interval=parse_interval, fallback_to_identifier=fallback_to_identifier 723 ) 724 725 def _parse_alter_table_alter_index(self) -> exp.AlterIndex: 726 index = self._parse_field(any_token=True) 727 728 if self._match_text_seq("VISIBLE"): 729 visible = True 730 elif self._match_text_seq("INVISIBLE"): 731 visible = False 732 else: 733 visible = None 734 735 return self.expression(exp.AlterIndex, this=index, visible=visible) 736 737 def _parse_partition_property( 738 self, 739 ) -> t.Optional[exp.Expression] | t.List[exp.Expression]: 740 partition_cls: t.Optional[t.Type[exp.Expression]] = None 741 value_parser = None 742 743 if self._match_text_seq("RANGE"): 744 partition_cls = exp.PartitionByRangeProperty 745 value_parser = self._parse_partition_range_value 746 elif self._match_text_seq("LIST"): 747 partition_cls = exp.PartitionByListProperty 748 value_parser = self._parse_partition_list_value 749 750 if not partition_cls or not value_parser: 751 return None 752 753 partition_expressions = self._parse_wrapped_csv(self._parse_assignment) 754 755 # For Doris and Starrocks 756 if not self._match_text_seq("(", "PARTITION", advance=False): 757 return partition_expressions 758 759 create_expressions = self._parse_wrapped_csv(value_parser) 760 761 return self.expression( 762 partition_cls, 763 partition_expressions=partition_expressions, 764 create_expressions=create_expressions, 765 ) 766 767 def _parse_partition_range_value(self) -> t.Optional[exp.Expression]: 768 self._match_text_seq("PARTITION") 769 name = self._parse_id_var() 770 771 if not self._match_text_seq("VALUES", "LESS", "THAN"): 772 return name 773 774 values = self._parse_wrapped_csv(self._parse_expression) 775 776 if ( 777 len(values) == 1 778 and isinstance(values[0], exp.Column) 779 and values[0].name.upper() == "MAXVALUE" 780 ): 781 values = [exp.var("MAXVALUE")] 782 783 part_range = self.expression(exp.PartitionRange, this=name, expressions=values) 784 return self.expression(exp.Partition, expressions=[part_range]) 785 786 def _parse_partition_list_value(self) -> exp.Partition: 787 self._match_text_seq("PARTITION") 788 name = self._parse_id_var() 789 self._match_text_seq("VALUES", "IN") 790 values = self._parse_wrapped_csv(self._parse_expression) 791 part_list = self.expression(exp.PartitionList, this=name, expressions=values) 792 return self.expression(exp.Partition, expressions=[part_list])
Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
Arguments:
- error_level: The desired error level. Default: ErrorLevel.IMMEDIATE
- error_message_context: The amount of context to capture from a query string when displaying the error message (in number of characters). Default: 100
- max_errors: Maximum number of error messages to include in a raised ParseError. This is only relevant if error_level is ErrorLevel.RAISE. Default: 3
Inherited Members
- sqlglot.parser.Parser
- Parser
- NO_PAREN_FUNCTIONS
- STRUCT_TYPE_TOKENS
- NESTED_TYPE_TOKENS
- AGGREGATE_TYPE_TOKENS
- SIGNED_TO_UNSIGNED_TYPE_TOKEN
- SUBQUERY_PREDICATES
- RESERVED_TOKENS
- DB_CREATABLES
- CREATABLES
- ALTERABLES
- ALIAS_TOKENS
- COLON_PLACEHOLDER_TOKENS
- ARRAY_CONSTRUCTORS
- COMMENT_TABLE_ALIAS_TOKENS
- UPDATE_ALIAS_TOKENS
- TRIM_TYPES
- ASSIGNMENT
- EQUALITY
- COMPARISON
- BITWISE
- TERM
- FACTOR
- EXPONENT
- TIMES
- TIMESTAMPS
- SET_OPERATIONS
- JOIN_METHODS
- JOIN_SIDES
- JOIN_KINDS
- JOIN_HINTS
- LAMBDAS
- COLUMN_OPERATORS
- CAST_COLUMN_OPERATORS
- EXPRESSION_PARSERS
- UNARY_PARSERS
- STRING_PARSERS
- NUMERIC_PARSERS
- PRIMARY_PARSERS
- PLACEHOLDER_PARSERS
- PIPE_SYNTAX_TRANSFORM_PARSERS
- NO_PAREN_FUNCTION_PARSERS
- INVALID_FUNC_NAME_TOKENS
- FUNCTIONS_WITH_ALIASED_ARGS
- KEY_VALUE_DEFINITIONS
- QUERY_MODIFIER_PARSERS
- QUERY_MODIFIER_TOKENS
- TYPE_LITERAL_PARSERS
- TYPE_CONVERTERS
- DDL_SELECT_TOKENS
- PRE_VOLATILE_TOKENS
- TRANSACTION_KIND
- TRANSACTION_CHARACTERISTICS
- CONFLICT_ACTIONS
- CREATE_SEQUENCE
- ISOLATED_LOADING_OPTIONS
- USABLES
- CAST_ACTIONS
- SCHEMA_BINDING_OPTIONS
- PROCEDURE_OPTIONS
- EXECUTE_AS_OPTIONS
- KEY_CONSTRAINT_OPTIONS
- WINDOW_EXCLUDE_OPTIONS
- INSERT_ALTERNATIVES
- CLONE_KEYWORDS
- HISTORICAL_DATA_PREFIX
- HISTORICAL_DATA_KIND
- OPCLASS_FOLLOW_KEYWORDS
- OPTYPE_FOLLOW_TOKENS
- TABLE_INDEX_HINT_TOKENS
- VIEW_ATTRIBUTES
- WINDOW_ALIAS_TOKENS
- WINDOW_BEFORE_PAREN_TOKENS
- WINDOW_SIDES
- JSON_KEY_VALUE_SEPARATOR_TOKENS
- FETCH_TOKENS
- ADD_CONSTRAINT_TOKENS
- DISTINCT_TOKENS
- UNNEST_OFFSET_ALIAS_TOKENS
- SELECT_START_TOKENS
- COPY_INTO_VARLEN_OPTIONS
- IS_JSON_PREDICATE_KIND
- ODBC_DATETIME_LITERALS
- ON_CONDITION_TOKENS
- PRIVILEGE_FOLLOW_TOKENS
- DESCRIBE_STYLES
- SET_ASSIGNMENT_DELIMITERS
- ANALYZE_STYLES
- ANALYZE_EXPRESSION_PARSERS
- PARTITION_KEYWORDS
- AMBIGUOUS_ALIAS_TOKENS
- RECURSIVE_CTE_SEARCH_KIND
- MODIFIABLES
- STRICT_CAST
- PREFIXED_PIVOT_COLUMNS
- IDENTIFY_PIVOT_STRINGS
- TABLESAMPLE_CSV
- DEFAULT_SAMPLING_METHOD
- SET_REQUIRES_ASSIGNMENT_DELIMITER
- TRIM_PATTERN_FIRST
- MODIFIERS_ATTACHED_TO_SET_OP
- SET_OP_MODIFIERS
- NO_PAREN_IF_COMMANDS
- JSON_ARROWS_REQUIRE_JSON_TYPE
- COLON_IS_VARIANT_EXTRACT
- SUPPORTS_IMPLICIT_UNNEST
- INTERVAL_SPANS
- WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
- OPTIONAL_ALIAS_TOKEN_CTE
- ALTER_RENAME_REQUIRES_COLUMN
- ALTER_TABLE_PARTITIONS
- JOINS_HAVE_EQUAL_PRECEDENCE
- ZONE_AWARE_TIMESTAMP_CONSTRUCTOR
- MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS
- JSON_EXTRACT_REQUIRES_JSON_EXPRESSION
- ADD_JOIN_ON_TRUE
- SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
- error_level
- error_message_context
- max_errors
- dialect
- reset
- parse
- parse_into
- check_errors
- raise_error
- expression
- validate_expression
- parse_set_operation
- build_cast
- errors
- sql
794 class Generator(generator.Generator): 795 INTERVAL_ALLOWS_PLURAL_FORM = False 796 LOCKING_READS_SUPPORTED = True 797 NULL_ORDERING_SUPPORTED: t.Optional[bool] = None 798 JOIN_HINTS = False 799 TABLE_HINTS = True 800 DUPLICATE_KEY_UPDATE_WITH_SET = False 801 QUERY_HINT_SEP = " " 802 VALUES_AS_TABLE = False 803 NVL2_SUPPORTED = False 804 LAST_DAY_SUPPORTS_DATE_PART = False 805 JSON_TYPE_REQUIRED_FOR_EXTRACTION = True 806 JSON_PATH_BRACKETED_KEY_SUPPORTED = False 807 JSON_KEY_VALUE_PAIR_SEP = "," 808 SUPPORTS_TO_NUMBER = False 809 PARSE_JSON_NAME: t.Optional[str] = None 810 PAD_FILL_PATTERN_IS_REQUIRED = True 811 WRAP_DERIVED_VALUES = False 812 VARCHAR_REQUIRES_SIZE = True 813 SUPPORTS_MEDIAN = False 814 UPDATE_STATEMENT_SUPPORTS_FROM = False 815 816 TRANSFORMS = { 817 **generator.Generator.TRANSFORMS, 818 exp.ArrayAgg: rename_func("GROUP_CONCAT"), 819 exp.BitwiseAndAgg: rename_func("BIT_AND"), 820 exp.BitwiseOrAgg: rename_func("BIT_OR"), 821 exp.BitwiseXorAgg: rename_func("BIT_XOR"), 822 exp.BitwiseCount: rename_func("BIT_COUNT"), 823 exp.Chr: lambda self, e: self.chr_sql(e, "CHAR"), 824 exp.CurrentDate: no_paren_current_date_sql, 825 exp.DateDiff: _remove_ts_or_ds_to_date( 826 lambda self, e: self.func("DATEDIFF", e.this, e.expression), ("this", "expression") 827 ), 828 exp.DateAdd: _remove_ts_or_ds_to_date(date_add_sql("ADD")), 829 exp.DateStrToDate: datestrtodate_sql, 830 exp.DateSub: _remove_ts_or_ds_to_date(date_add_sql("SUB")), 831 exp.DateTrunc: _date_trunc_sql, 832 exp.Day: _remove_ts_or_ds_to_date(), 833 exp.DayOfMonth: _remove_ts_or_ds_to_date(rename_func("DAYOFMONTH")), 834 exp.DayOfWeek: _remove_ts_or_ds_to_date(rename_func("DAYOFWEEK")), 835 exp.DayOfYear: _remove_ts_or_ds_to_date(rename_func("DAYOFYEAR")), 836 exp.GroupConcat: lambda self, 837 e: f"""GROUP_CONCAT({self.sql(e, "this")} SEPARATOR {self.sql(e, "separator") or "','"})""", 838 exp.ILike: no_ilike_sql, 839 exp.JSONExtractScalar: arrow_json_extract_sql, 840 exp.Length: length_or_char_length_sql, 841 exp.LogicalOr: rename_func("MAX"), 842 exp.LogicalAnd: rename_func("MIN"), 843 exp.Max: max_or_greatest, 844 exp.Min: min_or_least, 845 exp.Month: _remove_ts_or_ds_to_date(), 846 exp.NullSafeEQ: lambda self, e: self.binary(e, "<=>"), 847 exp.NullSafeNEQ: lambda self, e: f"NOT {self.binary(e, '<=>')}", 848 exp.NumberToStr: rename_func("FORMAT"), 849 exp.Pivot: no_pivot_sql, 850 exp.Select: transforms.preprocess( 851 [ 852 transforms.eliminate_distinct_on, 853 transforms.eliminate_semi_and_anti_joins, 854 transforms.eliminate_qualify, 855 transforms.eliminate_full_outer_join, 856 transforms.unnest_generate_date_array_using_recursive_cte, 857 ] 858 ), 859 exp.StrPosition: lambda self, e: strposition_sql( 860 self, e, func_name="LOCATE", supports_position=True 861 ), 862 exp.StrToDate: _str_to_date_sql, 863 exp.StrToTime: _str_to_date_sql, 864 exp.Stuff: rename_func("INSERT"), 865 exp.SessionUser: lambda *_: "SESSION_USER()", 866 exp.TableSample: no_tablesample_sql, 867 exp.TimeFromParts: rename_func("MAKETIME"), 868 exp.TimestampAdd: date_add_interval_sql("DATE", "ADD"), 869 exp.TimestampDiff: lambda self, e: self.func( 870 "TIMESTAMPDIFF", unit_to_var(e), e.expression, e.this 871 ), 872 exp.TimestampSub: date_add_interval_sql("DATE", "SUB"), 873 exp.TimeStrToUnix: rename_func("UNIX_TIMESTAMP"), 874 exp.TimeStrToTime: lambda self, e: timestrtotime_sql( 875 self, 876 e, 877 include_precision=not e.args.get("zone"), 878 ), 879 exp.TimeToStr: _remove_ts_or_ds_to_date( 880 lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)) 881 ), 882 exp.Trim: trim_sql, 883 exp.TryCast: no_trycast_sql, 884 exp.TsOrDsAdd: date_add_sql("ADD"), 885 exp.TsOrDsDiff: lambda self, e: self.func("DATEDIFF", e.this, e.expression), 886 exp.TsOrDsToDate: _ts_or_ds_to_date_sql, 887 exp.Unicode: lambda self, e: f"ORD(CONVERT({self.sql(e.this)} USING utf32))", 888 exp.UnixToTime: _unix_to_time_sql, 889 exp.Week: _remove_ts_or_ds_to_date(), 890 exp.WeekOfYear: _remove_ts_or_ds_to_date(rename_func("WEEKOFYEAR")), 891 exp.Year: _remove_ts_or_ds_to_date(), 892 exp.UtcTimestamp: rename_func("UTC_TIMESTAMP"), 893 exp.UtcTime: rename_func("UTC_TIME"), 894 } 895 896 UNSIGNED_TYPE_MAPPING = { 897 exp.DataType.Type.UBIGINT: "BIGINT", 898 exp.DataType.Type.UINT: "INT", 899 exp.DataType.Type.UMEDIUMINT: "MEDIUMINT", 900 exp.DataType.Type.USMALLINT: "SMALLINT", 901 exp.DataType.Type.UTINYINT: "TINYINT", 902 exp.DataType.Type.UDECIMAL: "DECIMAL", 903 exp.DataType.Type.UDOUBLE: "DOUBLE", 904 } 905 906 TIMESTAMP_TYPE_MAPPING = { 907 exp.DataType.Type.DATETIME2: "DATETIME", 908 exp.DataType.Type.SMALLDATETIME: "DATETIME", 909 exp.DataType.Type.TIMESTAMP: "DATETIME", 910 exp.DataType.Type.TIMESTAMPNTZ: "DATETIME", 911 exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP", 912 exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP", 913 } 914 915 TYPE_MAPPING = { 916 **generator.Generator.TYPE_MAPPING, 917 **UNSIGNED_TYPE_MAPPING, 918 **TIMESTAMP_TYPE_MAPPING, 919 } 920 921 TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMTEXT) 922 TYPE_MAPPING.pop(exp.DataType.Type.LONGTEXT) 923 TYPE_MAPPING.pop(exp.DataType.Type.TINYTEXT) 924 TYPE_MAPPING.pop(exp.DataType.Type.BLOB) 925 TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMBLOB) 926 TYPE_MAPPING.pop(exp.DataType.Type.LONGBLOB) 927 TYPE_MAPPING.pop(exp.DataType.Type.TINYBLOB) 928 929 PROPERTIES_LOCATION = { 930 **generator.Generator.PROPERTIES_LOCATION, 931 exp.TransientProperty: exp.Properties.Location.UNSUPPORTED, 932 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 933 exp.PartitionedByProperty: exp.Properties.Location.UNSUPPORTED, 934 exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA, 935 exp.PartitionByListProperty: exp.Properties.Location.POST_SCHEMA, 936 } 937 938 LIMIT_FETCH = "LIMIT" 939 940 LIMIT_ONLY_LITERALS = True 941 942 CHAR_CAST_MAPPING = dict.fromkeys( 943 ( 944 exp.DataType.Type.LONGTEXT, 945 exp.DataType.Type.LONGBLOB, 946 exp.DataType.Type.MEDIUMBLOB, 947 exp.DataType.Type.MEDIUMTEXT, 948 exp.DataType.Type.TEXT, 949 exp.DataType.Type.TINYBLOB, 950 exp.DataType.Type.TINYTEXT, 951 exp.DataType.Type.VARCHAR, 952 ), 953 "CHAR", 954 ) 955 SIGNED_CAST_MAPPING = dict.fromkeys( 956 ( 957 exp.DataType.Type.BIGINT, 958 exp.DataType.Type.BOOLEAN, 959 exp.DataType.Type.INT, 960 exp.DataType.Type.SMALLINT, 961 exp.DataType.Type.TINYINT, 962 exp.DataType.Type.MEDIUMINT, 963 ), 964 "SIGNED", 965 ) 966 967 # MySQL doesn't support many datatypes in cast. 968 # https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast 969 CAST_MAPPING = { 970 **CHAR_CAST_MAPPING, 971 **SIGNED_CAST_MAPPING, 972 exp.DataType.Type.UBIGINT: "UNSIGNED", 973 } 974 975 TIMESTAMP_FUNC_TYPES = { 976 exp.DataType.Type.TIMESTAMPTZ, 977 exp.DataType.Type.TIMESTAMPLTZ, 978 } 979 980 # https://dev.mysql.com/doc/refman/8.0/en/keywords.html 981 RESERVED_KEYWORDS = { 982 "accessible", 983 "add", 984 "all", 985 "alter", 986 "analyze", 987 "and", 988 "as", 989 "asc", 990 "asensitive", 991 "before", 992 "between", 993 "bigint", 994 "binary", 995 "blob", 996 "both", 997 "by", 998 "call", 999 "cascade", 1000 "case", 1001 "change", 1002 "char", 1003 "character", 1004 "check", 1005 "collate", 1006 "column", 1007 "condition", 1008 "constraint", 1009 "continue", 1010 "convert", 1011 "create", 1012 "cross", 1013 "cube", 1014 "cume_dist", 1015 "current_date", 1016 "current_time", 1017 "current_timestamp", 1018 "current_user", 1019 "cursor", 1020 "database", 1021 "databases", 1022 "day_hour", 1023 "day_microsecond", 1024 "day_minute", 1025 "day_second", 1026 "dec", 1027 "decimal", 1028 "declare", 1029 "default", 1030 "delayed", 1031 "delete", 1032 "dense_rank", 1033 "desc", 1034 "describe", 1035 "deterministic", 1036 "distinct", 1037 "distinctrow", 1038 "div", 1039 "double", 1040 "drop", 1041 "dual", 1042 "each", 1043 "else", 1044 "elseif", 1045 "empty", 1046 "enclosed", 1047 "escaped", 1048 "except", 1049 "exists", 1050 "exit", 1051 "explain", 1052 "false", 1053 "fetch", 1054 "first_value", 1055 "float", 1056 "float4", 1057 "float8", 1058 "for", 1059 "force", 1060 "foreign", 1061 "from", 1062 "fulltext", 1063 "function", 1064 "generated", 1065 "get", 1066 "grant", 1067 "group", 1068 "grouping", 1069 "groups", 1070 "having", 1071 "high_priority", 1072 "hour_microsecond", 1073 "hour_minute", 1074 "hour_second", 1075 "if", 1076 "ignore", 1077 "in", 1078 "index", 1079 "infile", 1080 "inner", 1081 "inout", 1082 "insensitive", 1083 "insert", 1084 "int", 1085 "int1", 1086 "int2", 1087 "int3", 1088 "int4", 1089 "int8", 1090 "integer", 1091 "intersect", 1092 "interval", 1093 "into", 1094 "io_after_gtids", 1095 "io_before_gtids", 1096 "is", 1097 "iterate", 1098 "join", 1099 "json_table", 1100 "key", 1101 "keys", 1102 "kill", 1103 "lag", 1104 "last_value", 1105 "lateral", 1106 "lead", 1107 "leading", 1108 "leave", 1109 "left", 1110 "like", 1111 "limit", 1112 "linear", 1113 "lines", 1114 "load", 1115 "localtime", 1116 "localtimestamp", 1117 "lock", 1118 "long", 1119 "longblob", 1120 "longtext", 1121 "loop", 1122 "low_priority", 1123 "master_bind", 1124 "master_ssl_verify_server_cert", 1125 "match", 1126 "maxvalue", 1127 "mediumblob", 1128 "mediumint", 1129 "mediumtext", 1130 "middleint", 1131 "minute_microsecond", 1132 "minute_second", 1133 "mod", 1134 "modifies", 1135 "natural", 1136 "not", 1137 "no_write_to_binlog", 1138 "nth_value", 1139 "ntile", 1140 "null", 1141 "numeric", 1142 "of", 1143 "on", 1144 "optimize", 1145 "optimizer_costs", 1146 "option", 1147 "optionally", 1148 "or", 1149 "order", 1150 "out", 1151 "outer", 1152 "outfile", 1153 "over", 1154 "partition", 1155 "percent_rank", 1156 "precision", 1157 "primary", 1158 "procedure", 1159 "purge", 1160 "range", 1161 "rank", 1162 "read", 1163 "reads", 1164 "read_write", 1165 "real", 1166 "recursive", 1167 "references", 1168 "regexp", 1169 "release", 1170 "rename", 1171 "repeat", 1172 "replace", 1173 "require", 1174 "resignal", 1175 "restrict", 1176 "return", 1177 "revoke", 1178 "right", 1179 "rlike", 1180 "row", 1181 "rows", 1182 "row_number", 1183 "schema", 1184 "schemas", 1185 "second_microsecond", 1186 "select", 1187 "sensitive", 1188 "separator", 1189 "set", 1190 "show", 1191 "signal", 1192 "smallint", 1193 "spatial", 1194 "specific", 1195 "sql", 1196 "sqlexception", 1197 "sqlstate", 1198 "sqlwarning", 1199 "sql_big_result", 1200 "sql_calc_found_rows", 1201 "sql_small_result", 1202 "ssl", 1203 "starting", 1204 "stored", 1205 "straight_join", 1206 "system", 1207 "table", 1208 "terminated", 1209 "then", 1210 "tinyblob", 1211 "tinyint", 1212 "tinytext", 1213 "to", 1214 "trailing", 1215 "trigger", 1216 "true", 1217 "undo", 1218 "union", 1219 "unique", 1220 "unlock", 1221 "unsigned", 1222 "update", 1223 "usage", 1224 "use", 1225 "using", 1226 "utc_date", 1227 "utc_time", 1228 "utc_timestamp", 1229 "values", 1230 "varbinary", 1231 "varchar", 1232 "varcharacter", 1233 "varying", 1234 "virtual", 1235 "when", 1236 "where", 1237 "while", 1238 "window", 1239 "with", 1240 "write", 1241 "xor", 1242 "year_month", 1243 "zerofill", 1244 } 1245 1246 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1247 persisted = "STORED" if expression.args.get("persisted") else "VIRTUAL" 1248 return f"GENERATED ALWAYS AS ({self.sql(expression.this.unnest())}) {persisted}" 1249 1250 def array_sql(self, expression: exp.Array) -> str: 1251 self.unsupported("Arrays are not supported by MySQL") 1252 return self.function_fallback_sql(expression) 1253 1254 def arraycontainsall_sql(self, expression: exp.ArrayContainsAll) -> str: 1255 self.unsupported("Array operations are not supported by MySQL") 1256 return self.function_fallback_sql(expression) 1257 1258 def dpipe_sql(self, expression: exp.DPipe) -> str: 1259 return self.func("CONCAT", *expression.flatten()) 1260 1261 def extract_sql(self, expression: exp.Extract) -> str: 1262 unit = expression.name 1263 if unit and unit.lower() == "epoch": 1264 return self.func("UNIX_TIMESTAMP", expression.expression) 1265 1266 return super().extract_sql(expression) 1267 1268 def datatype_sql(self, expression: exp.DataType) -> str: 1269 if ( 1270 self.VARCHAR_REQUIRES_SIZE 1271 and expression.is_type(exp.DataType.Type.VARCHAR) 1272 and not expression.expressions 1273 ): 1274 # `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT` 1275 return "TEXT" 1276 1277 # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html 1278 result = super().datatype_sql(expression) 1279 if expression.this in self.UNSIGNED_TYPE_MAPPING: 1280 result = f"{result} UNSIGNED" 1281 1282 return result 1283 1284 def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str: 1285 return f"{self.sql(expression, 'this')} MEMBER OF({self.sql(expression, 'expression')})" 1286 1287 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1288 if expression.to.this in self.TIMESTAMP_FUNC_TYPES: 1289 return self.func("TIMESTAMP", expression.this) 1290 1291 to = self.CAST_MAPPING.get(expression.to.this) 1292 1293 if to: 1294 expression.to.set("this", to) 1295 return super().cast_sql(expression) 1296 1297 def show_sql(self, expression: exp.Show) -> str: 1298 this = f" {expression.name}" 1299 full = " FULL" if expression.args.get("full") else "" 1300 global_ = " GLOBAL" if expression.args.get("global_") else "" 1301 1302 target = self.sql(expression, "target") 1303 target = f" {target}" if target else "" 1304 if expression.name in ("COLUMNS", "INDEX"): 1305 target = f" FROM{target}" 1306 elif expression.name == "GRANTS": 1307 target = f" FOR{target}" 1308 elif expression.name in ("LINKS", "PARTITIONS"): 1309 target = f" ON{target}" if target else "" 1310 elif expression.name == "PROJECTIONS": 1311 target = f" ON TABLE{target}" if target else "" 1312 1313 db = self._prefixed_sql("FROM", expression, "db") 1314 1315 like = self._prefixed_sql("LIKE", expression, "like") 1316 where = self.sql(expression, "where") 1317 1318 types = self.expressions(expression, key="types") 1319 types = f" {types}" if types else types 1320 query = self._prefixed_sql("FOR QUERY", expression, "query") 1321 1322 if expression.name == "PROFILE": 1323 offset = self._prefixed_sql("OFFSET", expression, "offset") 1324 limit = self._prefixed_sql("LIMIT", expression, "limit") 1325 else: 1326 offset = "" 1327 limit = self._oldstyle_limit_sql(expression) 1328 1329 log = self._prefixed_sql("IN", expression, "log") 1330 position = self._prefixed_sql("FROM", expression, "position") 1331 1332 channel = self._prefixed_sql("FOR CHANNEL", expression, "channel") 1333 1334 if expression.name == "ENGINE": 1335 mutex_or_status = " MUTEX" if expression.args.get("mutex") else " STATUS" 1336 else: 1337 mutex_or_status = "" 1338 1339 for_table = self._prefixed_sql("FOR TABLE", expression, "for_table") 1340 for_group = self._prefixed_sql("FOR GROUP", expression, "for_group") 1341 for_user = self._prefixed_sql("FOR USER", expression, "for_user") 1342 for_role = self._prefixed_sql("FOR ROLE", expression, "for_role") 1343 into_outfile = self._prefixed_sql("INTO OUTFILE", expression, "into_outfile") 1344 json = " JSON" if expression.args.get("json") else "" 1345 1346 return f"SHOW{full}{global_}{this}{json}{target}{for_table}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}{for_group}{for_user}{for_role}{into_outfile}" 1347 1348 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 1349 """To avoid TO keyword in ALTER ... RENAME statements. 1350 It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks. 1351 """ 1352 return super().alterrename_sql(expression, include_to=False) 1353 1354 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 1355 dtype = self.sql(expression, "dtype") 1356 if not dtype: 1357 return super().altercolumn_sql(expression) 1358 1359 this = self.sql(expression, "this") 1360 return f"MODIFY COLUMN {this} {dtype}" 1361 1362 def _prefixed_sql(self, prefix: str, expression: exp.Expression, arg: str) -> str: 1363 sql = self.sql(expression, arg) 1364 return f" {prefix} {sql}" if sql else "" 1365 1366 def _oldstyle_limit_sql(self, expression: exp.Show) -> str: 1367 limit = self.sql(expression, "limit") 1368 offset = self.sql(expression, "offset") 1369 if limit: 1370 limit_offset = f"{offset}, {limit}" if offset else limit 1371 return f" LIMIT {limit_offset}" 1372 return "" 1373 1374 def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str: 1375 unit = expression.args.get("unit") 1376 1377 # Pick an old-enough date to avoid negative timestamp diffs 1378 start_ts = "'0000-01-01 00:00:00'" 1379 1380 # Source: https://stackoverflow.com/a/32955740 1381 timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this]) 1382 interval = exp.Interval(this=timestamp_diff, unit=unit) 1383 dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval]) 1384 1385 return self.sql(dateadd) 1386 1387 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 1388 from_tz = expression.args.get("source_tz") 1389 to_tz = expression.args.get("target_tz") 1390 dt = expression.args.get("timestamp") 1391 1392 return self.func("CONVERT_TZ", dt, from_tz, to_tz) 1393 1394 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 1395 self.unsupported("AT TIME ZONE is not supported by MySQL") 1396 return self.sql(expression.this) 1397 1398 def isascii_sql(self, expression: exp.IsAscii) -> str: 1399 return f"REGEXP_LIKE({self.sql(expression.this)}, '^[[:ascii:]]*$')" 1400 1401 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 1402 # https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html 1403 self.unsupported("MySQL does not support IGNORE NULLS.") 1404 return self.sql(expression.this) 1405 1406 @unsupported_args("this") 1407 def currentschema_sql(self, expression: exp.CurrentSchema) -> str: 1408 return self.func("SCHEMA") 1409 1410 def partition_sql(self, expression: exp.Partition) -> str: 1411 parent = expression.parent 1412 if isinstance(parent, (exp.PartitionByRangeProperty, exp.PartitionByListProperty)): 1413 return self.expressions(expression, flat=True) 1414 return super().partition_sql(expression) 1415 1416 def _partition_by_sql( 1417 self, expression: exp.PartitionByRangeProperty | exp.PartitionByListProperty, kind: str 1418 ) -> str: 1419 partitions = self.expressions(expression, key="partition_expressions", flat=True) 1420 create = self.expressions(expression, key="create_expressions", flat=True) 1421 return f"PARTITION BY {kind} ({partitions}) ({create})" 1422 1423 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 1424 return self._partition_by_sql(expression, "RANGE") 1425 1426 def partitionbylistproperty_sql(self, expression: exp.PartitionByListProperty) -> str: 1427 return self._partition_by_sql(expression, "LIST") 1428 1429 def partitionlist_sql(self, expression: exp.PartitionList) -> str: 1430 name = self.sql(expression, "this") 1431 values = self.expressions(expression, flat=True) 1432 return f"PARTITION {name} VALUES IN ({values})" 1433 1434 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 1435 name = self.sql(expression, "this") 1436 values = self.expressions(expression, flat=True) 1437 return f"PARTITION {name} VALUES LESS THAN ({values})"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
1268 def datatype_sql(self, expression: exp.DataType) -> str: 1269 if ( 1270 self.VARCHAR_REQUIRES_SIZE 1271 and expression.is_type(exp.DataType.Type.VARCHAR) 1272 and not expression.expressions 1273 ): 1274 # `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT` 1275 return "TEXT" 1276 1277 # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html 1278 result = super().datatype_sql(expression) 1279 if expression.this in self.UNSIGNED_TYPE_MAPPING: 1280 result = f"{result} UNSIGNED" 1281 1282 return result
1287 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1288 if expression.to.this in self.TIMESTAMP_FUNC_TYPES: 1289 return self.func("TIMESTAMP", expression.this) 1290 1291 to = self.CAST_MAPPING.get(expression.to.this) 1292 1293 if to: 1294 expression.to.set("this", to) 1295 return super().cast_sql(expression)
1297 def show_sql(self, expression: exp.Show) -> str: 1298 this = f" {expression.name}" 1299 full = " FULL" if expression.args.get("full") else "" 1300 global_ = " GLOBAL" if expression.args.get("global_") else "" 1301 1302 target = self.sql(expression, "target") 1303 target = f" {target}" if target else "" 1304 if expression.name in ("COLUMNS", "INDEX"): 1305 target = f" FROM{target}" 1306 elif expression.name == "GRANTS": 1307 target = f" FOR{target}" 1308 elif expression.name in ("LINKS", "PARTITIONS"): 1309 target = f" ON{target}" if target else "" 1310 elif expression.name == "PROJECTIONS": 1311 target = f" ON TABLE{target}" if target else "" 1312 1313 db = self._prefixed_sql("FROM", expression, "db") 1314 1315 like = self._prefixed_sql("LIKE", expression, "like") 1316 where = self.sql(expression, "where") 1317 1318 types = self.expressions(expression, key="types") 1319 types = f" {types}" if types else types 1320 query = self._prefixed_sql("FOR QUERY", expression, "query") 1321 1322 if expression.name == "PROFILE": 1323 offset = self._prefixed_sql("OFFSET", expression, "offset") 1324 limit = self._prefixed_sql("LIMIT", expression, "limit") 1325 else: 1326 offset = "" 1327 limit = self._oldstyle_limit_sql(expression) 1328 1329 log = self._prefixed_sql("IN", expression, "log") 1330 position = self._prefixed_sql("FROM", expression, "position") 1331 1332 channel = self._prefixed_sql("FOR CHANNEL", expression, "channel") 1333 1334 if expression.name == "ENGINE": 1335 mutex_or_status = " MUTEX" if expression.args.get("mutex") else " STATUS" 1336 else: 1337 mutex_or_status = "" 1338 1339 for_table = self._prefixed_sql("FOR TABLE", expression, "for_table") 1340 for_group = self._prefixed_sql("FOR GROUP", expression, "for_group") 1341 for_user = self._prefixed_sql("FOR USER", expression, "for_user") 1342 for_role = self._prefixed_sql("FOR ROLE", expression, "for_role") 1343 into_outfile = self._prefixed_sql("INTO OUTFILE", expression, "into_outfile") 1344 json = " JSON" if expression.args.get("json") else "" 1345 1346 return f"SHOW{full}{global_}{this}{json}{target}{for_table}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}{for_group}{for_user}{for_role}{into_outfile}"
1348 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 1349 """To avoid TO keyword in ALTER ... RENAME statements. 1350 It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks. 1351 """ 1352 return super().alterrename_sql(expression, include_to=False)
To avoid TO keyword in ALTER ... RENAME statements. It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks.
1374 def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str: 1375 unit = expression.args.get("unit") 1376 1377 # Pick an old-enough date to avoid negative timestamp diffs 1378 start_ts = "'0000-01-01 00:00:00'" 1379 1380 # Source: https://stackoverflow.com/a/32955740 1381 timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this]) 1382 interval = exp.Interval(this=timestamp_diff, unit=unit) 1383 dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval]) 1384 1385 return self.sql(dateadd)
Inherited Members
- sqlglot.generator.Generator
- Generator
- IGNORE_NULLS_IN_FUNC
- EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
- CREATE_FUNCTION_RETURN_AS
- MATCHED_BY_SOURCE
- SINGLE_STRING_INTERVAL
- RENAME_TABLE_WITH_DB
- GROUPINGS_SEP
- INDEX_ON
- INOUT_SEPARATOR
- DIRECTED_JOINS
- QUERY_HINTS
- IS_BOOL_ALLOWED
- LIMIT_IS_TOP
- RETURNING_END
- EXTRACT_ALLOWS_QUOTES
- TZ_TO_WITH_TIME_ZONE
- ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
- UNNEST_WITH_ORDINALITY
- AGGREGATE_FILTER_SUPPORTED
- SEMI_ANTI_JOIN_WITH_SIDE
- COMPUTED_COLUMN_WITH_TYPE
- SUPPORTS_TABLE_COPY
- TABLESAMPLE_REQUIRES_PARENS
- TABLESAMPLE_SIZE_IS_ROWS
- TABLESAMPLE_KEYWORDS
- TABLESAMPLE_WITH_METHOD
- TABLESAMPLE_SEED_KEYWORD
- COLLATE_IS_FUNC
- DATA_TYPE_SPECIFIERS_ALLOWED
- ENSURE_BOOLS
- CTE_RECURSIVE_KEYWORD_REQUIRED
- SUPPORTS_SINGLE_ARG_CONCAT
- SUPPORTS_TABLE_ALIAS_COLUMNS
- UNPIVOT_ALIASES_ARE_IDENTIFIERS
- INSERT_OVERWRITE
- SUPPORTS_SELECT_INTO
- SUPPORTS_UNLOGGED_TABLES
- SUPPORTS_CREATE_TABLE_LIKE
- LIKE_PROPERTY_INSIDE_SCHEMA
- MULTI_ARG_DISTINCT
- JSON_PATH_SINGLE_QUOTE_ESCAPE
- SUPPORTED_JSON_PATH_PARTS
- CAN_IMPLEMENT_ARRAY_ANY
- SUPPORTS_WINDOW_EXCLUDE
- SET_OP_MODIFIERS
- COPY_PARAMS_ARE_WRAPPED
- COPY_PARAMS_EQ_REQUIRED
- COPY_HAS_INTO_KEYWORD
- UNICODE_SUBSTITUTE
- STAR_EXCEPT
- HEX_FUNC
- WITH_PROPERTIES_PREFIX
- QUOTE_JSON_PATH
- SUPPORTS_EXPLODING_PROJECTIONS
- ARRAY_CONCAT_IS_VAR_LEN
- SUPPORTS_CONVERT_TIMEZONE
- SUPPORTS_UNIX_SECONDS
- ALTER_SET_WRAPPED
- NORMALIZE_EXTRACT_DATE_PARTS
- ARRAY_SIZE_NAME
- ALTER_SET_TYPE
- ARRAY_SIZE_DIM_REQUIRED
- SUPPORTS_BETWEEN_FLAGS
- SUPPORTS_LIKE_QUANTIFIERS
- MATCH_AGAINST_TABLE_PREFIX
- SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD
- UNSUPPORTED_TYPES
- TIME_PART_SINGULARS
- TOKEN_MAPPING
- STRUCT_DELIMITER
- PARAMETER_TOKEN
- NAMED_PLACEHOLDER_TOKEN
- EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
- WITH_SEPARATED_COMMENTS
- EXCLUDE_COMMENTS
- UNWRAPPED_INTERVAL_VALUES
- PARAMETERIZABLE_TEXT_TYPES
- EXPRESSIONS_WITHOUT_NESTED_CTES
- RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS
- SAFE_JSON_PATH_KEY_RE
- SENTINEL_LINE_BREAK
- pretty
- identify
- normalize
- pad
- unsupported_level
- max_unsupported
- leading_comma
- max_text_width
- comments
- dialect
- normalize_functions
- unsupported_messages
- generate
- preprocess
- unsupported
- sep
- seg
- sanitize_comment
- maybe_comment
- wrap
- no_identify
- normalize_func
- indent
- sql
- uncache_sql
- cache_sql
- characterset_sql
- column_parts
- column_sql
- pseudocolumn_sql
- columnposition_sql
- columndef_sql
- columnconstraint_sql
- autoincrementcolumnconstraint_sql
- compresscolumnconstraint_sql
- generatedasidentitycolumnconstraint_sql
- generatedasrowcolumnconstraint_sql
- periodforsystemtimeconstraint_sql
- notnullcolumnconstraint_sql
- primarykeycolumnconstraint_sql
- uniquecolumnconstraint_sql
- inoutcolumnconstraint_sql
- createable_sql
- create_sql
- sequenceproperties_sql
- clone_sql
- describe_sql
- heredoc_sql
- prepend_ctes
- with_sql
- cte_sql
- tablealias_sql
- bitstring_sql
- hexstring_sql
- bytestring_sql
- unicodestring_sql
- rawstring_sql
- datatypeparam_sql
- directory_sql
- delete_sql
- drop_sql
- set_operation
- set_operations
- fetch_sql
- limitoptions_sql
- filter_sql
- hint_sql
- indexparameters_sql
- index_sql
- identifier_sql
- hex_sql
- lowerhex_sql
- inputoutputformat_sql
- national_sql
- properties_sql
- root_properties
- properties
- with_properties
- locate_properties
- property_name
- property_sql
- likeproperty_sql
- fallbackproperty_sql
- journalproperty_sql
- freespaceproperty_sql
- checksumproperty_sql
- mergeblockratioproperty_sql
- datablocksizeproperty_sql
- blockcompressionproperty_sql
- isolatedloadingproperty_sql
- partitionboundspec_sql
- partitionedofproperty_sql
- lockingproperty_sql
- withdataproperty_sql
- withsystemversioningproperty_sql
- insert_sql
- introducer_sql
- kill_sql
- pseudotype_sql
- objectidentifier_sql
- onconflict_sql
- returning_sql
- rowformatdelimitedproperty_sql
- withtablehint_sql
- indextablehint_sql
- historicaldata_sql
- table_parts
- table_sql
- tablefromrows_sql
- tablesample_sql
- pivot_sql
- version_sql
- tuple_sql
- update_sql
- values_sql
- var_sql
- into_sql
- from_sql
- groupingsets_sql
- rollup_sql
- rollupindex_sql
- rollupproperty_sql
- cube_sql
- group_sql
- having_sql
- connect_sql
- prior_sql
- join_sql
- lambda_sql
- lateral_op
- lateral_sql
- limit_sql
- offset_sql
- setitem_sql
- set_sql
- queryband_sql
- pragma_sql
- lock_sql
- literal_sql
- escape_str
- loaddata_sql
- null_sql
- boolean_sql
- booland_sql
- boolor_sql
- order_sql
- withfill_sql
- cluster_sql
- distribute_sql
- sort_sql
- ordered_sql
- matchrecognizemeasure_sql
- matchrecognize_sql
- query_modifiers
- options_modifier
- for_modifiers
- queryoption_sql
- offset_limit_modifiers
- after_limit_modifiers
- select_sql
- schema_sql
- schema_columns_sql
- star_sql
- parameter_sql
- sessionparameter_sql
- placeholder_sql
- subquery_sql
- qualify_sql
- unnest_sql
- prewhere_sql
- where_sql
- window_sql
- partition_by_sql
- windowspec_sql
- withingroup_sql
- between_sql
- bracket_offset_expressions
- bracket_sql
- all_sql
- any_sql
- exists_sql
- case_sql
- constraint_sql
- nextvaluefor_sql
- trim_sql
- convert_concat_args
- concat_sql
- concatws_sql
- check_sql
- foreignkey_sql
- primarykey_sql
- if_sql
- matchagainst_sql
- jsonkeyvalue_sql
- jsonpath_sql
- json_path_part
- formatjson_sql
- formatphrase_sql
- jsonobject_sql
- jsonobjectagg_sql
- jsonarray_sql
- jsonarrayagg_sql
- jsoncolumndef_sql
- jsonschema_sql
- jsontable_sql
- openjsoncolumndef_sql
- openjson_sql
- in_sql
- in_unnest_op
- interval_sql
- return_sql
- reference_sql
- anonymous_sql
- paren_sql
- neg_sql
- not_sql
- alias_sql
- pivotalias_sql
- aliases_sql
- atindex_sql
- fromtimezone_sql
- add_sql
- and_sql
- or_sql
- xor_sql
- connector_sql
- bitwiseand_sql
- bitwiseleftshift_sql
- bitwisenot_sql
- bitwiseor_sql
- bitwiserightshift_sql
- bitwisexor_sql
- strtotime_sql
- currentdate_sql
- collate_sql
- command_sql
- comment_sql
- mergetreettlaction_sql
- mergetreettl_sql
- transaction_sql
- commit_sql
- rollback_sql
- alterindex_sql
- alterdiststyle_sql
- altersortkey_sql
- renamecolumn_sql
- alterset_sql
- alter_sql
- altersession_sql
- add_column_sql
- droppartition_sql
- addconstraint_sql
- addpartition_sql
- distinct_sql
- respectnulls_sql
- havingmax_sql
- intdiv_sql
- div_sql
- safedivide_sql
- overlaps_sql
- distance_sql
- dot_sql
- eq_sql
- propertyeq_sql
- escape_sql
- glob_sql
- gt_sql
- gte_sql
- is_sql
- like_sql
- ilike_sql
- match_sql
- similarto_sql
- lt_sql
- lte_sql
- mod_sql
- mul_sql
- neq_sql
- nullsafeeq_sql
- nullsafeneq_sql
- sub_sql
- trycast_sql
- jsoncast_sql
- try_sql
- log_sql
- use_sql
- binary
- ceil_floor
- function_fallback_sql
- func
- format_args
- too_wide
- format_time
- expressions
- op_expressions
- naked_property
- tag_sql
- token_sql
- userdefinedfunction_sql
- joinhint_sql
- kwarg_sql
- when_sql
- whens_sql
- merge_sql
- tochar_sql
- tonumber_sql
- dictproperty_sql
- dictrange_sql
- dictsubproperty_sql
- duplicatekeyproperty_sql
- uniquekeyproperty_sql
- distributedbyproperty_sql
- oncluster_sql
- clusteredbyproperty_sql
- anyvalue_sql
- querytransform_sql
- indexconstraintoption_sql
- checkcolumnconstraint_sql
- indexcolumnconstraint_sql
- nvl2_sql
- comprehension_sql
- columnprefix_sql
- opclass_sql
- predict_sql
- generateembedding_sql
- mltranslate_sql
- mlforecast_sql
- featuresattime_sql
- vectorsearch_sql
- forin_sql
- refresh_sql
- toarray_sql
- tsordstotime_sql
- tsordstotimestamp_sql
- tsordstodatetime_sql
- tsordstodate_sql
- unixdate_sql
- lastday_sql
- dateadd_sql
- arrayany_sql
- struct_sql
- truncatetable_sql
- convert_sql
- copyparameter_sql
- credentials_sql
- copy_sql
- semicolon_sql
- datadeletionproperty_sql
- maskingpolicycolumnconstraint_sql
- gapfill_sql
- scope_resolution
- scoperesolution_sql
- parsejson_sql
- rand_sql
- changes_sql
- pad_sql
- summarize_sql
- explodinggenerateseries_sql
- json_sql
- jsonvalue_sql
- conditionalinsert_sql
- multitableinserts_sql
- oncondition_sql
- jsonextractquote_sql
- jsonexists_sql
- arrayagg_sql
- slice_sql
- apply_sql
- grant_sql
- revoke_sql
- grantprivilege_sql
- grantprincipal_sql
- columns_sql
- overlay_sql
- todouble_sql
- string_sql
- median_sql
- overflowtruncatebehavior_sql
- unixseconds_sql
- arraysize_sql
- attach_sql
- detach_sql
- attachoption_sql
- watermarkcolumnconstraint_sql
- encodeproperty_sql
- includeproperty_sql
- xmlelement_sql
- xmlkeyvalueoption_sql
- partitionbyrangepropertydynamic_sql
- unpivotcolumns_sql
- analyzesample_sql
- analyzestatistics_sql
- analyzehistogram_sql
- analyzedelete_sql
- analyzelistchainedrows_sql
- analyzevalidate_sql
- analyze_sql
- xmltable_sql
- xmlnamespace_sql
- export_sql
- declare_sql
- declareitem_sql
- recursivewithsearch_sql
- parameterizedagg_sql
- anonymousaggfunc_sql
- combinedaggfunc_sql
- combinedparameterizedagg_sql
- install_sql
- get_put_sql
- translatecharacters_sql
- decodecase_sql
- semanticview_sql
- getextract_sql
- datefromunixdate_sql
- space_sql
- buildproperty_sql
- refreshtriggerproperty_sql
- modelattribute_sql
- directorystage_sql
- uuid_sql
- initcap_sql
- localtime_sql
- localtimestamp_sql
- weekstart_sql
- chr_sql