sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6 7from sqlglot import exp 8from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 9from sqlglot.helper import apply_index_offset, csv 10from sqlglot.time import format_time 11from sqlglot.tokens import TokenType 12 13logger = logging.getLogger("sqlglot") 14 15BACKSLASH_RE = re.compile(r"\\(?!b|f|n|r|t|0)") 16 17 18class Generator: 19 """ 20 Generator interprets the given syntax tree and produces a SQL string as an output. 21 22 Args: 23 time_mapping (dict): the dictionary of custom time mappings in which the key 24 represents a python time format and the output the target time format 25 time_trie (trie): a trie of the time_mapping keys 26 pretty (bool): if set to True the returned string will be formatted. Default: False. 27 quote_start (str): specifies which starting character to use to delimit quotes. Default: '. 28 quote_end (str): specifies which ending character to use to delimit quotes. Default: '. 29 identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ". 30 identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ". 31 identify (bool): if set to True all identifiers will be delimited by the corresponding 32 character. 33 normalize (bool): if set to True all identifiers will lower cased 34 string_escape (str): specifies a string escape character. Default: '. 35 identifier_escape (str): specifies an identifier escape character. Default: ". 36 pad (int): determines padding in a formatted string. Default: 2. 37 indent (int): determines the size of indentation in a formatted string. Default: 4. 38 unnest_column_only (bool): if true unnest table aliases are considered only as column aliases 39 normalize_functions (str): normalize function names, "upper", "lower", or None 40 Default: "upper" 41 alias_post_tablesample (bool): if the table alias comes after tablesample 42 Default: False 43 unsupported_level (ErrorLevel): determines the generator's behavior when it encounters 44 unsupported expressions. Default ErrorLevel.WARN. 45 null_ordering (str): Indicates the default null ordering method to use if not explicitly set. 46 Options are "nulls_are_small", "nulls_are_large", "nulls_are_last". 47 Default: "nulls_are_small" 48 max_unsupported (int): Maximum number of unsupported messages to include in a raised UnsupportedError. 49 This is only relevant if unsupported_level is ErrorLevel.RAISE. 50 Default: 3 51 leading_comma (bool): if the the comma is leading or trailing in select statements 52 Default: False 53 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 54 The default is on the smaller end because the length only represents a segment and not the true 55 line length. 56 Default: 80 57 comments: Whether or not to preserve comments in the output SQL code. 58 Default: True 59 """ 60 61 TRANSFORMS = { 62 exp.DateAdd: lambda self, e: f"DATE_ADD({self.format_args(e.this, e.expression, e.args.get('unit'))})", 63 exp.DateDiff: lambda self, e: f"DATEDIFF({self.format_args(e.this, e.expression)})", 64 exp.TsOrDsAdd: lambda self, e: f"TS_OR_DS_ADD({self.format_args(e.this, e.expression, e.args.get('unit'))})", 65 exp.VarMap: lambda self, e: f"MAP({self.format_args(e.args['keys'], e.args['values'])})", 66 exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args['default'] else ''}CHARACTER SET={self.sql(e, 'this')}", 67 exp.LanguageProperty: lambda self, e: self.naked_property(e), 68 exp.LocationProperty: lambda self, e: self.naked_property(e), 69 exp.ReturnsProperty: lambda self, e: self.naked_property(e), 70 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 71 exp.VolatilityProperty: lambda self, e: e.name, 72 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 73 exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG", 74 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 75 } 76 77 # Whether 'CREATE ... TRANSIENT ... TABLE' is allowed 78 CREATE_TRANSIENT = False 79 80 # Whether or not null ordering is supported in order by 81 NULL_ORDERING_SUPPORTED = True 82 83 # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 84 LOCKING_READS_SUPPORTED = False 85 86 # Always do union distinct or union all 87 EXPLICIT_UNION = False 88 89 # Wrap derived values in parens, usually standard but spark doesn't support it 90 WRAP_DERIVED_VALUES = True 91 92 # Whether or not create function uses an AS before the def. 93 CREATE_FUNCTION_AS = True 94 95 TYPE_MAPPING = { 96 exp.DataType.Type.NCHAR: "CHAR", 97 exp.DataType.Type.NVARCHAR: "VARCHAR", 98 exp.DataType.Type.MEDIUMTEXT: "TEXT", 99 exp.DataType.Type.LONGTEXT: "TEXT", 100 exp.DataType.Type.MEDIUMBLOB: "BLOB", 101 exp.DataType.Type.LONGBLOB: "BLOB", 102 } 103 104 STAR_MAPPING = { 105 "except": "EXCEPT", 106 "replace": "REPLACE", 107 } 108 109 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 110 111 STRUCT_DELIMITER = ("<", ">") 112 113 PROPERTIES_LOCATION = { 114 exp.AfterJournalProperty: exp.Properties.Location.PRE_SCHEMA, 115 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 116 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 117 exp.BlockCompressionProperty: exp.Properties.Location.PRE_SCHEMA, 118 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 119 exp.ChecksumProperty: exp.Properties.Location.PRE_SCHEMA, 120 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 121 exp.DataBlocksizeProperty: exp.Properties.Location.PRE_SCHEMA, 122 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 123 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 124 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 125 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 126 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 127 exp.FallbackProperty: exp.Properties.Location.PRE_SCHEMA, 128 exp.FileFormatProperty: exp.Properties.Location.POST_SCHEMA_WITH, 129 exp.FreespaceProperty: exp.Properties.Location.PRE_SCHEMA, 130 exp.IsolatedLoadingProperty: exp.Properties.Location.PRE_SCHEMA, 131 exp.JournalProperty: exp.Properties.Location.PRE_SCHEMA, 132 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 133 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 134 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 135 exp.LogProperty: exp.Properties.Location.PRE_SCHEMA, 136 exp.MergeBlockRatioProperty: exp.Properties.Location.PRE_SCHEMA, 137 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA_WITH, 138 exp.Property: exp.Properties.Location.POST_SCHEMA_WITH, 139 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 140 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 141 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 142 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 143 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA_ROOT, 144 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 145 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 146 exp.TableFormatProperty: exp.Properties.Location.POST_SCHEMA_WITH, 147 exp.VolatilityProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 148 exp.WithJournalTableProperty: exp.Properties.Location.PRE_SCHEMA, 149 } 150 151 WITH_SEPARATED_COMMENTS = (exp.Select, exp.From, exp.Where, exp.Binary) 152 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 153 154 __slots__ = ( 155 "time_mapping", 156 "time_trie", 157 "pretty", 158 "quote_start", 159 "quote_end", 160 "identifier_start", 161 "identifier_end", 162 "identify", 163 "normalize", 164 "string_escape", 165 "identifier_escape", 166 "pad", 167 "index_offset", 168 "unnest_column_only", 169 "alias_post_tablesample", 170 "normalize_functions", 171 "unsupported_level", 172 "unsupported_messages", 173 "null_ordering", 174 "max_unsupported", 175 "_indent", 176 "_replace_backslash", 177 "_escaped_quote_end", 178 "_escaped_identifier_end", 179 "_leading_comma", 180 "_max_text_width", 181 "_comments", 182 ) 183 184 def __init__( 185 self, 186 time_mapping=None, 187 time_trie=None, 188 pretty=None, 189 quote_start=None, 190 quote_end=None, 191 identifier_start=None, 192 identifier_end=None, 193 identify=False, 194 normalize=False, 195 string_escape=None, 196 identifier_escape=None, 197 pad=2, 198 indent=2, 199 index_offset=0, 200 unnest_column_only=False, 201 alias_post_tablesample=False, 202 normalize_functions="upper", 203 unsupported_level=ErrorLevel.WARN, 204 null_ordering=None, 205 max_unsupported=3, 206 leading_comma=False, 207 max_text_width=80, 208 comments=True, 209 ): 210 import sqlglot 211 212 self.time_mapping = time_mapping or {} 213 self.time_trie = time_trie 214 self.pretty = pretty if pretty is not None else sqlglot.pretty 215 self.quote_start = quote_start or "'" 216 self.quote_end = quote_end or "'" 217 self.identifier_start = identifier_start or '"' 218 self.identifier_end = identifier_end or '"' 219 self.identify = identify 220 self.normalize = normalize 221 self.string_escape = string_escape or "'" 222 self.identifier_escape = identifier_escape or '"' 223 self.pad = pad 224 self.index_offset = index_offset 225 self.unnest_column_only = unnest_column_only 226 self.alias_post_tablesample = alias_post_tablesample 227 self.normalize_functions = normalize_functions 228 self.unsupported_level = unsupported_level 229 self.unsupported_messages = [] 230 self.max_unsupported = max_unsupported 231 self.null_ordering = null_ordering 232 self._indent = indent 233 self._replace_backslash = self.string_escape == "\\" 234 self._escaped_quote_end = self.string_escape + self.quote_end 235 self._escaped_identifier_end = self.identifier_escape + self.identifier_end 236 self._leading_comma = leading_comma 237 self._max_text_width = max_text_width 238 self._comments = comments 239 240 def generate(self, expression: t.Optional[exp.Expression]) -> str: 241 """ 242 Generates a SQL string by interpreting the given syntax tree. 243 244 Args 245 expression: the syntax tree. 246 247 Returns 248 the SQL string. 249 """ 250 self.unsupported_messages = [] 251 sql = self.sql(expression).strip() 252 253 if self.unsupported_level == ErrorLevel.IGNORE: 254 return sql 255 256 if self.unsupported_level == ErrorLevel.WARN: 257 for msg in self.unsupported_messages: 258 logger.warning(msg) 259 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 260 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 261 262 if self.pretty: 263 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 264 return sql 265 266 def unsupported(self, message: str) -> None: 267 if self.unsupported_level == ErrorLevel.IMMEDIATE: 268 raise UnsupportedError(message) 269 self.unsupported_messages.append(message) 270 271 def sep(self, sep: str = " ") -> str: 272 return f"{sep.strip()}\n" if self.pretty else sep 273 274 def seg(self, sql: str, sep: str = " ") -> str: 275 return f"{self.sep(sep)}{sql}" 276 277 def pad_comment(self, comment: str) -> str: 278 comment = " " + comment if comment[0].strip() else comment 279 comment = comment + " " if comment[-1].strip() else comment 280 return comment 281 282 def maybe_comment(self, sql: str, expression: exp.Expression) -> str: 283 comments = expression.comments if self._comments else None 284 285 if not comments: 286 return sql 287 288 sep = "\n" if self.pretty else " " 289 comments_sql = sep.join( 290 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 291 ) 292 293 if not comments_sql: 294 return sql 295 296 if isinstance(expression, self.WITH_SEPARATED_COMMENTS): 297 return f"{comments_sql}{self.sep()}{sql}" 298 299 return f"{sql} {comments_sql}" 300 301 def wrap(self, expression: exp.Expression | str) -> str: 302 this_sql = self.indent( 303 self.sql(expression) 304 if isinstance(expression, (exp.Select, exp.Union)) 305 else self.sql(expression, "this"), 306 level=1, 307 pad=0, 308 ) 309 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 310 311 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 312 original = self.identify 313 self.identify = False 314 result = func(*args, **kwargs) 315 self.identify = original 316 return result 317 318 def normalize_func(self, name: str) -> str: 319 if self.normalize_functions == "upper": 320 return name.upper() 321 if self.normalize_functions == "lower": 322 return name.lower() 323 return name 324 325 def indent( 326 self, 327 sql: str, 328 level: int = 0, 329 pad: t.Optional[int] = None, 330 skip_first: bool = False, 331 skip_last: bool = False, 332 ) -> str: 333 if not self.pretty: 334 return sql 335 336 pad = self.pad if pad is None else pad 337 lines = sql.split("\n") 338 339 return "\n".join( 340 line 341 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 342 else f"{' ' * (level * self._indent + pad)}{line}" 343 for i, line in enumerate(lines) 344 ) 345 346 def sql( 347 self, 348 expression: t.Optional[str | exp.Expression], 349 key: t.Optional[str] = None, 350 comment: bool = True, 351 ) -> str: 352 if not expression: 353 return "" 354 355 if isinstance(expression, str): 356 return expression 357 358 if key: 359 return self.sql(expression.args.get(key)) 360 361 transform = self.TRANSFORMS.get(expression.__class__) 362 363 if callable(transform): 364 sql = transform(self, expression) 365 elif transform: 366 sql = transform 367 elif isinstance(expression, exp.Expression): 368 exp_handler_name = f"{expression.key}_sql" 369 370 if hasattr(self, exp_handler_name): 371 sql = getattr(self, exp_handler_name)(expression) 372 elif isinstance(expression, exp.Func): 373 sql = self.function_fallback_sql(expression) 374 elif isinstance(expression, exp.Property): 375 sql = self.property_sql(expression) 376 else: 377 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 378 else: 379 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 380 381 return self.maybe_comment(sql, expression) if self._comments and comment else sql 382 383 def uncache_sql(self, expression: exp.Uncache) -> str: 384 table = self.sql(expression, "this") 385 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 386 return f"UNCACHE TABLE{exists_sql} {table}" 387 388 def cache_sql(self, expression: exp.Cache) -> str: 389 lazy = " LAZY" if expression.args.get("lazy") else "" 390 table = self.sql(expression, "this") 391 options = expression.args.get("options") 392 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 393 sql = self.sql(expression, "expression") 394 sql = f" AS{self.sep()}{sql}" if sql else "" 395 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 396 return self.prepend_ctes(expression, sql) 397 398 def characterset_sql(self, expression: exp.CharacterSet) -> str: 399 if isinstance(expression.parent, exp.Cast): 400 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 401 default = "DEFAULT " if expression.args.get("default") else "" 402 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 403 404 def column_sql(self, expression: exp.Column) -> str: 405 return ".".join( 406 part 407 for part in [ 408 self.sql(expression, "db"), 409 self.sql(expression, "table"), 410 self.sql(expression, "this"), 411 ] 412 if part 413 ) 414 415 def columndef_sql(self, expression: exp.ColumnDef) -> str: 416 column = self.sql(expression, "this") 417 kind = self.sql(expression, "kind") 418 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 419 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 420 kind = f" {kind}" if kind else "" 421 constraints = f" {constraints}" if constraints else "" 422 423 return f"{exists}{column}{kind}{constraints}" 424 425 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 426 this = self.sql(expression, "this") 427 kind_sql = self.sql(expression, "kind") 428 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 429 430 def autoincrementcolumnconstraint_sql(self, _) -> str: 431 return self.token_sql(TokenType.AUTO_INCREMENT) 432 433 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 434 this = self.sql(expression, "this") 435 return f"CHECK ({this})" 436 437 def commentcolumnconstraint_sql(self, expression: exp.CommentColumnConstraint) -> str: 438 comment = self.sql(expression, "this") 439 return f"COMMENT {comment}" 440 441 def collatecolumnconstraint_sql(self, expression: exp.CollateColumnConstraint) -> str: 442 collate = self.sql(expression, "this") 443 return f"COLLATE {collate}" 444 445 def encodecolumnconstraint_sql(self, expression: exp.EncodeColumnConstraint) -> str: 446 encode = self.sql(expression, "this") 447 return f"ENCODE {encode}" 448 449 def defaultcolumnconstraint_sql(self, expression: exp.DefaultColumnConstraint) -> str: 450 default = self.sql(expression, "this") 451 return f"DEFAULT {default}" 452 453 def generatedasidentitycolumnconstraint_sql( 454 self, expression: exp.GeneratedAsIdentityColumnConstraint 455 ) -> str: 456 this = "" 457 if expression.this is not None: 458 this = " ALWAYS " if expression.this else " BY DEFAULT " 459 start = expression.args.get("start") 460 start = f"START WITH {start}" if start else "" 461 increment = expression.args.get("increment") 462 increment = f"INCREMENT BY {increment}" if increment else "" 463 sequence_opts = "" 464 if start or increment: 465 sequence_opts = f"{start} {increment}" 466 sequence_opts = f" ({sequence_opts.strip()})" 467 return f"GENERATED{this}AS IDENTITY{sequence_opts}" 468 469 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 470 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 471 472 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 473 desc = expression.args.get("desc") 474 if desc is not None: 475 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 476 return f"PRIMARY KEY" 477 478 def uniquecolumnconstraint_sql(self, _) -> str: 479 return "UNIQUE" 480 481 def create_sql(self, expression: exp.Create) -> str: 482 kind = self.sql(expression, "kind").upper() 483 properties = expression.args.get("properties") 484 properties_exp = expression.copy() 485 properties_locs = self.locate_properties(properties) if properties else {} 486 if properties_locs.get(exp.Properties.Location.POST_SCHEMA_ROOT) or properties_locs.get( 487 exp.Properties.Location.POST_SCHEMA_WITH 488 ): 489 properties_exp.set( 490 "properties", 491 exp.Properties( 492 expressions=[ 493 *properties_locs[exp.Properties.Location.POST_SCHEMA_ROOT], 494 *properties_locs[exp.Properties.Location.POST_SCHEMA_WITH], 495 ] 496 ), 497 ) 498 if kind == "TABLE" and properties_locs.get(exp.Properties.Location.PRE_SCHEMA): 499 this_name = self.sql(expression.this, "this") 500 this_properties = self.properties( 501 exp.Properties(expressions=properties_locs[exp.Properties.Location.PRE_SCHEMA]), 502 wrapped=False, 503 ) 504 this_schema = f"({self.expressions(expression.this)})" 505 this = f"{this_name}, {this_properties} {this_schema}" 506 properties_sql = "" 507 else: 508 this = self.sql(expression, "this") 509 properties_sql = self.sql(properties_exp, "properties") 510 begin = " BEGIN" if expression.args.get("begin") else "" 511 expression_sql = self.sql(expression, "expression") 512 if expression_sql: 513 expression_sql = f"{begin}{self.sep()}{expression_sql}" 514 515 if self.CREATE_FUNCTION_AS or kind != "FUNCTION": 516 expression_sql = f" AS{expression_sql}" 517 518 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 519 transient = ( 520 " TRANSIENT" if self.CREATE_TRANSIENT and expression.args.get("transient") else "" 521 ) 522 external = " EXTERNAL" if expression.args.get("external") else "" 523 replace = " OR REPLACE" if expression.args.get("replace") else "" 524 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 525 unique = " UNIQUE" if expression.args.get("unique") else "" 526 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 527 set_ = " SET" if expression.args.get("set") else "" 528 multiset = " MULTISET" if expression.args.get("multiset") else "" 529 global_temporary = " GLOBAL TEMPORARY" if expression.args.get("global_temporary") else "" 530 volatile = " VOLATILE" if expression.args.get("volatile") else "" 531 data = expression.args.get("data") 532 if data is None: 533 data = "" 534 elif data: 535 data = " WITH DATA" 536 else: 537 data = " WITH NO DATA" 538 statistics = expression.args.get("statistics") 539 if statistics is None: 540 statistics = "" 541 elif statistics: 542 statistics = " AND STATISTICS" 543 else: 544 statistics = " AND NO STATISTICS" 545 no_primary_index = " NO PRIMARY INDEX" if expression.args.get("no_primary_index") else "" 546 547 indexes = expression.args.get("indexes") 548 index_sql = "" 549 if indexes: 550 indexes_sql = [] 551 for index in indexes: 552 ind_unique = " UNIQUE" if index.args.get("unique") else "" 553 ind_primary = " PRIMARY" if index.args.get("primary") else "" 554 ind_amp = " AMP" if index.args.get("amp") else "" 555 ind_name = f" {index.name}" if index.name else "" 556 ind_columns = ( 557 f' ({self.expressions(index, key="columns", flat=True)})' 558 if index.args.get("columns") 559 else "" 560 ) 561 if index.args.get("primary") and properties_locs.get( 562 exp.Properties.Location.POST_INDEX 563 ): 564 postindex_props_sql = self.properties( 565 exp.Properties( 566 expressions=properties_locs[exp.Properties.Location.POST_INDEX] 567 ), 568 wrapped=False, 569 ) 570 ind_columns = f"{ind_columns} {postindex_props_sql}" 571 572 indexes_sql.append( 573 f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}" 574 ) 575 index_sql = "".join(indexes_sql) 576 577 postcreate_props_sql = "" 578 if properties_locs.get(exp.Properties.Location.POST_CREATE): 579 postcreate_props_sql = self.properties( 580 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 581 sep=" ", 582 prefix=" ", 583 wrapped=False, 584 ) 585 586 modifiers = "".join( 587 ( 588 replace, 589 temporary, 590 transient, 591 external, 592 unique, 593 materialized, 594 set_, 595 multiset, 596 global_temporary, 597 volatile, 598 postcreate_props_sql, 599 ) 600 ) 601 no_schema_binding = ( 602 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 603 ) 604 605 post_expression_modifiers = "".join((data, statistics, no_primary_index)) 606 607 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{post_expression_modifiers}{index_sql}{no_schema_binding}" 608 return self.prepend_ctes(expression, expression_sql) 609 610 def describe_sql(self, expression: exp.Describe) -> str: 611 return f"DESCRIBE {self.sql(expression, 'this')}" 612 613 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 614 with_ = self.sql(expression, "with") 615 if with_: 616 sql = f"{with_}{self.sep()}{sql}" 617 return sql 618 619 def with_sql(self, expression: exp.With) -> str: 620 sql = self.expressions(expression, flat=True) 621 recursive = "RECURSIVE " if expression.args.get("recursive") else "" 622 623 return f"WITH {recursive}{sql}" 624 625 def cte_sql(self, expression: exp.CTE) -> str: 626 alias = self.sql(expression, "alias") 627 return f"{alias} AS {self.wrap(expression)}" 628 629 def tablealias_sql(self, expression: exp.TableAlias) -> str: 630 alias = self.sql(expression, "this") 631 columns = self.expressions(expression, key="columns", flat=True) 632 columns = f"({columns})" if columns else "" 633 return f"{alias}{columns}" 634 635 def bitstring_sql(self, expression: exp.BitString) -> str: 636 return self.sql(expression, "this") 637 638 def hexstring_sql(self, expression: exp.HexString) -> str: 639 return self.sql(expression, "this") 640 641 def datatype_sql(self, expression: exp.DataType) -> str: 642 type_value = expression.this 643 type_sql = self.TYPE_MAPPING.get(type_value, type_value.value) 644 nested = "" 645 interior = self.expressions(expression, flat=True) 646 values = "" 647 if interior: 648 if expression.args.get("nested"): 649 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 650 if expression.args.get("values") is not None: 651 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 652 values = ( 653 f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}" 654 ) 655 else: 656 nested = f"({interior})" 657 658 return f"{type_sql}{nested}{values}" 659 660 def directory_sql(self, expression: exp.Directory) -> str: 661 local = "LOCAL " if expression.args.get("local") else "" 662 row_format = self.sql(expression, "row_format") 663 row_format = f" {row_format}" if row_format else "" 664 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 665 666 def delete_sql(self, expression: exp.Delete) -> str: 667 this = self.sql(expression, "this") 668 this = f" FROM {this}" if this else "" 669 using_sql = ( 670 f" USING {self.expressions(expression, 'using', sep=', USING ')}" 671 if expression.args.get("using") 672 else "" 673 ) 674 where_sql = self.sql(expression, "where") 675 sql = f"DELETE{this}{using_sql}{where_sql}" 676 return self.prepend_ctes(expression, sql) 677 678 def drop_sql(self, expression: exp.Drop) -> str: 679 this = self.sql(expression, "this") 680 kind = expression.args["kind"] 681 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 682 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 683 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 684 cascade = " CASCADE" if expression.args.get("cascade") else "" 685 return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}" 686 687 def except_sql(self, expression: exp.Except) -> str: 688 return self.prepend_ctes( 689 expression, 690 self.set_operation(expression, self.except_op(expression)), 691 ) 692 693 def except_op(self, expression: exp.Except) -> str: 694 return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}" 695 696 def fetch_sql(self, expression: exp.Fetch) -> str: 697 direction = expression.args.get("direction") 698 direction = f" {direction.upper()}" if direction else "" 699 count = expression.args.get("count") 700 count = f" {count}" if count else "" 701 return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY" 702 703 def filter_sql(self, expression: exp.Filter) -> str: 704 this = self.sql(expression, "this") 705 where = self.sql(expression, "expression")[1:] # where has a leading space 706 return f"{this} FILTER({where})" 707 708 def hint_sql(self, expression: exp.Hint) -> str: 709 if self.sql(expression, "this"): 710 self.unsupported("Hints are not supported") 711 return "" 712 713 def index_sql(self, expression: exp.Index) -> str: 714 this = self.sql(expression, "this") 715 table = self.sql(expression, "table") 716 columns = self.sql(expression, "columns") 717 return f"{this} ON {table} {columns}" 718 719 def identifier_sql(self, expression: exp.Identifier) -> str: 720 text = expression.name 721 text = text.lower() if self.normalize else text 722 text = text.replace(self.identifier_end, self._escaped_identifier_end) 723 if expression.args.get("quoted") or self.identify: 724 text = f"{self.identifier_start}{text}{self.identifier_end}" 725 return text 726 727 def national_sql(self, expression: exp.National) -> str: 728 return f"N{self.sql(expression, 'this')}" 729 730 def partition_sql(self, expression: exp.Partition) -> str: 731 return f"PARTITION({self.expressions(expression)})" 732 733 def properties_sql(self, expression: exp.Properties) -> str: 734 root_properties = [] 735 with_properties = [] 736 737 for p in expression.expressions: 738 p_loc = self.PROPERTIES_LOCATION[p.__class__] 739 if p_loc == exp.Properties.Location.POST_SCHEMA_WITH: 740 with_properties.append(p) 741 elif p_loc == exp.Properties.Location.POST_SCHEMA_ROOT: 742 root_properties.append(p) 743 744 return self.root_properties( 745 exp.Properties(expressions=root_properties) 746 ) + self.with_properties(exp.Properties(expressions=with_properties)) 747 748 def root_properties(self, properties: exp.Properties) -> str: 749 if properties.expressions: 750 return self.sep() + self.expressions(properties, indent=False, sep=" ") 751 return "" 752 753 def properties( 754 self, 755 properties: exp.Properties, 756 prefix: str = "", 757 sep: str = ", ", 758 suffix: str = "", 759 wrapped: bool = True, 760 ) -> str: 761 if properties.expressions: 762 expressions = self.expressions(properties, sep=sep, indent=False) 763 expressions = self.wrap(expressions) if wrapped else expressions 764 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}" 765 return "" 766 767 def with_properties(self, properties: exp.Properties) -> str: 768 return self.properties(properties, prefix=self.seg("WITH")) 769 770 def locate_properties( 771 self, properties: exp.Properties 772 ) -> t.Dict[exp.Properties.Location, list[exp.Property]]: 773 properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = { 774 key: [] for key in exp.Properties.Location 775 } 776 777 for p in properties.expressions: 778 p_loc = self.PROPERTIES_LOCATION[p.__class__] 779 if p_loc == exp.Properties.Location.PRE_SCHEMA: 780 properties_locs[exp.Properties.Location.PRE_SCHEMA].append(p) 781 elif p_loc == exp.Properties.Location.POST_INDEX: 782 properties_locs[exp.Properties.Location.POST_INDEX].append(p) 783 elif p_loc == exp.Properties.Location.POST_SCHEMA_ROOT: 784 properties_locs[exp.Properties.Location.POST_SCHEMA_ROOT].append(p) 785 elif p_loc == exp.Properties.Location.POST_SCHEMA_WITH: 786 properties_locs[exp.Properties.Location.POST_SCHEMA_WITH].append(p) 787 elif p_loc == exp.Properties.Location.POST_CREATE: 788 properties_locs[exp.Properties.Location.POST_CREATE].append(p) 789 elif p_loc == exp.Properties.Location.UNSUPPORTED: 790 self.unsupported(f"Unsupported property {p.key}") 791 792 return properties_locs 793 794 def property_sql(self, expression: exp.Property) -> str: 795 property_cls = expression.__class__ 796 if property_cls == exp.Property: 797 return f"{expression.name}={self.sql(expression, 'value')}" 798 799 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 800 if not property_name: 801 self.unsupported(f"Unsupported property {expression.key}") 802 803 return f"{property_name}={self.sql(expression, 'this')}" 804 805 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 806 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 807 options = f" {options}" if options else "" 808 return f"LIKE {self.sql(expression, 'this')}{options}" 809 810 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 811 no = "NO " if expression.args.get("no") else "" 812 protection = " PROTECTION" if expression.args.get("protection") else "" 813 return f"{no}FALLBACK{protection}" 814 815 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 816 no = "NO " if expression.args.get("no") else "" 817 dual = "DUAL " if expression.args.get("dual") else "" 818 before = "BEFORE " if expression.args.get("before") else "" 819 return f"{no}{dual}{before}JOURNAL" 820 821 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 822 freespace = self.sql(expression, "this") 823 percent = " PERCENT" if expression.args.get("percent") else "" 824 return f"FREESPACE={freespace}{percent}" 825 826 def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str: 827 no = "NO " if expression.args.get("no") else "" 828 dual = "DUAL " if expression.args.get("dual") else "" 829 local = "" 830 if expression.args.get("local") is not None: 831 local = "LOCAL " if expression.args.get("local") else "NOT LOCAL " 832 return f"{no}{dual}{local}AFTER JOURNAL" 833 834 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 835 if expression.args.get("default"): 836 property = "DEFAULT" 837 elif expression.args.get("on"): 838 property = "ON" 839 else: 840 property = "OFF" 841 return f"CHECKSUM={property}" 842 843 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 844 if expression.args.get("no"): 845 return "NO MERGEBLOCKRATIO" 846 if expression.args.get("default"): 847 return "DEFAULT MERGEBLOCKRATIO" 848 849 percent = " PERCENT" if expression.args.get("percent") else "" 850 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 851 852 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 853 default = expression.args.get("default") 854 min = expression.args.get("min") 855 if default is not None or min is not None: 856 if default: 857 property = "DEFAULT" 858 elif min: 859 property = "MINIMUM" 860 else: 861 property = "MAXIMUM" 862 return f"{property} DATABLOCKSIZE" 863 else: 864 units = expression.args.get("units") 865 units = f" {units}" if units else "" 866 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 867 868 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 869 autotemp = expression.args.get("autotemp") 870 always = expression.args.get("always") 871 default = expression.args.get("default") 872 manual = expression.args.get("manual") 873 never = expression.args.get("never") 874 875 if autotemp is not None: 876 property = f"AUTOTEMP({self.expressions(autotemp)})" 877 elif always: 878 property = "ALWAYS" 879 elif default: 880 property = "DEFAULT" 881 elif manual: 882 property = "MANUAL" 883 elif never: 884 property = "NEVER" 885 return f"BLOCKCOMPRESSION={property}" 886 887 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 888 no = expression.args.get("no") 889 no = " NO" if no else "" 890 concurrent = expression.args.get("concurrent") 891 concurrent = " CONCURRENT" if concurrent else "" 892 893 for_ = "" 894 if expression.args.get("for_all"): 895 for_ = " FOR ALL" 896 elif expression.args.get("for_insert"): 897 for_ = " FOR INSERT" 898 elif expression.args.get("for_none"): 899 for_ = " FOR NONE" 900 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}" 901 902 def insert_sql(self, expression: exp.Insert) -> str: 903 overwrite = expression.args.get("overwrite") 904 905 if isinstance(expression.this, exp.Directory): 906 this = "OVERWRITE " if overwrite else "INTO " 907 else: 908 this = "OVERWRITE TABLE " if overwrite else "INTO " 909 910 this = f"{this}{self.sql(expression, 'this')}" 911 exists = " IF EXISTS " if expression.args.get("exists") else " " 912 partition_sql = ( 913 self.sql(expression, "partition") if expression.args.get("partition") else "" 914 ) 915 expression_sql = self.sql(expression, "expression") 916 sep = self.sep() if partition_sql else "" 917 sql = f"INSERT {this}{exists}{partition_sql}{sep}{expression_sql}" 918 return self.prepend_ctes(expression, sql) 919 920 def intersect_sql(self, expression: exp.Intersect) -> str: 921 return self.prepend_ctes( 922 expression, 923 self.set_operation(expression, self.intersect_op(expression)), 924 ) 925 926 def intersect_op(self, expression: exp.Intersect) -> str: 927 return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}" 928 929 def introducer_sql(self, expression: exp.Introducer) -> str: 930 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 931 932 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 933 return expression.name.upper() 934 935 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 936 fields = expression.args.get("fields") 937 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 938 escaped = expression.args.get("escaped") 939 escaped = f" ESCAPED BY {escaped}" if escaped else "" 940 items = expression.args.get("collection_items") 941 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 942 keys = expression.args.get("map_keys") 943 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 944 lines = expression.args.get("lines") 945 lines = f" LINES TERMINATED BY {lines}" if lines else "" 946 null = expression.args.get("null") 947 null = f" NULL DEFINED AS {null}" if null else "" 948 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 949 950 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 951 table = ".".join( 952 part 953 for part in [ 954 self.sql(expression, "catalog"), 955 self.sql(expression, "db"), 956 self.sql(expression, "this"), 957 ] 958 if part 959 ) 960 961 alias = self.sql(expression, "alias") 962 alias = f"{sep}{alias}" if alias else "" 963 hints = self.expressions(expression, key="hints", sep=", ", flat=True) 964 hints = f" WITH ({hints})" if hints else "" 965 laterals = self.expressions(expression, key="laterals", sep="") 966 joins = self.expressions(expression, key="joins", sep="") 967 pivots = self.expressions(expression, key="pivots", sep="") 968 system_time = expression.args.get("system_time") 969 system_time = f" {self.sql(expression, 'system_time')}" if system_time else "" 970 971 if alias and pivots: 972 pivots = f"{pivots}{alias}" 973 alias = "" 974 975 return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}" 976 977 def tablesample_sql(self, expression: exp.TableSample) -> str: 978 if self.alias_post_tablesample and expression.this.alias: 979 this = self.sql(expression.this, "this") 980 alias = f" AS {self.sql(expression.this, 'alias')}" 981 else: 982 this = self.sql(expression, "this") 983 alias = "" 984 method = self.sql(expression, "method") 985 method = f" {method.upper()} " if method else "" 986 numerator = self.sql(expression, "bucket_numerator") 987 denominator = self.sql(expression, "bucket_denominator") 988 field = self.sql(expression, "bucket_field") 989 field = f" ON {field}" if field else "" 990 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 991 percent = self.sql(expression, "percent") 992 percent = f"{percent} PERCENT" if percent else "" 993 rows = self.sql(expression, "rows") 994 rows = f"{rows} ROWS" if rows else "" 995 size = self.sql(expression, "size") 996 seed = self.sql(expression, "seed") 997 seed = f" SEED ({seed})" if seed else "" 998 return f"{this} TABLESAMPLE{method}({bucket}{percent}{rows}{size}){seed}{alias}" 999 1000 def pivot_sql(self, expression: exp.Pivot) -> str: 1001 this = self.sql(expression, "this") 1002 unpivot = expression.args.get("unpivot") 1003 direction = "UNPIVOT" if unpivot else "PIVOT" 1004 expressions = self.expressions(expression, key="expressions") 1005 field = self.sql(expression, "field") 1006 return f"{this} {direction}({expressions} FOR {field})" 1007 1008 def tuple_sql(self, expression: exp.Tuple) -> str: 1009 return f"({self.expressions(expression, flat=True)})" 1010 1011 def update_sql(self, expression: exp.Update) -> str: 1012 this = self.sql(expression, "this") 1013 set_sql = self.expressions(expression, flat=True) 1014 from_sql = self.sql(expression, "from") 1015 where_sql = self.sql(expression, "where") 1016 sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}" 1017 return self.prepend_ctes(expression, sql) 1018 1019 def values_sql(self, expression: exp.Values) -> str: 1020 args = self.expressions(expression) 1021 alias = self.sql(expression, "alias") 1022 values = f"VALUES{self.seg('')}{args}" 1023 values = ( 1024 f"({values})" 1025 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1026 else values 1027 ) 1028 return f"{values} AS {alias}" if alias else values 1029 1030 def var_sql(self, expression: exp.Var) -> str: 1031 return self.sql(expression, "this") 1032 1033 def into_sql(self, expression: exp.Into) -> str: 1034 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1035 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 1036 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 1037 1038 def from_sql(self, expression: exp.From) -> str: 1039 expressions = self.expressions(expression, flat=True) 1040 return f"{self.seg('FROM')} {expressions}" 1041 1042 def group_sql(self, expression: exp.Group) -> str: 1043 group_by = self.op_expressions("GROUP BY", expression) 1044 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False) 1045 grouping_sets = ( 1046 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else "" 1047 ) 1048 1049 cube = expression.args.get("cube") 1050 if cube is True: 1051 cube = self.seg("WITH CUBE") 1052 else: 1053 cube = self.expressions(expression, key="cube", indent=False) 1054 cube = f"{self.seg('CUBE')} {self.wrap(cube)}" if cube else "" 1055 1056 rollup = expression.args.get("rollup") 1057 if rollup is True: 1058 rollup = self.seg("WITH ROLLUP") 1059 else: 1060 rollup = self.expressions(expression, key="rollup", indent=False) 1061 rollup = f"{self.seg('ROLLUP')} {self.wrap(rollup)}" if rollup else "" 1062 1063 return f"{group_by}{csv(grouping_sets, cube, rollup, sep=',')}" 1064 1065 def having_sql(self, expression: exp.Having) -> str: 1066 this = self.indent(self.sql(expression, "this")) 1067 return f"{self.seg('HAVING')}{self.sep()}{this}" 1068 1069 def join_sql(self, expression: exp.Join) -> str: 1070 op_sql = self.seg( 1071 " ".join( 1072 op 1073 for op in ( 1074 "NATURAL" if expression.args.get("natural") else None, 1075 expression.side, 1076 expression.kind, 1077 "JOIN", 1078 ) 1079 if op 1080 ) 1081 ) 1082 on_sql = self.sql(expression, "on") 1083 using = expression.args.get("using") 1084 1085 if not on_sql and using: 1086 on_sql = csv(*(self.sql(column) for column in using)) 1087 1088 if on_sql: 1089 on_sql = self.indent(on_sql, skip_first=True) 1090 space = self.seg(" " * self.pad) if self.pretty else " " 1091 if using: 1092 on_sql = f"{space}USING ({on_sql})" 1093 else: 1094 on_sql = f"{space}ON {on_sql}" 1095 1096 expression_sql = self.sql(expression, "expression") 1097 this_sql = self.sql(expression, "this") 1098 return f"{expression_sql}{op_sql} {this_sql}{on_sql}" 1099 1100 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 1101 args = self.expressions(expression, flat=True) 1102 args = f"({args})" if len(args.split(",")) > 1 else args 1103 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 1104 1105 def lateral_sql(self, expression: exp.Lateral) -> str: 1106 this = self.sql(expression, "this") 1107 1108 if isinstance(expression.this, exp.Subquery): 1109 return f"LATERAL {this}" 1110 1111 if expression.args.get("view"): 1112 alias = expression.args["alias"] 1113 columns = self.expressions(alias, key="columns", flat=True) 1114 table = f" {alias.name}" if alias.name else "" 1115 columns = f" AS {columns}" if columns else "" 1116 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 1117 return f"{op_sql}{self.sep()}{this}{table}{columns}" 1118 1119 alias = self.sql(expression, "alias") 1120 alias = f" AS {alias}" if alias else "" 1121 return f"LATERAL {this}{alias}" 1122 1123 def limit_sql(self, expression: exp.Limit) -> str: 1124 this = self.sql(expression, "this") 1125 return f"{this}{self.seg('LIMIT')} {self.sql(expression, 'expression')}" 1126 1127 def offset_sql(self, expression: exp.Offset) -> str: 1128 this = self.sql(expression, "this") 1129 return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}" 1130 1131 def lock_sql(self, expression: exp.Lock) -> str: 1132 if self.LOCKING_READS_SUPPORTED: 1133 lock_type = "UPDATE" if expression.args["update"] else "SHARE" 1134 return self.seg(f"FOR {lock_type}") 1135 1136 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 1137 return "" 1138 1139 def literal_sql(self, expression: exp.Literal) -> str: 1140 text = expression.this or "" 1141 if expression.is_string: 1142 if self._replace_backslash: 1143 text = BACKSLASH_RE.sub(r"\\\\", text) 1144 text = text.replace(self.quote_end, self._escaped_quote_end) 1145 if self.pretty: 1146 text = text.replace("\n", self.SENTINEL_LINE_BREAK) 1147 text = f"{self.quote_start}{text}{self.quote_end}" 1148 return text 1149 1150 def loaddata_sql(self, expression: exp.LoadData) -> str: 1151 local = " LOCAL" if expression.args.get("local") else "" 1152 inpath = f" INPATH {self.sql(expression, 'inpath')}" 1153 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 1154 this = f" INTO TABLE {self.sql(expression, 'this')}" 1155 partition = self.sql(expression, "partition") 1156 partition = f" {partition}" if partition else "" 1157 input_format = self.sql(expression, "input_format") 1158 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 1159 serde = self.sql(expression, "serde") 1160 serde = f" SERDE {serde}" if serde else "" 1161 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 1162 1163 def null_sql(self, *_) -> str: 1164 return "NULL" 1165 1166 def boolean_sql(self, expression: exp.Boolean) -> str: 1167 return "TRUE" if expression.this else "FALSE" 1168 1169 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 1170 this = self.sql(expression, "this") 1171 this = f"{this} " if this else this 1172 return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat) # type: ignore 1173 1174 def cluster_sql(self, expression: exp.Cluster) -> str: 1175 return self.op_expressions("CLUSTER BY", expression) 1176 1177 def distribute_sql(self, expression: exp.Distribute) -> str: 1178 return self.op_expressions("DISTRIBUTE BY", expression) 1179 1180 def sort_sql(self, expression: exp.Sort) -> str: 1181 return self.op_expressions("SORT BY", expression) 1182 1183 def ordered_sql(self, expression: exp.Ordered) -> str: 1184 desc = expression.args.get("desc") 1185 asc = not desc 1186 1187 nulls_first = expression.args.get("nulls_first") 1188 nulls_last = not nulls_first 1189 nulls_are_large = self.null_ordering == "nulls_are_large" 1190 nulls_are_small = self.null_ordering == "nulls_are_small" 1191 nulls_are_last = self.null_ordering == "nulls_are_last" 1192 1193 sort_order = " DESC" if desc else "" 1194 nulls_sort_change = "" 1195 if nulls_first and ( 1196 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 1197 ): 1198 nulls_sort_change = " NULLS FIRST" 1199 elif ( 1200 nulls_last 1201 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 1202 and not nulls_are_last 1203 ): 1204 nulls_sort_change = " NULLS LAST" 1205 1206 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 1207 self.unsupported( 1208 "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect" 1209 ) 1210 nulls_sort_change = "" 1211 1212 return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}" 1213 1214 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 1215 partition = self.partition_by_sql(expression) 1216 order = self.sql(expression, "order") 1217 measures = self.sql(expression, "measures") 1218 measures = self.seg(f"MEASURES {measures}") if measures else "" 1219 rows = self.sql(expression, "rows") 1220 rows = self.seg(rows) if rows else "" 1221 after = self.sql(expression, "after") 1222 after = self.seg(after) if after else "" 1223 pattern = self.sql(expression, "pattern") 1224 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 1225 define = self.sql(expression, "define") 1226 define = self.seg(f"DEFINE {define}") if define else "" 1227 body = "".join( 1228 ( 1229 partition, 1230 order, 1231 measures, 1232 rows, 1233 after, 1234 pattern, 1235 define, 1236 ) 1237 ) 1238 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}" 1239 1240 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 1241 return csv( 1242 *sqls, 1243 *[self.sql(sql) for sql in expression.args.get("joins") or []], 1244 self.sql(expression, "match"), 1245 *[self.sql(sql) for sql in expression.args.get("laterals") or []], 1246 self.sql(expression, "where"), 1247 self.sql(expression, "group"), 1248 self.sql(expression, "having"), 1249 self.sql(expression, "qualify"), 1250 self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True) 1251 if expression.args.get("windows") 1252 else "", 1253 self.sql(expression, "distribute"), 1254 self.sql(expression, "sort"), 1255 self.sql(expression, "cluster"), 1256 self.sql(expression, "order"), 1257 self.sql(expression, "limit"), 1258 self.sql(expression, "offset"), 1259 self.sql(expression, "lock"), 1260 sep="", 1261 ) 1262 1263 def select_sql(self, expression: exp.Select) -> str: 1264 hint = self.sql(expression, "hint") 1265 distinct = self.sql(expression, "distinct") 1266 distinct = f" {distinct}" if distinct else "" 1267 expressions = self.expressions(expression) 1268 expressions = f"{self.sep()}{expressions}" if expressions else expressions 1269 sql = self.query_modifiers( 1270 expression, 1271 f"SELECT{hint}{distinct}{expressions}", 1272 self.sql(expression, "into", comment=False), 1273 self.sql(expression, "from", comment=False), 1274 ) 1275 return self.prepend_ctes(expression, sql) 1276 1277 def schema_sql(self, expression: exp.Schema) -> str: 1278 this = self.sql(expression, "this") 1279 this = f"{this} " if this else "" 1280 sql = f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 1281 return f"{this}{sql}" 1282 1283 def star_sql(self, expression: exp.Star) -> str: 1284 except_ = self.expressions(expression, key="except", flat=True) 1285 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else "" 1286 replace = self.expressions(expression, key="replace", flat=True) 1287 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else "" 1288 return f"*{except_}{replace}" 1289 1290 def structkwarg_sql(self, expression: exp.StructKwarg) -> str: 1291 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1292 1293 def parameter_sql(self, expression: exp.Parameter) -> str: 1294 return f"@{self.sql(expression, 'this')}" 1295 1296 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 1297 this = self.sql(expression, "this") 1298 kind = expression.text("kind") 1299 if kind: 1300 kind = f"{kind}." 1301 return f"@@{kind}{this}" 1302 1303 def placeholder_sql(self, expression: exp.Placeholder) -> str: 1304 return f":{expression.name}" if expression.name else "?" 1305 1306 def subquery_sql(self, expression: exp.Subquery) -> str: 1307 alias = self.sql(expression, "alias") 1308 1309 sql = self.query_modifiers( 1310 expression, 1311 self.wrap(expression), 1312 self.expressions(expression, key="pivots", sep=" "), 1313 f" AS {alias}" if alias else "", 1314 ) 1315 1316 return self.prepend_ctes(expression, sql) 1317 1318 def qualify_sql(self, expression: exp.Qualify) -> str: 1319 this = self.indent(self.sql(expression, "this")) 1320 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 1321 1322 def union_sql(self, expression: exp.Union) -> str: 1323 return self.prepend_ctes( 1324 expression, 1325 self.set_operation(expression, self.union_op(expression)), 1326 ) 1327 1328 def union_op(self, expression: exp.Union) -> str: 1329 kind = " DISTINCT" if self.EXPLICIT_UNION else "" 1330 kind = kind if expression.args.get("distinct") else " ALL" 1331 return f"UNION{kind}" 1332 1333 def unnest_sql(self, expression: exp.Unnest) -> str: 1334 args = self.expressions(expression, flat=True) 1335 alias = expression.args.get("alias") 1336 if alias and self.unnest_column_only: 1337 columns = alias.columns 1338 alias = self.sql(columns[0]) if columns else "" 1339 else: 1340 alias = self.sql(expression, "alias") 1341 alias = f" AS {alias}" if alias else alias 1342 ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else "" 1343 offset = expression.args.get("offset") 1344 offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else "" 1345 return f"UNNEST({args}){ordinality}{alias}{offset}" 1346 1347 def where_sql(self, expression: exp.Where) -> str: 1348 this = self.indent(self.sql(expression, "this")) 1349 return f"{self.seg('WHERE')}{self.sep()}{this}" 1350 1351 def window_sql(self, expression: exp.Window) -> str: 1352 this = self.sql(expression, "this") 1353 1354 partition = self.partition_by_sql(expression) 1355 1356 order = expression.args.get("order") 1357 order_sql = self.order_sql(order, flat=True) if order else "" 1358 1359 partition_sql = partition + " " if partition and order else partition 1360 1361 spec = expression.args.get("spec") 1362 spec_sql = " " + self.window_spec_sql(spec) if spec else "" 1363 1364 alias = self.sql(expression, "alias") 1365 this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}" 1366 1367 if not partition and not order and not spec and alias: 1368 return f"{this} {alias}" 1369 1370 window_args = alias + partition_sql + order_sql + spec_sql 1371 1372 return f"{this} ({window_args.strip()})" 1373 1374 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 1375 partition = self.expressions(expression, key="partition_by", flat=True) 1376 return f"PARTITION BY {partition}" if partition else "" 1377 1378 def window_spec_sql(self, expression: exp.WindowSpec) -> str: 1379 kind = self.sql(expression, "kind") 1380 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 1381 end = ( 1382 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 1383 or "CURRENT ROW" 1384 ) 1385 return f"{kind} BETWEEN {start} AND {end}" 1386 1387 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1388 this = self.sql(expression, "this") 1389 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 1390 return f"{this} WITHIN GROUP ({expression_sql})" 1391 1392 def between_sql(self, expression: exp.Between) -> str: 1393 this = self.sql(expression, "this") 1394 low = self.sql(expression, "low") 1395 high = self.sql(expression, "high") 1396 return f"{this} BETWEEN {low} AND {high}" 1397 1398 def bracket_sql(self, expression: exp.Bracket) -> str: 1399 expressions = apply_index_offset(expression.expressions, self.index_offset) 1400 expressions_sql = ", ".join(self.sql(e) for e in expressions) 1401 1402 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 1403 1404 def all_sql(self, expression: exp.All) -> str: 1405 return f"ALL {self.wrap(expression)}" 1406 1407 def any_sql(self, expression: exp.Any) -> str: 1408 return f"ANY {self.wrap(expression)}" 1409 1410 def exists_sql(self, expression: exp.Exists) -> str: 1411 return f"EXISTS{self.wrap(expression)}" 1412 1413 def case_sql(self, expression: exp.Case) -> str: 1414 this = self.sql(expression, "this") 1415 statements = [f"CASE {this}" if this else "CASE"] 1416 1417 for e in expression.args["ifs"]: 1418 statements.append(f"WHEN {self.sql(e, 'this')}") 1419 statements.append(f"THEN {self.sql(e, 'true')}") 1420 1421 default = self.sql(expression, "default") 1422 1423 if default: 1424 statements.append(f"ELSE {default}") 1425 1426 statements.append("END") 1427 1428 if self.pretty and self.text_width(statements) > self._max_text_width: 1429 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 1430 1431 return " ".join(statements) 1432 1433 def constraint_sql(self, expression: exp.Constraint) -> str: 1434 this = self.sql(expression, "this") 1435 expressions = self.expressions(expression, flat=True) 1436 return f"CONSTRAINT {this} {expressions}" 1437 1438 def extract_sql(self, expression: exp.Extract) -> str: 1439 this = self.sql(expression, "this") 1440 expression_sql = self.sql(expression, "expression") 1441 return f"EXTRACT({this} FROM {expression_sql})" 1442 1443 def trim_sql(self, expression: exp.Trim) -> str: 1444 trim_type = self.sql(expression, "position") 1445 1446 if trim_type == "LEADING": 1447 return f"{self.normalize_func('LTRIM')}({self.format_args(expression.this)})" 1448 elif trim_type == "TRAILING": 1449 return f"{self.normalize_func('RTRIM')}({self.format_args(expression.this)})" 1450 else: 1451 return f"{self.normalize_func('TRIM')}({self.format_args(expression.this, expression.expression)})" 1452 1453 def concat_sql(self, expression: exp.Concat) -> str: 1454 if len(expression.expressions) == 1: 1455 return self.sql(expression.expressions[0]) 1456 return self.function_fallback_sql(expression) 1457 1458 def check_sql(self, expression: exp.Check) -> str: 1459 this = self.sql(expression, key="this") 1460 return f"CHECK ({this})" 1461 1462 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 1463 expressions = self.expressions(expression, flat=True) 1464 reference = self.sql(expression, "reference") 1465 reference = f" {reference}" if reference else "" 1466 delete = self.sql(expression, "delete") 1467 delete = f" ON DELETE {delete}" if delete else "" 1468 update = self.sql(expression, "update") 1469 update = f" ON UPDATE {update}" if update else "" 1470 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 1471 1472 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 1473 expressions = self.expressions(expression, flat=True) 1474 options = self.expressions(expression, "options", flat=True, sep=" ") 1475 options = f" {options}" if options else "" 1476 return f"PRIMARY KEY ({expressions}){options}" 1477 1478 def unique_sql(self, expression: exp.Unique) -> str: 1479 columns = self.expressions(expression, key="expressions") 1480 return f"UNIQUE ({columns})" 1481 1482 def if_sql(self, expression: exp.If) -> str: 1483 return self.case_sql( 1484 exp.Case(ifs=[expression.copy()], default=expression.args.get("false")) 1485 ) 1486 1487 def in_sql(self, expression: exp.In) -> str: 1488 query = expression.args.get("query") 1489 unnest = expression.args.get("unnest") 1490 field = expression.args.get("field") 1491 is_global = " GLOBAL" if expression.args.get("is_global") else "" 1492 1493 if query: 1494 in_sql = self.wrap(query) 1495 elif unnest: 1496 in_sql = self.in_unnest_op(unnest) 1497 elif field: 1498 in_sql = self.sql(field) 1499 else: 1500 in_sql = f"({self.expressions(expression, flat=True)})" 1501 1502 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 1503 1504 def in_unnest_op(self, unnest: exp.Unnest) -> str: 1505 return f"(SELECT {self.sql(unnest)})" 1506 1507 def interval_sql(self, expression: exp.Interval) -> str: 1508 this = expression.args.get("this") 1509 if this: 1510 this = ( 1511 f" {this}" 1512 if isinstance(this, exp.Literal) or isinstance(this, exp.Paren) 1513 else f" ({this})" 1514 ) 1515 else: 1516 this = "" 1517 unit = expression.args.get("unit") 1518 unit = f" {unit}" if unit else "" 1519 return f"INTERVAL{this}{unit}" 1520 1521 def return_sql(self, expression: exp.Return) -> str: 1522 return f"RETURN {self.sql(expression, 'this')}" 1523 1524 def reference_sql(self, expression: exp.Reference) -> str: 1525 this = self.sql(expression, "this") 1526 expressions = self.expressions(expression, flat=True) 1527 expressions = f"({expressions})" if expressions else "" 1528 options = self.expressions(expression, "options", flat=True, sep=" ") 1529 options = f" {options}" if options else "" 1530 return f"REFERENCES {this}{expressions}{options}" 1531 1532 def anonymous_sql(self, expression: exp.Anonymous) -> str: 1533 args = self.format_args(*expression.expressions) 1534 return f"{self.normalize_func(self.sql(expression, 'this'))}({args})" 1535 1536 def paren_sql(self, expression: exp.Paren) -> str: 1537 if isinstance(expression.unnest(), exp.Select): 1538 sql = self.wrap(expression) 1539 else: 1540 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 1541 sql = f"({sql}{self.seg(')', sep='')}" 1542 1543 return self.prepend_ctes(expression, sql) 1544 1545 def neg_sql(self, expression: exp.Neg) -> str: 1546 # This makes sure we don't convert "- - 5" to "--5", which is a comment 1547 this_sql = self.sql(expression, "this") 1548 sep = " " if this_sql[0] == "-" else "" 1549 return f"-{sep}{this_sql}" 1550 1551 def not_sql(self, expression: exp.Not) -> str: 1552 return f"NOT {self.sql(expression, 'this')}" 1553 1554 def alias_sql(self, expression: exp.Alias) -> str: 1555 to_sql = self.sql(expression, "alias") 1556 to_sql = f" AS {to_sql}" if to_sql else "" 1557 return f"{self.sql(expression, 'this')}{to_sql}" 1558 1559 def aliases_sql(self, expression: exp.Aliases) -> str: 1560 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 1561 1562 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 1563 this = self.sql(expression, "this") 1564 zone = self.sql(expression, "zone") 1565 return f"{this} AT TIME ZONE {zone}" 1566 1567 def add_sql(self, expression: exp.Add) -> str: 1568 return self.binary(expression, "+") 1569 1570 def and_sql(self, expression: exp.And) -> str: 1571 return self.connector_sql(expression, "AND") 1572 1573 def connector_sql(self, expression: exp.Connector, op: str) -> str: 1574 if not self.pretty: 1575 return self.binary(expression, op) 1576 1577 sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False)) 1578 sep = "\n" if self.text_width(sqls) > self._max_text_width else " " 1579 return f"{sep}{op} ".join(sqls) 1580 1581 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 1582 return self.binary(expression, "&") 1583 1584 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 1585 return self.binary(expression, "<<") 1586 1587 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 1588 return f"~{self.sql(expression, 'this')}" 1589 1590 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 1591 return self.binary(expression, "|") 1592 1593 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 1594 return self.binary(expression, ">>") 1595 1596 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 1597 return self.binary(expression, "^") 1598 1599 def cast_sql(self, expression: exp.Cast) -> str: 1600 return f"CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})" 1601 1602 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1603 zone = self.sql(expression, "this") 1604 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 1605 1606 def collate_sql(self, expression: exp.Collate) -> str: 1607 return self.binary(expression, "COLLATE") 1608 1609 def command_sql(self, expression: exp.Command) -> str: 1610 return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}" 1611 1612 def transaction_sql(self, *_) -> str: 1613 return "BEGIN" 1614 1615 def commit_sql(self, expression: exp.Commit) -> str: 1616 chain = expression.args.get("chain") 1617 if chain is not None: 1618 chain = " AND CHAIN" if chain else " AND NO CHAIN" 1619 1620 return f"COMMIT{chain or ''}" 1621 1622 def rollback_sql(self, expression: exp.Rollback) -> str: 1623 savepoint = expression.args.get("savepoint") 1624 savepoint = f" TO {savepoint}" if savepoint else "" 1625 return f"ROLLBACK{savepoint}" 1626 1627 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 1628 this = self.sql(expression, "this") 1629 1630 dtype = self.sql(expression, "dtype") 1631 if dtype: 1632 collate = self.sql(expression, "collate") 1633 collate = f" COLLATE {collate}" if collate else "" 1634 using = self.sql(expression, "using") 1635 using = f" USING {using}" if using else "" 1636 return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}" 1637 1638 default = self.sql(expression, "default") 1639 if default: 1640 return f"ALTER COLUMN {this} SET DEFAULT {default}" 1641 1642 if not expression.args.get("drop"): 1643 self.unsupported("Unsupported ALTER COLUMN syntax") 1644 1645 return f"ALTER COLUMN {this} DROP DEFAULT" 1646 1647 def renametable_sql(self, expression: exp.RenameTable) -> str: 1648 this = self.sql(expression, "this") 1649 return f"RENAME TO {this}" 1650 1651 def altertable_sql(self, expression: exp.AlterTable) -> str: 1652 actions = expression.args["actions"] 1653 1654 if isinstance(actions[0], exp.ColumnDef): 1655 actions = self.expressions(expression, "actions", prefix="ADD COLUMN ") 1656 elif isinstance(actions[0], exp.Schema): 1657 actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ") 1658 elif isinstance(actions[0], exp.Delete): 1659 actions = self.expressions(expression, "actions", flat=True) 1660 else: 1661 actions = self.expressions(expression, "actions") 1662 1663 exists = " IF EXISTS" if expression.args.get("exists") else "" 1664 return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}" 1665 1666 def droppartition_sql(self, expression: exp.DropPartition) -> str: 1667 expressions = self.expressions(expression) 1668 exists = " IF EXISTS " if expression.args.get("exists") else " " 1669 return f"DROP{exists}{expressions}" 1670 1671 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 1672 this = self.sql(expression, "this") 1673 expression_ = self.sql(expression, "expression") 1674 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD" 1675 1676 enforced = expression.args.get("enforced") 1677 if enforced is not None: 1678 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}" 1679 1680 return f"{add_constraint} {expression_}" 1681 1682 def distinct_sql(self, expression: exp.Distinct) -> str: 1683 this = self.expressions(expression, flat=True) 1684 this = f" {this}" if this else "" 1685 1686 on = self.sql(expression, "on") 1687 on = f" ON {on}" if on else "" 1688 return f"DISTINCT{this}{on}" 1689 1690 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 1691 return f"{self.sql(expression, 'this')} IGNORE NULLS" 1692 1693 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 1694 return f"{self.sql(expression, 'this')} RESPECT NULLS" 1695 1696 def intdiv_sql(self, expression: exp.IntDiv) -> str: 1697 return self.sql( 1698 exp.Cast( 1699 this=exp.Div(this=expression.this, expression=expression.expression), 1700 to=exp.DataType(this=exp.DataType.Type.INT), 1701 ) 1702 ) 1703 1704 def dpipe_sql(self, expression: exp.DPipe) -> str: 1705 return self.binary(expression, "||") 1706 1707 def div_sql(self, expression: exp.Div) -> str: 1708 return self.binary(expression, "/") 1709 1710 def distance_sql(self, expression: exp.Distance) -> str: 1711 return self.binary(expression, "<->") 1712 1713 def dot_sql(self, expression: exp.Dot) -> str: 1714 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 1715 1716 def eq_sql(self, expression: exp.EQ) -> str: 1717 return self.binary(expression, "=") 1718 1719 def escape_sql(self, expression: exp.Escape) -> str: 1720 return self.binary(expression, "ESCAPE") 1721 1722 def glob_sql(self, expression: exp.Glob) -> str: 1723 return self.binary(expression, "GLOB") 1724 1725 def gt_sql(self, expression: exp.GT) -> str: 1726 return self.binary(expression, ">") 1727 1728 def gte_sql(self, expression: exp.GTE) -> str: 1729 return self.binary(expression, ">=") 1730 1731 def ilike_sql(self, expression: exp.ILike) -> str: 1732 return self.binary(expression, "ILIKE") 1733 1734 def is_sql(self, expression: exp.Is) -> str: 1735 return self.binary(expression, "IS") 1736 1737 def like_sql(self, expression: exp.Like) -> str: 1738 return self.binary(expression, "LIKE") 1739 1740 def similarto_sql(self, expression: exp.SimilarTo) -> str: 1741 return self.binary(expression, "SIMILAR TO") 1742 1743 def lt_sql(self, expression: exp.LT) -> str: 1744 return self.binary(expression, "<") 1745 1746 def lte_sql(self, expression: exp.LTE) -> str: 1747 return self.binary(expression, "<=") 1748 1749 def mod_sql(self, expression: exp.Mod) -> str: 1750 return self.binary(expression, "%") 1751 1752 def mul_sql(self, expression: exp.Mul) -> str: 1753 return self.binary(expression, "*") 1754 1755 def neq_sql(self, expression: exp.NEQ) -> str: 1756 return self.binary(expression, "<>") 1757 1758 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 1759 return self.binary(expression, "IS NOT DISTINCT FROM") 1760 1761 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 1762 return self.binary(expression, "IS DISTINCT FROM") 1763 1764 def or_sql(self, expression: exp.Or) -> str: 1765 return self.connector_sql(expression, "OR") 1766 1767 def slice_sql(self, expression: exp.Slice) -> str: 1768 return self.binary(expression, ":") 1769 1770 def sub_sql(self, expression: exp.Sub) -> str: 1771 return self.binary(expression, "-") 1772 1773 def trycast_sql(self, expression: exp.TryCast) -> str: 1774 return f"TRY_CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})" 1775 1776 def use_sql(self, expression: exp.Use) -> str: 1777 kind = self.sql(expression, "kind") 1778 kind = f" {kind}" if kind else "" 1779 this = self.sql(expression, "this") 1780 this = f" {this}" if this else "" 1781 return f"USE{kind}{this}" 1782 1783 def binary(self, expression: exp.Binary, op: str) -> str: 1784 return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}" 1785 1786 def function_fallback_sql(self, expression: exp.Func) -> str: 1787 args = [] 1788 for arg_value in expression.args.values(): 1789 if isinstance(arg_value, list): 1790 for value in arg_value: 1791 args.append(value) 1792 else: 1793 args.append(arg_value) 1794 1795 return f"{self.normalize_func(expression.sql_name())}({self.format_args(*args)})" 1796 1797 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 1798 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None) 1799 if self.pretty and self.text_width(arg_sqls) > self._max_text_width: 1800 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 1801 return ", ".join(arg_sqls) 1802 1803 def text_width(self, args: t.Iterable) -> int: 1804 return sum(len(arg) for arg in args) 1805 1806 def format_time(self, expression: exp.Expression) -> t.Optional[str]: 1807 return format_time(self.sql(expression, "format"), self.time_mapping, self.time_trie) 1808 1809 def expressions( 1810 self, 1811 expression: exp.Expression, 1812 key: t.Optional[str] = None, 1813 flat: bool = False, 1814 indent: bool = True, 1815 sep: str = ", ", 1816 prefix: str = "", 1817 ) -> str: 1818 expressions = expression.args.get(key or "expressions") 1819 1820 if not expressions: 1821 return "" 1822 1823 if flat: 1824 return sep.join(self.sql(e) for e in expressions) 1825 1826 num_sqls = len(expressions) 1827 1828 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly 1829 pad = " " * self.pad 1830 stripped_sep = sep.strip() 1831 1832 result_sqls = [] 1833 for i, e in enumerate(expressions): 1834 sql = self.sql(e, comment=False) 1835 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 1836 1837 if self.pretty: 1838 if self._leading_comma: 1839 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}") 1840 else: 1841 result_sqls.append( 1842 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}" 1843 ) 1844 else: 1845 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 1846 1847 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls) 1848 return self.indent(result_sql, skip_first=False) if indent else result_sql 1849 1850 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 1851 expressions_sql = self.expressions(expression, flat=flat) 1852 if flat: 1853 return f"{op} {expressions_sql}" 1854 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 1855 1856 def naked_property(self, expression: exp.Property) -> str: 1857 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 1858 if not property_name: 1859 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 1860 return f"{property_name} {self.sql(expression, 'this')}" 1861 1862 def set_operation(self, expression: exp.Expression, op: str) -> str: 1863 this = self.sql(expression, "this") 1864 op = self.seg(op) 1865 return self.query_modifiers( 1866 expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}" 1867 ) 1868 1869 def tag_sql(self, expression: exp.Tag) -> str: 1870 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 1871 1872 def token_sql(self, token_type: TokenType) -> str: 1873 return self.TOKEN_MAPPING.get(token_type, token_type.name) 1874 1875 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 1876 this = self.sql(expression, "this") 1877 expressions = self.no_identify(self.expressions, expression) 1878 expressions = ( 1879 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 1880 ) 1881 return f"{this}{expressions}" 1882 1883 def userdefinedfunctionkwarg_sql(self, expression: exp.UserDefinedFunctionKwarg) -> str: 1884 this = self.sql(expression, "this") 1885 kind = self.sql(expression, "kind") 1886 return f"{this} {kind}" 1887 1888 def joinhint_sql(self, expression: exp.JoinHint) -> str: 1889 this = self.sql(expression, "this") 1890 expressions = self.expressions(expression, flat=True) 1891 return f"{this}({expressions})" 1892 1893 def kwarg_sql(self, expression: exp.Kwarg) -> str: 1894 return self.binary(expression, "=>") 1895 1896 def when_sql(self, expression: exp.When) -> str: 1897 this = self.sql(expression, "this") 1898 then_expression = expression.args.get("then") 1899 if isinstance(then_expression, exp.Insert): 1900 then = f"INSERT {self.sql(then_expression, 'this')}" 1901 if "expression" in then_expression.args: 1902 then += f" VALUES {self.sql(then_expression, 'expression')}" 1903 elif isinstance(then_expression, exp.Update): 1904 if isinstance(then_expression.args.get("expressions"), exp.Star): 1905 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 1906 else: 1907 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 1908 else: 1909 then = self.sql(then_expression) 1910 return f"WHEN {this} THEN {then}" 1911 1912 def merge_sql(self, expression: exp.Merge) -> str: 1913 this = self.sql(expression, "this") 1914 using = f"USING {self.sql(expression, 'using')}" 1915 on = f"ON {self.sql(expression, 'on')}" 1916 return f"MERGE INTO {this} {using} {on} {self.expressions(expression, sep=' ')}"
class
Generator:
19class Generator: 20 """ 21 Generator interprets the given syntax tree and produces a SQL string as an output. 22 23 Args: 24 time_mapping (dict): the dictionary of custom time mappings in which the key 25 represents a python time format and the output the target time format 26 time_trie (trie): a trie of the time_mapping keys 27 pretty (bool): if set to True the returned string will be formatted. Default: False. 28 quote_start (str): specifies which starting character to use to delimit quotes. Default: '. 29 quote_end (str): specifies which ending character to use to delimit quotes. Default: '. 30 identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ". 31 identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ". 32 identify (bool): if set to True all identifiers will be delimited by the corresponding 33 character. 34 normalize (bool): if set to True all identifiers will lower cased 35 string_escape (str): specifies a string escape character. Default: '. 36 identifier_escape (str): specifies an identifier escape character. Default: ". 37 pad (int): determines padding in a formatted string. Default: 2. 38 indent (int): determines the size of indentation in a formatted string. Default: 4. 39 unnest_column_only (bool): if true unnest table aliases are considered only as column aliases 40 normalize_functions (str): normalize function names, "upper", "lower", or None 41 Default: "upper" 42 alias_post_tablesample (bool): if the table alias comes after tablesample 43 Default: False 44 unsupported_level (ErrorLevel): determines the generator's behavior when it encounters 45 unsupported expressions. Default ErrorLevel.WARN. 46 null_ordering (str): Indicates the default null ordering method to use if not explicitly set. 47 Options are "nulls_are_small", "nulls_are_large", "nulls_are_last". 48 Default: "nulls_are_small" 49 max_unsupported (int): Maximum number of unsupported messages to include in a raised UnsupportedError. 50 This is only relevant if unsupported_level is ErrorLevel.RAISE. 51 Default: 3 52 leading_comma (bool): if the the comma is leading or trailing in select statements 53 Default: False 54 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 55 The default is on the smaller end because the length only represents a segment and not the true 56 line length. 57 Default: 80 58 comments: Whether or not to preserve comments in the output SQL code. 59 Default: True 60 """ 61 62 TRANSFORMS = { 63 exp.DateAdd: lambda self, e: f"DATE_ADD({self.format_args(e.this, e.expression, e.args.get('unit'))})", 64 exp.DateDiff: lambda self, e: f"DATEDIFF({self.format_args(e.this, e.expression)})", 65 exp.TsOrDsAdd: lambda self, e: f"TS_OR_DS_ADD({self.format_args(e.this, e.expression, e.args.get('unit'))})", 66 exp.VarMap: lambda self, e: f"MAP({self.format_args(e.args['keys'], e.args['values'])})", 67 exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args['default'] else ''}CHARACTER SET={self.sql(e, 'this')}", 68 exp.LanguageProperty: lambda self, e: self.naked_property(e), 69 exp.LocationProperty: lambda self, e: self.naked_property(e), 70 exp.ReturnsProperty: lambda self, e: self.naked_property(e), 71 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 72 exp.VolatilityProperty: lambda self, e: e.name, 73 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 74 exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG", 75 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 76 } 77 78 # Whether 'CREATE ... TRANSIENT ... TABLE' is allowed 79 CREATE_TRANSIENT = False 80 81 # Whether or not null ordering is supported in order by 82 NULL_ORDERING_SUPPORTED = True 83 84 # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 85 LOCKING_READS_SUPPORTED = False 86 87 # Always do union distinct or union all 88 EXPLICIT_UNION = False 89 90 # Wrap derived values in parens, usually standard but spark doesn't support it 91 WRAP_DERIVED_VALUES = True 92 93 # Whether or not create function uses an AS before the def. 94 CREATE_FUNCTION_AS = True 95 96 TYPE_MAPPING = { 97 exp.DataType.Type.NCHAR: "CHAR", 98 exp.DataType.Type.NVARCHAR: "VARCHAR", 99 exp.DataType.Type.MEDIUMTEXT: "TEXT", 100 exp.DataType.Type.LONGTEXT: "TEXT", 101 exp.DataType.Type.MEDIUMBLOB: "BLOB", 102 exp.DataType.Type.LONGBLOB: "BLOB", 103 } 104 105 STAR_MAPPING = { 106 "except": "EXCEPT", 107 "replace": "REPLACE", 108 } 109 110 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 111 112 STRUCT_DELIMITER = ("<", ">") 113 114 PROPERTIES_LOCATION = { 115 exp.AfterJournalProperty: exp.Properties.Location.PRE_SCHEMA, 116 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 117 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 118 exp.BlockCompressionProperty: exp.Properties.Location.PRE_SCHEMA, 119 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 120 exp.ChecksumProperty: exp.Properties.Location.PRE_SCHEMA, 121 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 122 exp.DataBlocksizeProperty: exp.Properties.Location.PRE_SCHEMA, 123 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 124 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 125 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 126 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 127 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 128 exp.FallbackProperty: exp.Properties.Location.PRE_SCHEMA, 129 exp.FileFormatProperty: exp.Properties.Location.POST_SCHEMA_WITH, 130 exp.FreespaceProperty: exp.Properties.Location.PRE_SCHEMA, 131 exp.IsolatedLoadingProperty: exp.Properties.Location.PRE_SCHEMA, 132 exp.JournalProperty: exp.Properties.Location.PRE_SCHEMA, 133 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 134 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 135 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 136 exp.LogProperty: exp.Properties.Location.PRE_SCHEMA, 137 exp.MergeBlockRatioProperty: exp.Properties.Location.PRE_SCHEMA, 138 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA_WITH, 139 exp.Property: exp.Properties.Location.POST_SCHEMA_WITH, 140 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 141 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 142 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 143 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 144 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA_ROOT, 145 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 146 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 147 exp.TableFormatProperty: exp.Properties.Location.POST_SCHEMA_WITH, 148 exp.VolatilityProperty: exp.Properties.Location.POST_SCHEMA_ROOT, 149 exp.WithJournalTableProperty: exp.Properties.Location.PRE_SCHEMA, 150 } 151 152 WITH_SEPARATED_COMMENTS = (exp.Select, exp.From, exp.Where, exp.Binary) 153 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 154 155 __slots__ = ( 156 "time_mapping", 157 "time_trie", 158 "pretty", 159 "quote_start", 160 "quote_end", 161 "identifier_start", 162 "identifier_end", 163 "identify", 164 "normalize", 165 "string_escape", 166 "identifier_escape", 167 "pad", 168 "index_offset", 169 "unnest_column_only", 170 "alias_post_tablesample", 171 "normalize_functions", 172 "unsupported_level", 173 "unsupported_messages", 174 "null_ordering", 175 "max_unsupported", 176 "_indent", 177 "_replace_backslash", 178 "_escaped_quote_end", 179 "_escaped_identifier_end", 180 "_leading_comma", 181 "_max_text_width", 182 "_comments", 183 ) 184 185 def __init__( 186 self, 187 time_mapping=None, 188 time_trie=None, 189 pretty=None, 190 quote_start=None, 191 quote_end=None, 192 identifier_start=None, 193 identifier_end=None, 194 identify=False, 195 normalize=False, 196 string_escape=None, 197 identifier_escape=None, 198 pad=2, 199 indent=2, 200 index_offset=0, 201 unnest_column_only=False, 202 alias_post_tablesample=False, 203 normalize_functions="upper", 204 unsupported_level=ErrorLevel.WARN, 205 null_ordering=None, 206 max_unsupported=3, 207 leading_comma=False, 208 max_text_width=80, 209 comments=True, 210 ): 211 import sqlglot 212 213 self.time_mapping = time_mapping or {} 214 self.time_trie = time_trie 215 self.pretty = pretty if pretty is not None else sqlglot.pretty 216 self.quote_start = quote_start or "'" 217 self.quote_end = quote_end or "'" 218 self.identifier_start = identifier_start or '"' 219 self.identifier_end = identifier_end or '"' 220 self.identify = identify 221 self.normalize = normalize 222 self.string_escape = string_escape or "'" 223 self.identifier_escape = identifier_escape or '"' 224 self.pad = pad 225 self.index_offset = index_offset 226 self.unnest_column_only = unnest_column_only 227 self.alias_post_tablesample = alias_post_tablesample 228 self.normalize_functions = normalize_functions 229 self.unsupported_level = unsupported_level 230 self.unsupported_messages = [] 231 self.max_unsupported = max_unsupported 232 self.null_ordering = null_ordering 233 self._indent = indent 234 self._replace_backslash = self.string_escape == "\\" 235 self._escaped_quote_end = self.string_escape + self.quote_end 236 self._escaped_identifier_end = self.identifier_escape + self.identifier_end 237 self._leading_comma = leading_comma 238 self._max_text_width = max_text_width 239 self._comments = comments 240 241 def generate(self, expression: t.Optional[exp.Expression]) -> str: 242 """ 243 Generates a SQL string by interpreting the given syntax tree. 244 245 Args 246 expression: the syntax tree. 247 248 Returns 249 the SQL string. 250 """ 251 self.unsupported_messages = [] 252 sql = self.sql(expression).strip() 253 254 if self.unsupported_level == ErrorLevel.IGNORE: 255 return sql 256 257 if self.unsupported_level == ErrorLevel.WARN: 258 for msg in self.unsupported_messages: 259 logger.warning(msg) 260 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 261 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 262 263 if self.pretty: 264 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 265 return sql 266 267 def unsupported(self, message: str) -> None: 268 if self.unsupported_level == ErrorLevel.IMMEDIATE: 269 raise UnsupportedError(message) 270 self.unsupported_messages.append(message) 271 272 def sep(self, sep: str = " ") -> str: 273 return f"{sep.strip()}\n" if self.pretty else sep 274 275 def seg(self, sql: str, sep: str = " ") -> str: 276 return f"{self.sep(sep)}{sql}" 277 278 def pad_comment(self, comment: str) -> str: 279 comment = " " + comment if comment[0].strip() else comment 280 comment = comment + " " if comment[-1].strip() else comment 281 return comment 282 283 def maybe_comment(self, sql: str, expression: exp.Expression) -> str: 284 comments = expression.comments if self._comments else None 285 286 if not comments: 287 return sql 288 289 sep = "\n" if self.pretty else " " 290 comments_sql = sep.join( 291 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 292 ) 293 294 if not comments_sql: 295 return sql 296 297 if isinstance(expression, self.WITH_SEPARATED_COMMENTS): 298 return f"{comments_sql}{self.sep()}{sql}" 299 300 return f"{sql} {comments_sql}" 301 302 def wrap(self, expression: exp.Expression | str) -> str: 303 this_sql = self.indent( 304 self.sql(expression) 305 if isinstance(expression, (exp.Select, exp.Union)) 306 else self.sql(expression, "this"), 307 level=1, 308 pad=0, 309 ) 310 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 311 312 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 313 original = self.identify 314 self.identify = False 315 result = func(*args, **kwargs) 316 self.identify = original 317 return result 318 319 def normalize_func(self, name: str) -> str: 320 if self.normalize_functions == "upper": 321 return name.upper() 322 if self.normalize_functions == "lower": 323 return name.lower() 324 return name 325 326 def indent( 327 self, 328 sql: str, 329 level: int = 0, 330 pad: t.Optional[int] = None, 331 skip_first: bool = False, 332 skip_last: bool = False, 333 ) -> str: 334 if not self.pretty: 335 return sql 336 337 pad = self.pad if pad is None else pad 338 lines = sql.split("\n") 339 340 return "\n".join( 341 line 342 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 343 else f"{' ' * (level * self._indent + pad)}{line}" 344 for i, line in enumerate(lines) 345 ) 346 347 def sql( 348 self, 349 expression: t.Optional[str | exp.Expression], 350 key: t.Optional[str] = None, 351 comment: bool = True, 352 ) -> str: 353 if not expression: 354 return "" 355 356 if isinstance(expression, str): 357 return expression 358 359 if key: 360 return self.sql(expression.args.get(key)) 361 362 transform = self.TRANSFORMS.get(expression.__class__) 363 364 if callable(transform): 365 sql = transform(self, expression) 366 elif transform: 367 sql = transform 368 elif isinstance(expression, exp.Expression): 369 exp_handler_name = f"{expression.key}_sql" 370 371 if hasattr(self, exp_handler_name): 372 sql = getattr(self, exp_handler_name)(expression) 373 elif isinstance(expression, exp.Func): 374 sql = self.function_fallback_sql(expression) 375 elif isinstance(expression, exp.Property): 376 sql = self.property_sql(expression) 377 else: 378 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 379 else: 380 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 381 382 return self.maybe_comment(sql, expression) if self._comments and comment else sql 383 384 def uncache_sql(self, expression: exp.Uncache) -> str: 385 table = self.sql(expression, "this") 386 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 387 return f"UNCACHE TABLE{exists_sql} {table}" 388 389 def cache_sql(self, expression: exp.Cache) -> str: 390 lazy = " LAZY" if expression.args.get("lazy") else "" 391 table = self.sql(expression, "this") 392 options = expression.args.get("options") 393 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 394 sql = self.sql(expression, "expression") 395 sql = f" AS{self.sep()}{sql}" if sql else "" 396 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 397 return self.prepend_ctes(expression, sql) 398 399 def characterset_sql(self, expression: exp.CharacterSet) -> str: 400 if isinstance(expression.parent, exp.Cast): 401 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 402 default = "DEFAULT " if expression.args.get("default") else "" 403 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 404 405 def column_sql(self, expression: exp.Column) -> str: 406 return ".".join( 407 part 408 for part in [ 409 self.sql(expression, "db"), 410 self.sql(expression, "table"), 411 self.sql(expression, "this"), 412 ] 413 if part 414 ) 415 416 def columndef_sql(self, expression: exp.ColumnDef) -> str: 417 column = self.sql(expression, "this") 418 kind = self.sql(expression, "kind") 419 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 420 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 421 kind = f" {kind}" if kind else "" 422 constraints = f" {constraints}" if constraints else "" 423 424 return f"{exists}{column}{kind}{constraints}" 425 426 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 427 this = self.sql(expression, "this") 428 kind_sql = self.sql(expression, "kind") 429 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 430 431 def autoincrementcolumnconstraint_sql(self, _) -> str: 432 return self.token_sql(TokenType.AUTO_INCREMENT) 433 434 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 435 this = self.sql(expression, "this") 436 return f"CHECK ({this})" 437 438 def commentcolumnconstraint_sql(self, expression: exp.CommentColumnConstraint) -> str: 439 comment = self.sql(expression, "this") 440 return f"COMMENT {comment}" 441 442 def collatecolumnconstraint_sql(self, expression: exp.CollateColumnConstraint) -> str: 443 collate = self.sql(expression, "this") 444 return f"COLLATE {collate}" 445 446 def encodecolumnconstraint_sql(self, expression: exp.EncodeColumnConstraint) -> str: 447 encode = self.sql(expression, "this") 448 return f"ENCODE {encode}" 449 450 def defaultcolumnconstraint_sql(self, expression: exp.DefaultColumnConstraint) -> str: 451 default = self.sql(expression, "this") 452 return f"DEFAULT {default}" 453 454 def generatedasidentitycolumnconstraint_sql( 455 self, expression: exp.GeneratedAsIdentityColumnConstraint 456 ) -> str: 457 this = "" 458 if expression.this is not None: 459 this = " ALWAYS " if expression.this else " BY DEFAULT " 460 start = expression.args.get("start") 461 start = f"START WITH {start}" if start else "" 462 increment = expression.args.get("increment") 463 increment = f"INCREMENT BY {increment}" if increment else "" 464 sequence_opts = "" 465 if start or increment: 466 sequence_opts = f"{start} {increment}" 467 sequence_opts = f" ({sequence_opts.strip()})" 468 return f"GENERATED{this}AS IDENTITY{sequence_opts}" 469 470 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 471 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 472 473 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 474 desc = expression.args.get("desc") 475 if desc is not None: 476 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 477 return f"PRIMARY KEY" 478 479 def uniquecolumnconstraint_sql(self, _) -> str: 480 return "UNIQUE" 481 482 def create_sql(self, expression: exp.Create) -> str: 483 kind = self.sql(expression, "kind").upper() 484 properties = expression.args.get("properties") 485 properties_exp = expression.copy() 486 properties_locs = self.locate_properties(properties) if properties else {} 487 if properties_locs.get(exp.Properties.Location.POST_SCHEMA_ROOT) or properties_locs.get( 488 exp.Properties.Location.POST_SCHEMA_WITH 489 ): 490 properties_exp.set( 491 "properties", 492 exp.Properties( 493 expressions=[ 494 *properties_locs[exp.Properties.Location.POST_SCHEMA_ROOT], 495 *properties_locs[exp.Properties.Location.POST_SCHEMA_WITH], 496 ] 497 ), 498 ) 499 if kind == "TABLE" and properties_locs.get(exp.Properties.Location.PRE_SCHEMA): 500 this_name = self.sql(expression.this, "this") 501 this_properties = self.properties( 502 exp.Properties(expressions=properties_locs[exp.Properties.Location.PRE_SCHEMA]), 503 wrapped=False, 504 ) 505 this_schema = f"({self.expressions(expression.this)})" 506 this = f"{this_name}, {this_properties} {this_schema}" 507 properties_sql = "" 508 else: 509 this = self.sql(expression, "this") 510 properties_sql = self.sql(properties_exp, "properties") 511 begin = " BEGIN" if expression.args.get("begin") else "" 512 expression_sql = self.sql(expression, "expression") 513 if expression_sql: 514 expression_sql = f"{begin}{self.sep()}{expression_sql}" 515 516 if self.CREATE_FUNCTION_AS or kind != "FUNCTION": 517 expression_sql = f" AS{expression_sql}" 518 519 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 520 transient = ( 521 " TRANSIENT" if self.CREATE_TRANSIENT and expression.args.get("transient") else "" 522 ) 523 external = " EXTERNAL" if expression.args.get("external") else "" 524 replace = " OR REPLACE" if expression.args.get("replace") else "" 525 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 526 unique = " UNIQUE" if expression.args.get("unique") else "" 527 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 528 set_ = " SET" if expression.args.get("set") else "" 529 multiset = " MULTISET" if expression.args.get("multiset") else "" 530 global_temporary = " GLOBAL TEMPORARY" if expression.args.get("global_temporary") else "" 531 volatile = " VOLATILE" if expression.args.get("volatile") else "" 532 data = expression.args.get("data") 533 if data is None: 534 data = "" 535 elif data: 536 data = " WITH DATA" 537 else: 538 data = " WITH NO DATA" 539 statistics = expression.args.get("statistics") 540 if statistics is None: 541 statistics = "" 542 elif statistics: 543 statistics = " AND STATISTICS" 544 else: 545 statistics = " AND NO STATISTICS" 546 no_primary_index = " NO PRIMARY INDEX" if expression.args.get("no_primary_index") else "" 547 548 indexes = expression.args.get("indexes") 549 index_sql = "" 550 if indexes: 551 indexes_sql = [] 552 for index in indexes: 553 ind_unique = " UNIQUE" if index.args.get("unique") else "" 554 ind_primary = " PRIMARY" if index.args.get("primary") else "" 555 ind_amp = " AMP" if index.args.get("amp") else "" 556 ind_name = f" {index.name}" if index.name else "" 557 ind_columns = ( 558 f' ({self.expressions(index, key="columns", flat=True)})' 559 if index.args.get("columns") 560 else "" 561 ) 562 if index.args.get("primary") and properties_locs.get( 563 exp.Properties.Location.POST_INDEX 564 ): 565 postindex_props_sql = self.properties( 566 exp.Properties( 567 expressions=properties_locs[exp.Properties.Location.POST_INDEX] 568 ), 569 wrapped=False, 570 ) 571 ind_columns = f"{ind_columns} {postindex_props_sql}" 572 573 indexes_sql.append( 574 f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}" 575 ) 576 index_sql = "".join(indexes_sql) 577 578 postcreate_props_sql = "" 579 if properties_locs.get(exp.Properties.Location.POST_CREATE): 580 postcreate_props_sql = self.properties( 581 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 582 sep=" ", 583 prefix=" ", 584 wrapped=False, 585 ) 586 587 modifiers = "".join( 588 ( 589 replace, 590 temporary, 591 transient, 592 external, 593 unique, 594 materialized, 595 set_, 596 multiset, 597 global_temporary, 598 volatile, 599 postcreate_props_sql, 600 ) 601 ) 602 no_schema_binding = ( 603 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 604 ) 605 606 post_expression_modifiers = "".join((data, statistics, no_primary_index)) 607 608 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{post_expression_modifiers}{index_sql}{no_schema_binding}" 609 return self.prepend_ctes(expression, expression_sql) 610 611 def describe_sql(self, expression: exp.Describe) -> str: 612 return f"DESCRIBE {self.sql(expression, 'this')}" 613 614 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 615 with_ = self.sql(expression, "with") 616 if with_: 617 sql = f"{with_}{self.sep()}{sql}" 618 return sql 619 620 def with_sql(self, expression: exp.With) -> str: 621 sql = self.expressions(expression, flat=True) 622 recursive = "RECURSIVE " if expression.args.get("recursive") else "" 623 624 return f"WITH {recursive}{sql}" 625 626 def cte_sql(self, expression: exp.CTE) -> str: 627 alias = self.sql(expression, "alias") 628 return f"{alias} AS {self.wrap(expression)}" 629 630 def tablealias_sql(self, expression: exp.TableAlias) -> str: 631 alias = self.sql(expression, "this") 632 columns = self.expressions(expression, key="columns", flat=True) 633 columns = f"({columns})" if columns else "" 634 return f"{alias}{columns}" 635 636 def bitstring_sql(self, expression: exp.BitString) -> str: 637 return self.sql(expression, "this") 638 639 def hexstring_sql(self, expression: exp.HexString) -> str: 640 return self.sql(expression, "this") 641 642 def datatype_sql(self, expression: exp.DataType) -> str: 643 type_value = expression.this 644 type_sql = self.TYPE_MAPPING.get(type_value, type_value.value) 645 nested = "" 646 interior = self.expressions(expression, flat=True) 647 values = "" 648 if interior: 649 if expression.args.get("nested"): 650 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 651 if expression.args.get("values") is not None: 652 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 653 values = ( 654 f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}" 655 ) 656 else: 657 nested = f"({interior})" 658 659 return f"{type_sql}{nested}{values}" 660 661 def directory_sql(self, expression: exp.Directory) -> str: 662 local = "LOCAL " if expression.args.get("local") else "" 663 row_format = self.sql(expression, "row_format") 664 row_format = f" {row_format}" if row_format else "" 665 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 666 667 def delete_sql(self, expression: exp.Delete) -> str: 668 this = self.sql(expression, "this") 669 this = f" FROM {this}" if this else "" 670 using_sql = ( 671 f" USING {self.expressions(expression, 'using', sep=', USING ')}" 672 if expression.args.get("using") 673 else "" 674 ) 675 where_sql = self.sql(expression, "where") 676 sql = f"DELETE{this}{using_sql}{where_sql}" 677 return self.prepend_ctes(expression, sql) 678 679 def drop_sql(self, expression: exp.Drop) -> str: 680 this = self.sql(expression, "this") 681 kind = expression.args["kind"] 682 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 683 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 684 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 685 cascade = " CASCADE" if expression.args.get("cascade") else "" 686 return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}" 687 688 def except_sql(self, expression: exp.Except) -> str: 689 return self.prepend_ctes( 690 expression, 691 self.set_operation(expression, self.except_op(expression)), 692 ) 693 694 def except_op(self, expression: exp.Except) -> str: 695 return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}" 696 697 def fetch_sql(self, expression: exp.Fetch) -> str: 698 direction = expression.args.get("direction") 699 direction = f" {direction.upper()}" if direction else "" 700 count = expression.args.get("count") 701 count = f" {count}" if count else "" 702 return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY" 703 704 def filter_sql(self, expression: exp.Filter) -> str: 705 this = self.sql(expression, "this") 706 where = self.sql(expression, "expression")[1:] # where has a leading space 707 return f"{this} FILTER({where})" 708 709 def hint_sql(self, expression: exp.Hint) -> str: 710 if self.sql(expression, "this"): 711 self.unsupported("Hints are not supported") 712 return "" 713 714 def index_sql(self, expression: exp.Index) -> str: 715 this = self.sql(expression, "this") 716 table = self.sql(expression, "table") 717 columns = self.sql(expression, "columns") 718 return f"{this} ON {table} {columns}" 719 720 def identifier_sql(self, expression: exp.Identifier) -> str: 721 text = expression.name 722 text = text.lower() if self.normalize else text 723 text = text.replace(self.identifier_end, self._escaped_identifier_end) 724 if expression.args.get("quoted") or self.identify: 725 text = f"{self.identifier_start}{text}{self.identifier_end}" 726 return text 727 728 def national_sql(self, expression: exp.National) -> str: 729 return f"N{self.sql(expression, 'this')}" 730 731 def partition_sql(self, expression: exp.Partition) -> str: 732 return f"PARTITION({self.expressions(expression)})" 733 734 def properties_sql(self, expression: exp.Properties) -> str: 735 root_properties = [] 736 with_properties = [] 737 738 for p in expression.expressions: 739 p_loc = self.PROPERTIES_LOCATION[p.__class__] 740 if p_loc == exp.Properties.Location.POST_SCHEMA_WITH: 741 with_properties.append(p) 742 elif p_loc == exp.Properties.Location.POST_SCHEMA_ROOT: 743 root_properties.append(p) 744 745 return self.root_properties( 746 exp.Properties(expressions=root_properties) 747 ) + self.with_properties(exp.Properties(expressions=with_properties)) 748 749 def root_properties(self, properties: exp.Properties) -> str: 750 if properties.expressions: 751 return self.sep() + self.expressions(properties, indent=False, sep=" ") 752 return "" 753 754 def properties( 755 self, 756 properties: exp.Properties, 757 prefix: str = "", 758 sep: str = ", ", 759 suffix: str = "", 760 wrapped: bool = True, 761 ) -> str: 762 if properties.expressions: 763 expressions = self.expressions(properties, sep=sep, indent=False) 764 expressions = self.wrap(expressions) if wrapped else expressions 765 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}" 766 return "" 767 768 def with_properties(self, properties: exp.Properties) -> str: 769 return self.properties(properties, prefix=self.seg("WITH")) 770 771 def locate_properties( 772 self, properties: exp.Properties 773 ) -> t.Dict[exp.Properties.Location, list[exp.Property]]: 774 properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = { 775 key: [] for key in exp.Properties.Location 776 } 777 778 for p in properties.expressions: 779 p_loc = self.PROPERTIES_LOCATION[p.__class__] 780 if p_loc == exp.Properties.Location.PRE_SCHEMA: 781 properties_locs[exp.Properties.Location.PRE_SCHEMA].append(p) 782 elif p_loc == exp.Properties.Location.POST_INDEX: 783 properties_locs[exp.Properties.Location.POST_INDEX].append(p) 784 elif p_loc == exp.Properties.Location.POST_SCHEMA_ROOT: 785 properties_locs[exp.Properties.Location.POST_SCHEMA_ROOT].append(p) 786 elif p_loc == exp.Properties.Location.POST_SCHEMA_WITH: 787 properties_locs[exp.Properties.Location.POST_SCHEMA_WITH].append(p) 788 elif p_loc == exp.Properties.Location.POST_CREATE: 789 properties_locs[exp.Properties.Location.POST_CREATE].append(p) 790 elif p_loc == exp.Properties.Location.UNSUPPORTED: 791 self.unsupported(f"Unsupported property {p.key}") 792 793 return properties_locs 794 795 def property_sql(self, expression: exp.Property) -> str: 796 property_cls = expression.__class__ 797 if property_cls == exp.Property: 798 return f"{expression.name}={self.sql(expression, 'value')}" 799 800 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 801 if not property_name: 802 self.unsupported(f"Unsupported property {expression.key}") 803 804 return f"{property_name}={self.sql(expression, 'this')}" 805 806 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 807 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 808 options = f" {options}" if options else "" 809 return f"LIKE {self.sql(expression, 'this')}{options}" 810 811 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 812 no = "NO " if expression.args.get("no") else "" 813 protection = " PROTECTION" if expression.args.get("protection") else "" 814 return f"{no}FALLBACK{protection}" 815 816 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 817 no = "NO " if expression.args.get("no") else "" 818 dual = "DUAL " if expression.args.get("dual") else "" 819 before = "BEFORE " if expression.args.get("before") else "" 820 return f"{no}{dual}{before}JOURNAL" 821 822 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 823 freespace = self.sql(expression, "this") 824 percent = " PERCENT" if expression.args.get("percent") else "" 825 return f"FREESPACE={freespace}{percent}" 826 827 def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str: 828 no = "NO " if expression.args.get("no") else "" 829 dual = "DUAL " if expression.args.get("dual") else "" 830 local = "" 831 if expression.args.get("local") is not None: 832 local = "LOCAL " if expression.args.get("local") else "NOT LOCAL " 833 return f"{no}{dual}{local}AFTER JOURNAL" 834 835 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 836 if expression.args.get("default"): 837 property = "DEFAULT" 838 elif expression.args.get("on"): 839 property = "ON" 840 else: 841 property = "OFF" 842 return f"CHECKSUM={property}" 843 844 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 845 if expression.args.get("no"): 846 return "NO MERGEBLOCKRATIO" 847 if expression.args.get("default"): 848 return "DEFAULT MERGEBLOCKRATIO" 849 850 percent = " PERCENT" if expression.args.get("percent") else "" 851 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 852 853 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 854 default = expression.args.get("default") 855 min = expression.args.get("min") 856 if default is not None or min is not None: 857 if default: 858 property = "DEFAULT" 859 elif min: 860 property = "MINIMUM" 861 else: 862 property = "MAXIMUM" 863 return f"{property} DATABLOCKSIZE" 864 else: 865 units = expression.args.get("units") 866 units = f" {units}" if units else "" 867 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 868 869 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 870 autotemp = expression.args.get("autotemp") 871 always = expression.args.get("always") 872 default = expression.args.get("default") 873 manual = expression.args.get("manual") 874 never = expression.args.get("never") 875 876 if autotemp is not None: 877 property = f"AUTOTEMP({self.expressions(autotemp)})" 878 elif always: 879 property = "ALWAYS" 880 elif default: 881 property = "DEFAULT" 882 elif manual: 883 property = "MANUAL" 884 elif never: 885 property = "NEVER" 886 return f"BLOCKCOMPRESSION={property}" 887 888 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 889 no = expression.args.get("no") 890 no = " NO" if no else "" 891 concurrent = expression.args.get("concurrent") 892 concurrent = " CONCURRENT" if concurrent else "" 893 894 for_ = "" 895 if expression.args.get("for_all"): 896 for_ = " FOR ALL" 897 elif expression.args.get("for_insert"): 898 for_ = " FOR INSERT" 899 elif expression.args.get("for_none"): 900 for_ = " FOR NONE" 901 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}" 902 903 def insert_sql(self, expression: exp.Insert) -> str: 904 overwrite = expression.args.get("overwrite") 905 906 if isinstance(expression.this, exp.Directory): 907 this = "OVERWRITE " if overwrite else "INTO " 908 else: 909 this = "OVERWRITE TABLE " if overwrite else "INTO " 910 911 this = f"{this}{self.sql(expression, 'this')}" 912 exists = " IF EXISTS " if expression.args.get("exists") else " " 913 partition_sql = ( 914 self.sql(expression, "partition") if expression.args.get("partition") else "" 915 ) 916 expression_sql = self.sql(expression, "expression") 917 sep = self.sep() if partition_sql else "" 918 sql = f"INSERT {this}{exists}{partition_sql}{sep}{expression_sql}" 919 return self.prepend_ctes(expression, sql) 920 921 def intersect_sql(self, expression: exp.Intersect) -> str: 922 return self.prepend_ctes( 923 expression, 924 self.set_operation(expression, self.intersect_op(expression)), 925 ) 926 927 def intersect_op(self, expression: exp.Intersect) -> str: 928 return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}" 929 930 def introducer_sql(self, expression: exp.Introducer) -> str: 931 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 932 933 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 934 return expression.name.upper() 935 936 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 937 fields = expression.args.get("fields") 938 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 939 escaped = expression.args.get("escaped") 940 escaped = f" ESCAPED BY {escaped}" if escaped else "" 941 items = expression.args.get("collection_items") 942 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 943 keys = expression.args.get("map_keys") 944 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 945 lines = expression.args.get("lines") 946 lines = f" LINES TERMINATED BY {lines}" if lines else "" 947 null = expression.args.get("null") 948 null = f" NULL DEFINED AS {null}" if null else "" 949 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 950 951 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 952 table = ".".join( 953 part 954 for part in [ 955 self.sql(expression, "catalog"), 956 self.sql(expression, "db"), 957 self.sql(expression, "this"), 958 ] 959 if part 960 ) 961 962 alias = self.sql(expression, "alias") 963 alias = f"{sep}{alias}" if alias else "" 964 hints = self.expressions(expression, key="hints", sep=", ", flat=True) 965 hints = f" WITH ({hints})" if hints else "" 966 laterals = self.expressions(expression, key="laterals", sep="") 967 joins = self.expressions(expression, key="joins", sep="") 968 pivots = self.expressions(expression, key="pivots", sep="") 969 system_time = expression.args.get("system_time") 970 system_time = f" {self.sql(expression, 'system_time')}" if system_time else "" 971 972 if alias and pivots: 973 pivots = f"{pivots}{alias}" 974 alias = "" 975 976 return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}" 977 978 def tablesample_sql(self, expression: exp.TableSample) -> str: 979 if self.alias_post_tablesample and expression.this.alias: 980 this = self.sql(expression.this, "this") 981 alias = f" AS {self.sql(expression.this, 'alias')}" 982 else: 983 this = self.sql(expression, "this") 984 alias = "" 985 method = self.sql(expression, "method") 986 method = f" {method.upper()} " if method else "" 987 numerator = self.sql(expression, "bucket_numerator") 988 denominator = self.sql(expression, "bucket_denominator") 989 field = self.sql(expression, "bucket_field") 990 field = f" ON {field}" if field else "" 991 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 992 percent = self.sql(expression, "percent") 993 percent = f"{percent} PERCENT" if percent else "" 994 rows = self.sql(expression, "rows") 995 rows = f"{rows} ROWS" if rows else "" 996 size = self.sql(expression, "size") 997 seed = self.sql(expression, "seed") 998 seed = f" SEED ({seed})" if seed else "" 999 return f"{this} TABLESAMPLE{method}({bucket}{percent}{rows}{size}){seed}{alias}" 1000 1001 def pivot_sql(self, expression: exp.Pivot) -> str: 1002 this = self.sql(expression, "this") 1003 unpivot = expression.args.get("unpivot") 1004 direction = "UNPIVOT" if unpivot else "PIVOT" 1005 expressions = self.expressions(expression, key="expressions") 1006 field = self.sql(expression, "field") 1007 return f"{this} {direction}({expressions} FOR {field})" 1008 1009 def tuple_sql(self, expression: exp.Tuple) -> str: 1010 return f"({self.expressions(expression, flat=True)})" 1011 1012 def update_sql(self, expression: exp.Update) -> str: 1013 this = self.sql(expression, "this") 1014 set_sql = self.expressions(expression, flat=True) 1015 from_sql = self.sql(expression, "from") 1016 where_sql = self.sql(expression, "where") 1017 sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}" 1018 return self.prepend_ctes(expression, sql) 1019 1020 def values_sql(self, expression: exp.Values) -> str: 1021 args = self.expressions(expression) 1022 alias = self.sql(expression, "alias") 1023 values = f"VALUES{self.seg('')}{args}" 1024 values = ( 1025 f"({values})" 1026 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1027 else values 1028 ) 1029 return f"{values} AS {alias}" if alias else values 1030 1031 def var_sql(self, expression: exp.Var) -> str: 1032 return self.sql(expression, "this") 1033 1034 def into_sql(self, expression: exp.Into) -> str: 1035 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1036 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 1037 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 1038 1039 def from_sql(self, expression: exp.From) -> str: 1040 expressions = self.expressions(expression, flat=True) 1041 return f"{self.seg('FROM')} {expressions}" 1042 1043 def group_sql(self, expression: exp.Group) -> str: 1044 group_by = self.op_expressions("GROUP BY", expression) 1045 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False) 1046 grouping_sets = ( 1047 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else "" 1048 ) 1049 1050 cube = expression.args.get("cube") 1051 if cube is True: 1052 cube = self.seg("WITH CUBE") 1053 else: 1054 cube = self.expressions(expression, key="cube", indent=False) 1055 cube = f"{self.seg('CUBE')} {self.wrap(cube)}" if cube else "" 1056 1057 rollup = expression.args.get("rollup") 1058 if rollup is True: 1059 rollup = self.seg("WITH ROLLUP") 1060 else: 1061 rollup = self.expressions(expression, key="rollup", indent=False) 1062 rollup = f"{self.seg('ROLLUP')} {self.wrap(rollup)}" if rollup else "" 1063 1064 return f"{group_by}{csv(grouping_sets, cube, rollup, sep=',')}" 1065 1066 def having_sql(self, expression: exp.Having) -> str: 1067 this = self.indent(self.sql(expression, "this")) 1068 return f"{self.seg('HAVING')}{self.sep()}{this}" 1069 1070 def join_sql(self, expression: exp.Join) -> str: 1071 op_sql = self.seg( 1072 " ".join( 1073 op 1074 for op in ( 1075 "NATURAL" if expression.args.get("natural") else None, 1076 expression.side, 1077 expression.kind, 1078 "JOIN", 1079 ) 1080 if op 1081 ) 1082 ) 1083 on_sql = self.sql(expression, "on") 1084 using = expression.args.get("using") 1085 1086 if not on_sql and using: 1087 on_sql = csv(*(self.sql(column) for column in using)) 1088 1089 if on_sql: 1090 on_sql = self.indent(on_sql, skip_first=True) 1091 space = self.seg(" " * self.pad) if self.pretty else " " 1092 if using: 1093 on_sql = f"{space}USING ({on_sql})" 1094 else: 1095 on_sql = f"{space}ON {on_sql}" 1096 1097 expression_sql = self.sql(expression, "expression") 1098 this_sql = self.sql(expression, "this") 1099 return f"{expression_sql}{op_sql} {this_sql}{on_sql}" 1100 1101 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 1102 args = self.expressions(expression, flat=True) 1103 args = f"({args})" if len(args.split(",")) > 1 else args 1104 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 1105 1106 def lateral_sql(self, expression: exp.Lateral) -> str: 1107 this = self.sql(expression, "this") 1108 1109 if isinstance(expression.this, exp.Subquery): 1110 return f"LATERAL {this}" 1111 1112 if expression.args.get("view"): 1113 alias = expression.args["alias"] 1114 columns = self.expressions(alias, key="columns", flat=True) 1115 table = f" {alias.name}" if alias.name else "" 1116 columns = f" AS {columns}" if columns else "" 1117 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 1118 return f"{op_sql}{self.sep()}{this}{table}{columns}" 1119 1120 alias = self.sql(expression, "alias") 1121 alias = f" AS {alias}" if alias else "" 1122 return f"LATERAL {this}{alias}" 1123 1124 def limit_sql(self, expression: exp.Limit) -> str: 1125 this = self.sql(expression, "this") 1126 return f"{this}{self.seg('LIMIT')} {self.sql(expression, 'expression')}" 1127 1128 def offset_sql(self, expression: exp.Offset) -> str: 1129 this = self.sql(expression, "this") 1130 return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}" 1131 1132 def lock_sql(self, expression: exp.Lock) -> str: 1133 if self.LOCKING_READS_SUPPORTED: 1134 lock_type = "UPDATE" if expression.args["update"] else "SHARE" 1135 return self.seg(f"FOR {lock_type}") 1136 1137 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 1138 return "" 1139 1140 def literal_sql(self, expression: exp.Literal) -> str: 1141 text = expression.this or "" 1142 if expression.is_string: 1143 if self._replace_backslash: 1144 text = BACKSLASH_RE.sub(r"\\\\", text) 1145 text = text.replace(self.quote_end, self._escaped_quote_end) 1146 if self.pretty: 1147 text = text.replace("\n", self.SENTINEL_LINE_BREAK) 1148 text = f"{self.quote_start}{text}{self.quote_end}" 1149 return text 1150 1151 def loaddata_sql(self, expression: exp.LoadData) -> str: 1152 local = " LOCAL" if expression.args.get("local") else "" 1153 inpath = f" INPATH {self.sql(expression, 'inpath')}" 1154 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 1155 this = f" INTO TABLE {self.sql(expression, 'this')}" 1156 partition = self.sql(expression, "partition") 1157 partition = f" {partition}" if partition else "" 1158 input_format = self.sql(expression, "input_format") 1159 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 1160 serde = self.sql(expression, "serde") 1161 serde = f" SERDE {serde}" if serde else "" 1162 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 1163 1164 def null_sql(self, *_) -> str: 1165 return "NULL" 1166 1167 def boolean_sql(self, expression: exp.Boolean) -> str: 1168 return "TRUE" if expression.this else "FALSE" 1169 1170 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 1171 this = self.sql(expression, "this") 1172 this = f"{this} " if this else this 1173 return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat) # type: ignore 1174 1175 def cluster_sql(self, expression: exp.Cluster) -> str: 1176 return self.op_expressions("CLUSTER BY", expression) 1177 1178 def distribute_sql(self, expression: exp.Distribute) -> str: 1179 return self.op_expressions("DISTRIBUTE BY", expression) 1180 1181 def sort_sql(self, expression: exp.Sort) -> str: 1182 return self.op_expressions("SORT BY", expression) 1183 1184 def ordered_sql(self, expression: exp.Ordered) -> str: 1185 desc = expression.args.get("desc") 1186 asc = not desc 1187 1188 nulls_first = expression.args.get("nulls_first") 1189 nulls_last = not nulls_first 1190 nulls_are_large = self.null_ordering == "nulls_are_large" 1191 nulls_are_small = self.null_ordering == "nulls_are_small" 1192 nulls_are_last = self.null_ordering == "nulls_are_last" 1193 1194 sort_order = " DESC" if desc else "" 1195 nulls_sort_change = "" 1196 if nulls_first and ( 1197 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 1198 ): 1199 nulls_sort_change = " NULLS FIRST" 1200 elif ( 1201 nulls_last 1202 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 1203 and not nulls_are_last 1204 ): 1205 nulls_sort_change = " NULLS LAST" 1206 1207 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 1208 self.unsupported( 1209 "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect" 1210 ) 1211 nulls_sort_change = "" 1212 1213 return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}" 1214 1215 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 1216 partition = self.partition_by_sql(expression) 1217 order = self.sql(expression, "order") 1218 measures = self.sql(expression, "measures") 1219 measures = self.seg(f"MEASURES {measures}") if measures else "" 1220 rows = self.sql(expression, "rows") 1221 rows = self.seg(rows) if rows else "" 1222 after = self.sql(expression, "after") 1223 after = self.seg(after) if after else "" 1224 pattern = self.sql(expression, "pattern") 1225 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 1226 define = self.sql(expression, "define") 1227 define = self.seg(f"DEFINE {define}") if define else "" 1228 body = "".join( 1229 ( 1230 partition, 1231 order, 1232 measures, 1233 rows, 1234 after, 1235 pattern, 1236 define, 1237 ) 1238 ) 1239 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}" 1240 1241 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 1242 return csv( 1243 *sqls, 1244 *[self.sql(sql) for sql in expression.args.get("joins") or []], 1245 self.sql(expression, "match"), 1246 *[self.sql(sql) for sql in expression.args.get("laterals") or []], 1247 self.sql(expression, "where"), 1248 self.sql(expression, "group"), 1249 self.sql(expression, "having"), 1250 self.sql(expression, "qualify"), 1251 self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True) 1252 if expression.args.get("windows") 1253 else "", 1254 self.sql(expression, "distribute"), 1255 self.sql(expression, "sort"), 1256 self.sql(expression, "cluster"), 1257 self.sql(expression, "order"), 1258 self.sql(expression, "limit"), 1259 self.sql(expression, "offset"), 1260 self.sql(expression, "lock"), 1261 sep="", 1262 ) 1263 1264 def select_sql(self, expression: exp.Select) -> str: 1265 hint = self.sql(expression, "hint") 1266 distinct = self.sql(expression, "distinct") 1267 distinct = f" {distinct}" if distinct else "" 1268 expressions = self.expressions(expression) 1269 expressions = f"{self.sep()}{expressions}" if expressions else expressions 1270 sql = self.query_modifiers( 1271 expression, 1272 f"SELECT{hint}{distinct}{expressions}", 1273 self.sql(expression, "into", comment=False), 1274 self.sql(expression, "from", comment=False), 1275 ) 1276 return self.prepend_ctes(expression, sql) 1277 1278 def schema_sql(self, expression: exp.Schema) -> str: 1279 this = self.sql(expression, "this") 1280 this = f"{this} " if this else "" 1281 sql = f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 1282 return f"{this}{sql}" 1283 1284 def star_sql(self, expression: exp.Star) -> str: 1285 except_ = self.expressions(expression, key="except", flat=True) 1286 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else "" 1287 replace = self.expressions(expression, key="replace", flat=True) 1288 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else "" 1289 return f"*{except_}{replace}" 1290 1291 def structkwarg_sql(self, expression: exp.StructKwarg) -> str: 1292 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1293 1294 def parameter_sql(self, expression: exp.Parameter) -> str: 1295 return f"@{self.sql(expression, 'this')}" 1296 1297 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 1298 this = self.sql(expression, "this") 1299 kind = expression.text("kind") 1300 if kind: 1301 kind = f"{kind}." 1302 return f"@@{kind}{this}" 1303 1304 def placeholder_sql(self, expression: exp.Placeholder) -> str: 1305 return f":{expression.name}" if expression.name else "?" 1306 1307 def subquery_sql(self, expression: exp.Subquery) -> str: 1308 alias = self.sql(expression, "alias") 1309 1310 sql = self.query_modifiers( 1311 expression, 1312 self.wrap(expression), 1313 self.expressions(expression, key="pivots", sep=" "), 1314 f" AS {alias}" if alias else "", 1315 ) 1316 1317 return self.prepend_ctes(expression, sql) 1318 1319 def qualify_sql(self, expression: exp.Qualify) -> str: 1320 this = self.indent(self.sql(expression, "this")) 1321 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 1322 1323 def union_sql(self, expression: exp.Union) -> str: 1324 return self.prepend_ctes( 1325 expression, 1326 self.set_operation(expression, self.union_op(expression)), 1327 ) 1328 1329 def union_op(self, expression: exp.Union) -> str: 1330 kind = " DISTINCT" if self.EXPLICIT_UNION else "" 1331 kind = kind if expression.args.get("distinct") else " ALL" 1332 return f"UNION{kind}" 1333 1334 def unnest_sql(self, expression: exp.Unnest) -> str: 1335 args = self.expressions(expression, flat=True) 1336 alias = expression.args.get("alias") 1337 if alias and self.unnest_column_only: 1338 columns = alias.columns 1339 alias = self.sql(columns[0]) if columns else "" 1340 else: 1341 alias = self.sql(expression, "alias") 1342 alias = f" AS {alias}" if alias else alias 1343 ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else "" 1344 offset = expression.args.get("offset") 1345 offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else "" 1346 return f"UNNEST({args}){ordinality}{alias}{offset}" 1347 1348 def where_sql(self, expression: exp.Where) -> str: 1349 this = self.indent(self.sql(expression, "this")) 1350 return f"{self.seg('WHERE')}{self.sep()}{this}" 1351 1352 def window_sql(self, expression: exp.Window) -> str: 1353 this = self.sql(expression, "this") 1354 1355 partition = self.partition_by_sql(expression) 1356 1357 order = expression.args.get("order") 1358 order_sql = self.order_sql(order, flat=True) if order else "" 1359 1360 partition_sql = partition + " " if partition and order else partition 1361 1362 spec = expression.args.get("spec") 1363 spec_sql = " " + self.window_spec_sql(spec) if spec else "" 1364 1365 alias = self.sql(expression, "alias") 1366 this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}" 1367 1368 if not partition and not order and not spec and alias: 1369 return f"{this} {alias}" 1370 1371 window_args = alias + partition_sql + order_sql + spec_sql 1372 1373 return f"{this} ({window_args.strip()})" 1374 1375 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 1376 partition = self.expressions(expression, key="partition_by", flat=True) 1377 return f"PARTITION BY {partition}" if partition else "" 1378 1379 def window_spec_sql(self, expression: exp.WindowSpec) -> str: 1380 kind = self.sql(expression, "kind") 1381 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 1382 end = ( 1383 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 1384 or "CURRENT ROW" 1385 ) 1386 return f"{kind} BETWEEN {start} AND {end}" 1387 1388 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1389 this = self.sql(expression, "this") 1390 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 1391 return f"{this} WITHIN GROUP ({expression_sql})" 1392 1393 def between_sql(self, expression: exp.Between) -> str: 1394 this = self.sql(expression, "this") 1395 low = self.sql(expression, "low") 1396 high = self.sql(expression, "high") 1397 return f"{this} BETWEEN {low} AND {high}" 1398 1399 def bracket_sql(self, expression: exp.Bracket) -> str: 1400 expressions = apply_index_offset(expression.expressions, self.index_offset) 1401 expressions_sql = ", ".join(self.sql(e) for e in expressions) 1402 1403 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 1404 1405 def all_sql(self, expression: exp.All) -> str: 1406 return f"ALL {self.wrap(expression)}" 1407 1408 def any_sql(self, expression: exp.Any) -> str: 1409 return f"ANY {self.wrap(expression)}" 1410 1411 def exists_sql(self, expression: exp.Exists) -> str: 1412 return f"EXISTS{self.wrap(expression)}" 1413 1414 def case_sql(self, expression: exp.Case) -> str: 1415 this = self.sql(expression, "this") 1416 statements = [f"CASE {this}" if this else "CASE"] 1417 1418 for e in expression.args["ifs"]: 1419 statements.append(f"WHEN {self.sql(e, 'this')}") 1420 statements.append(f"THEN {self.sql(e, 'true')}") 1421 1422 default = self.sql(expression, "default") 1423 1424 if default: 1425 statements.append(f"ELSE {default}") 1426 1427 statements.append("END") 1428 1429 if self.pretty and self.text_width(statements) > self._max_text_width: 1430 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 1431 1432 return " ".join(statements) 1433 1434 def constraint_sql(self, expression: exp.Constraint) -> str: 1435 this = self.sql(expression, "this") 1436 expressions = self.expressions(expression, flat=True) 1437 return f"CONSTRAINT {this} {expressions}" 1438 1439 def extract_sql(self, expression: exp.Extract) -> str: 1440 this = self.sql(expression, "this") 1441 expression_sql = self.sql(expression, "expression") 1442 return f"EXTRACT({this} FROM {expression_sql})" 1443 1444 def trim_sql(self, expression: exp.Trim) -> str: 1445 trim_type = self.sql(expression, "position") 1446 1447 if trim_type == "LEADING": 1448 return f"{self.normalize_func('LTRIM')}({self.format_args(expression.this)})" 1449 elif trim_type == "TRAILING": 1450 return f"{self.normalize_func('RTRIM')}({self.format_args(expression.this)})" 1451 else: 1452 return f"{self.normalize_func('TRIM')}({self.format_args(expression.this, expression.expression)})" 1453 1454 def concat_sql(self, expression: exp.Concat) -> str: 1455 if len(expression.expressions) == 1: 1456 return self.sql(expression.expressions[0]) 1457 return self.function_fallback_sql(expression) 1458 1459 def check_sql(self, expression: exp.Check) -> str: 1460 this = self.sql(expression, key="this") 1461 return f"CHECK ({this})" 1462 1463 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 1464 expressions = self.expressions(expression, flat=True) 1465 reference = self.sql(expression, "reference") 1466 reference = f" {reference}" if reference else "" 1467 delete = self.sql(expression, "delete") 1468 delete = f" ON DELETE {delete}" if delete else "" 1469 update = self.sql(expression, "update") 1470 update = f" ON UPDATE {update}" if update else "" 1471 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 1472 1473 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 1474 expressions = self.expressions(expression, flat=True) 1475 options = self.expressions(expression, "options", flat=True, sep=" ") 1476 options = f" {options}" if options else "" 1477 return f"PRIMARY KEY ({expressions}){options}" 1478 1479 def unique_sql(self, expression: exp.Unique) -> str: 1480 columns = self.expressions(expression, key="expressions") 1481 return f"UNIQUE ({columns})" 1482 1483 def if_sql(self, expression: exp.If) -> str: 1484 return self.case_sql( 1485 exp.Case(ifs=[expression.copy()], default=expression.args.get("false")) 1486 ) 1487 1488 def in_sql(self, expression: exp.In) -> str: 1489 query = expression.args.get("query") 1490 unnest = expression.args.get("unnest") 1491 field = expression.args.get("field") 1492 is_global = " GLOBAL" if expression.args.get("is_global") else "" 1493 1494 if query: 1495 in_sql = self.wrap(query) 1496 elif unnest: 1497 in_sql = self.in_unnest_op(unnest) 1498 elif field: 1499 in_sql = self.sql(field) 1500 else: 1501 in_sql = f"({self.expressions(expression, flat=True)})" 1502 1503 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 1504 1505 def in_unnest_op(self, unnest: exp.Unnest) -> str: 1506 return f"(SELECT {self.sql(unnest)})" 1507 1508 def interval_sql(self, expression: exp.Interval) -> str: 1509 this = expression.args.get("this") 1510 if this: 1511 this = ( 1512 f" {this}" 1513 if isinstance(this, exp.Literal) or isinstance(this, exp.Paren) 1514 else f" ({this})" 1515 ) 1516 else: 1517 this = "" 1518 unit = expression.args.get("unit") 1519 unit = f" {unit}" if unit else "" 1520 return f"INTERVAL{this}{unit}" 1521 1522 def return_sql(self, expression: exp.Return) -> str: 1523 return f"RETURN {self.sql(expression, 'this')}" 1524 1525 def reference_sql(self, expression: exp.Reference) -> str: 1526 this = self.sql(expression, "this") 1527 expressions = self.expressions(expression, flat=True) 1528 expressions = f"({expressions})" if expressions else "" 1529 options = self.expressions(expression, "options", flat=True, sep=" ") 1530 options = f" {options}" if options else "" 1531 return f"REFERENCES {this}{expressions}{options}" 1532 1533 def anonymous_sql(self, expression: exp.Anonymous) -> str: 1534 args = self.format_args(*expression.expressions) 1535 return f"{self.normalize_func(self.sql(expression, 'this'))}({args})" 1536 1537 def paren_sql(self, expression: exp.Paren) -> str: 1538 if isinstance(expression.unnest(), exp.Select): 1539 sql = self.wrap(expression) 1540 else: 1541 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 1542 sql = f"({sql}{self.seg(')', sep='')}" 1543 1544 return self.prepend_ctes(expression, sql) 1545 1546 def neg_sql(self, expression: exp.Neg) -> str: 1547 # This makes sure we don't convert "- - 5" to "--5", which is a comment 1548 this_sql = self.sql(expression, "this") 1549 sep = " " if this_sql[0] == "-" else "" 1550 return f"-{sep}{this_sql}" 1551 1552 def not_sql(self, expression: exp.Not) -> str: 1553 return f"NOT {self.sql(expression, 'this')}" 1554 1555 def alias_sql(self, expression: exp.Alias) -> str: 1556 to_sql = self.sql(expression, "alias") 1557 to_sql = f" AS {to_sql}" if to_sql else "" 1558 return f"{self.sql(expression, 'this')}{to_sql}" 1559 1560 def aliases_sql(self, expression: exp.Aliases) -> str: 1561 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 1562 1563 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 1564 this = self.sql(expression, "this") 1565 zone = self.sql(expression, "zone") 1566 return f"{this} AT TIME ZONE {zone}" 1567 1568 def add_sql(self, expression: exp.Add) -> str: 1569 return self.binary(expression, "+") 1570 1571 def and_sql(self, expression: exp.And) -> str: 1572 return self.connector_sql(expression, "AND") 1573 1574 def connector_sql(self, expression: exp.Connector, op: str) -> str: 1575 if not self.pretty: 1576 return self.binary(expression, op) 1577 1578 sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False)) 1579 sep = "\n" if self.text_width(sqls) > self._max_text_width else " " 1580 return f"{sep}{op} ".join(sqls) 1581 1582 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 1583 return self.binary(expression, "&") 1584 1585 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 1586 return self.binary(expression, "<<") 1587 1588 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 1589 return f"~{self.sql(expression, 'this')}" 1590 1591 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 1592 return self.binary(expression, "|") 1593 1594 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 1595 return self.binary(expression, ">>") 1596 1597 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 1598 return self.binary(expression, "^") 1599 1600 def cast_sql(self, expression: exp.Cast) -> str: 1601 return f"CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})" 1602 1603 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1604 zone = self.sql(expression, "this") 1605 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 1606 1607 def collate_sql(self, expression: exp.Collate) -> str: 1608 return self.binary(expression, "COLLATE") 1609 1610 def command_sql(self, expression: exp.Command) -> str: 1611 return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}" 1612 1613 def transaction_sql(self, *_) -> str: 1614 return "BEGIN" 1615 1616 def commit_sql(self, expression: exp.Commit) -> str: 1617 chain = expression.args.get("chain") 1618 if chain is not None: 1619 chain = " AND CHAIN" if chain else " AND NO CHAIN" 1620 1621 return f"COMMIT{chain or ''}" 1622 1623 def rollback_sql(self, expression: exp.Rollback) -> str: 1624 savepoint = expression.args.get("savepoint") 1625 savepoint = f" TO {savepoint}" if savepoint else "" 1626 return f"ROLLBACK{savepoint}" 1627 1628 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 1629 this = self.sql(expression, "this") 1630 1631 dtype = self.sql(expression, "dtype") 1632 if dtype: 1633 collate = self.sql(expression, "collate") 1634 collate = f" COLLATE {collate}" if collate else "" 1635 using = self.sql(expression, "using") 1636 using = f" USING {using}" if using else "" 1637 return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}" 1638 1639 default = self.sql(expression, "default") 1640 if default: 1641 return f"ALTER COLUMN {this} SET DEFAULT {default}" 1642 1643 if not expression.args.get("drop"): 1644 self.unsupported("Unsupported ALTER COLUMN syntax") 1645 1646 return f"ALTER COLUMN {this} DROP DEFAULT" 1647 1648 def renametable_sql(self, expression: exp.RenameTable) -> str: 1649 this = self.sql(expression, "this") 1650 return f"RENAME TO {this}" 1651 1652 def altertable_sql(self, expression: exp.AlterTable) -> str: 1653 actions = expression.args["actions"] 1654 1655 if isinstance(actions[0], exp.ColumnDef): 1656 actions = self.expressions(expression, "actions", prefix="ADD COLUMN ") 1657 elif isinstance(actions[0], exp.Schema): 1658 actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ") 1659 elif isinstance(actions[0], exp.Delete): 1660 actions = self.expressions(expression, "actions", flat=True) 1661 else: 1662 actions = self.expressions(expression, "actions") 1663 1664 exists = " IF EXISTS" if expression.args.get("exists") else "" 1665 return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}" 1666 1667 def droppartition_sql(self, expression: exp.DropPartition) -> str: 1668 expressions = self.expressions(expression) 1669 exists = " IF EXISTS " if expression.args.get("exists") else " " 1670 return f"DROP{exists}{expressions}" 1671 1672 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 1673 this = self.sql(expression, "this") 1674 expression_ = self.sql(expression, "expression") 1675 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD" 1676 1677 enforced = expression.args.get("enforced") 1678 if enforced is not None: 1679 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}" 1680 1681 return f"{add_constraint} {expression_}" 1682 1683 def distinct_sql(self, expression: exp.Distinct) -> str: 1684 this = self.expressions(expression, flat=True) 1685 this = f" {this}" if this else "" 1686 1687 on = self.sql(expression, "on") 1688 on = f" ON {on}" if on else "" 1689 return f"DISTINCT{this}{on}" 1690 1691 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 1692 return f"{self.sql(expression, 'this')} IGNORE NULLS" 1693 1694 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 1695 return f"{self.sql(expression, 'this')} RESPECT NULLS" 1696 1697 def intdiv_sql(self, expression: exp.IntDiv) -> str: 1698 return self.sql( 1699 exp.Cast( 1700 this=exp.Div(this=expression.this, expression=expression.expression), 1701 to=exp.DataType(this=exp.DataType.Type.INT), 1702 ) 1703 ) 1704 1705 def dpipe_sql(self, expression: exp.DPipe) -> str: 1706 return self.binary(expression, "||") 1707 1708 def div_sql(self, expression: exp.Div) -> str: 1709 return self.binary(expression, "/") 1710 1711 def distance_sql(self, expression: exp.Distance) -> str: 1712 return self.binary(expression, "<->") 1713 1714 def dot_sql(self, expression: exp.Dot) -> str: 1715 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 1716 1717 def eq_sql(self, expression: exp.EQ) -> str: 1718 return self.binary(expression, "=") 1719 1720 def escape_sql(self, expression: exp.Escape) -> str: 1721 return self.binary(expression, "ESCAPE") 1722 1723 def glob_sql(self, expression: exp.Glob) -> str: 1724 return self.binary(expression, "GLOB") 1725 1726 def gt_sql(self, expression: exp.GT) -> str: 1727 return self.binary(expression, ">") 1728 1729 def gte_sql(self, expression: exp.GTE) -> str: 1730 return self.binary(expression, ">=") 1731 1732 def ilike_sql(self, expression: exp.ILike) -> str: 1733 return self.binary(expression, "ILIKE") 1734 1735 def is_sql(self, expression: exp.Is) -> str: 1736 return self.binary(expression, "IS") 1737 1738 def like_sql(self, expression: exp.Like) -> str: 1739 return self.binary(expression, "LIKE") 1740 1741 def similarto_sql(self, expression: exp.SimilarTo) -> str: 1742 return self.binary(expression, "SIMILAR TO") 1743 1744 def lt_sql(self, expression: exp.LT) -> str: 1745 return self.binary(expression, "<") 1746 1747 def lte_sql(self, expression: exp.LTE) -> str: 1748 return self.binary(expression, "<=") 1749 1750 def mod_sql(self, expression: exp.Mod) -> str: 1751 return self.binary(expression, "%") 1752 1753 def mul_sql(self, expression: exp.Mul) -> str: 1754 return self.binary(expression, "*") 1755 1756 def neq_sql(self, expression: exp.NEQ) -> str: 1757 return self.binary(expression, "<>") 1758 1759 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 1760 return self.binary(expression, "IS NOT DISTINCT FROM") 1761 1762 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 1763 return self.binary(expression, "IS DISTINCT FROM") 1764 1765 def or_sql(self, expression: exp.Or) -> str: 1766 return self.connector_sql(expression, "OR") 1767 1768 def slice_sql(self, expression: exp.Slice) -> str: 1769 return self.binary(expression, ":") 1770 1771 def sub_sql(self, expression: exp.Sub) -> str: 1772 return self.binary(expression, "-") 1773 1774 def trycast_sql(self, expression: exp.TryCast) -> str: 1775 return f"TRY_CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})" 1776 1777 def use_sql(self, expression: exp.Use) -> str: 1778 kind = self.sql(expression, "kind") 1779 kind = f" {kind}" if kind else "" 1780 this = self.sql(expression, "this") 1781 this = f" {this}" if this else "" 1782 return f"USE{kind}{this}" 1783 1784 def binary(self, expression: exp.Binary, op: str) -> str: 1785 return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}" 1786 1787 def function_fallback_sql(self, expression: exp.Func) -> str: 1788 args = [] 1789 for arg_value in expression.args.values(): 1790 if isinstance(arg_value, list): 1791 for value in arg_value: 1792 args.append(value) 1793 else: 1794 args.append(arg_value) 1795 1796 return f"{self.normalize_func(expression.sql_name())}({self.format_args(*args)})" 1797 1798 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 1799 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None) 1800 if self.pretty and self.text_width(arg_sqls) > self._max_text_width: 1801 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 1802 return ", ".join(arg_sqls) 1803 1804 def text_width(self, args: t.Iterable) -> int: 1805 return sum(len(arg) for arg in args) 1806 1807 def format_time(self, expression: exp.Expression) -> t.Optional[str]: 1808 return format_time(self.sql(expression, "format"), self.time_mapping, self.time_trie) 1809 1810 def expressions( 1811 self, 1812 expression: exp.Expression, 1813 key: t.Optional[str] = None, 1814 flat: bool = False, 1815 indent: bool = True, 1816 sep: str = ", ", 1817 prefix: str = "", 1818 ) -> str: 1819 expressions = expression.args.get(key or "expressions") 1820 1821 if not expressions: 1822 return "" 1823 1824 if flat: 1825 return sep.join(self.sql(e) for e in expressions) 1826 1827 num_sqls = len(expressions) 1828 1829 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly 1830 pad = " " * self.pad 1831 stripped_sep = sep.strip() 1832 1833 result_sqls = [] 1834 for i, e in enumerate(expressions): 1835 sql = self.sql(e, comment=False) 1836 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 1837 1838 if self.pretty: 1839 if self._leading_comma: 1840 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}") 1841 else: 1842 result_sqls.append( 1843 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}" 1844 ) 1845 else: 1846 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 1847 1848 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls) 1849 return self.indent(result_sql, skip_first=False) if indent else result_sql 1850 1851 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 1852 expressions_sql = self.expressions(expression, flat=flat) 1853 if flat: 1854 return f"{op} {expressions_sql}" 1855 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 1856 1857 def naked_property(self, expression: exp.Property) -> str: 1858 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 1859 if not property_name: 1860 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 1861 return f"{property_name} {self.sql(expression, 'this')}" 1862 1863 def set_operation(self, expression: exp.Expression, op: str) -> str: 1864 this = self.sql(expression, "this") 1865 op = self.seg(op) 1866 return self.query_modifiers( 1867 expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}" 1868 ) 1869 1870 def tag_sql(self, expression: exp.Tag) -> str: 1871 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 1872 1873 def token_sql(self, token_type: TokenType) -> str: 1874 return self.TOKEN_MAPPING.get(token_type, token_type.name) 1875 1876 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 1877 this = self.sql(expression, "this") 1878 expressions = self.no_identify(self.expressions, expression) 1879 expressions = ( 1880 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 1881 ) 1882 return f"{this}{expressions}" 1883 1884 def userdefinedfunctionkwarg_sql(self, expression: exp.UserDefinedFunctionKwarg) -> str: 1885 this = self.sql(expression, "this") 1886 kind = self.sql(expression, "kind") 1887 return f"{this} {kind}" 1888 1889 def joinhint_sql(self, expression: exp.JoinHint) -> str: 1890 this = self.sql(expression, "this") 1891 expressions = self.expressions(expression, flat=True) 1892 return f"{this}({expressions})" 1893 1894 def kwarg_sql(self, expression: exp.Kwarg) -> str: 1895 return self.binary(expression, "=>") 1896 1897 def when_sql(self, expression: exp.When) -> str: 1898 this = self.sql(expression, "this") 1899 then_expression = expression.args.get("then") 1900 if isinstance(then_expression, exp.Insert): 1901 then = f"INSERT {self.sql(then_expression, 'this')}" 1902 if "expression" in then_expression.args: 1903 then += f" VALUES {self.sql(then_expression, 'expression')}" 1904 elif isinstance(then_expression, exp.Update): 1905 if isinstance(then_expression.args.get("expressions"), exp.Star): 1906 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 1907 else: 1908 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 1909 else: 1910 then = self.sql(then_expression) 1911 return f"WHEN {this} THEN {then}" 1912 1913 def merge_sql(self, expression: exp.Merge) -> str: 1914 this = self.sql(expression, "this") 1915 using = f"USING {self.sql(expression, 'using')}" 1916 on = f"ON {self.sql(expression, 'on')}" 1917 return f"MERGE INTO {this} {using} {on} {self.expressions(expression, sep=' ')}"
Generator interprets the given syntax tree and produces a SQL string as an output.
Arguments:
- time_mapping (dict): the dictionary of custom time mappings in which the key represents a python time format and the output the target time format
- time_trie (trie): a trie of the time_mapping keys
- pretty (bool): if set to True the returned string will be formatted. Default: False.
- quote_start (str): specifies which starting character to use to delimit quotes. Default: '.
- quote_end (str): specifies which ending character to use to delimit quotes. Default: '.
- identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ".
- identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ".
- identify (bool): if set to True all identifiers will be delimited by the corresponding character.
- normalize (bool): if set to True all identifiers will lower cased
- string_escape (str): specifies a string escape character. Default: '.
- identifier_escape (str): specifies an identifier escape character. Default: ".
- pad (int): determines padding in a formatted string. Default: 2.
- indent (int): determines the size of indentation in a formatted string. Default: 4.
- unnest_column_only (bool): if true unnest table aliases are considered only as column aliases
- normalize_functions (str): normalize function names, "upper", "lower", or None Default: "upper"
- alias_post_tablesample (bool): if the table alias comes after tablesample Default: False
- unsupported_level (ErrorLevel): determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- null_ordering (str): Indicates the default null ordering method to use if not explicitly set. Options are "nulls_are_small", "nulls_are_large", "nulls_are_last". Default: "nulls_are_small"
- max_unsupported (int): 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 (bool): if the the comma is leading or trailing in select statements 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 or not to preserve comments in the output SQL code. Default: True
Generator( time_mapping=None, time_trie=None, pretty=None, quote_start=None, quote_end=None, identifier_start=None, identifier_end=None, identify=False, normalize=False, string_escape=None, identifier_escape=None, pad=2, indent=2, index_offset=0, unnest_column_only=False, alias_post_tablesample=False, normalize_functions='upper', unsupported_level=<ErrorLevel.WARN: 'WARN'>, null_ordering=None, max_unsupported=3, leading_comma=False, max_text_width=80, comments=True)
185 def __init__( 186 self, 187 time_mapping=None, 188 time_trie=None, 189 pretty=None, 190 quote_start=None, 191 quote_end=None, 192 identifier_start=None, 193 identifier_end=None, 194 identify=False, 195 normalize=False, 196 string_escape=None, 197 identifier_escape=None, 198 pad=2, 199 indent=2, 200 index_offset=0, 201 unnest_column_only=False, 202 alias_post_tablesample=False, 203 normalize_functions="upper", 204 unsupported_level=ErrorLevel.WARN, 205 null_ordering=None, 206 max_unsupported=3, 207 leading_comma=False, 208 max_text_width=80, 209 comments=True, 210 ): 211 import sqlglot 212 213 self.time_mapping = time_mapping or {} 214 self.time_trie = time_trie 215 self.pretty = pretty if pretty is not None else sqlglot.pretty 216 self.quote_start = quote_start or "'" 217 self.quote_end = quote_end or "'" 218 self.identifier_start = identifier_start or '"' 219 self.identifier_end = identifier_end or '"' 220 self.identify = identify 221 self.normalize = normalize 222 self.string_escape = string_escape or "'" 223 self.identifier_escape = identifier_escape or '"' 224 self.pad = pad 225 self.index_offset = index_offset 226 self.unnest_column_only = unnest_column_only 227 self.alias_post_tablesample = alias_post_tablesample 228 self.normalize_functions = normalize_functions 229 self.unsupported_level = unsupported_level 230 self.unsupported_messages = [] 231 self.max_unsupported = max_unsupported 232 self.null_ordering = null_ordering 233 self._indent = indent 234 self._replace_backslash = self.string_escape == "\\" 235 self._escaped_quote_end = self.string_escape + self.quote_end 236 self._escaped_identifier_end = self.identifier_escape + self.identifier_end 237 self._leading_comma = leading_comma 238 self._max_text_width = max_text_width 239 self._comments = comments
241 def generate(self, expression: t.Optional[exp.Expression]) -> str: 242 """ 243 Generates a SQL string by interpreting the given syntax tree. 244 245 Args 246 expression: the syntax tree. 247 248 Returns 249 the SQL string. 250 """ 251 self.unsupported_messages = [] 252 sql = self.sql(expression).strip() 253 254 if self.unsupported_level == ErrorLevel.IGNORE: 255 return sql 256 257 if self.unsupported_level == ErrorLevel.WARN: 258 for msg in self.unsupported_messages: 259 logger.warning(msg) 260 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 261 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 262 263 if self.pretty: 264 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 265 return sql
Generates a SQL string by interpreting the given syntax tree.
Args expression: the syntax tree.
Returns the SQL string.
283 def maybe_comment(self, sql: str, expression: exp.Expression) -> str: 284 comments = expression.comments if self._comments else None 285 286 if not comments: 287 return sql 288 289 sep = "\n" if self.pretty else " " 290 comments_sql = sep.join( 291 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 292 ) 293 294 if not comments_sql: 295 return sql 296 297 if isinstance(expression, self.WITH_SEPARATED_COMMENTS): 298 return f"{comments_sql}{self.sep()}{sql}" 299 300 return f"{sql} {comments_sql}"
302 def wrap(self, expression: exp.Expression | str) -> str: 303 this_sql = self.indent( 304 self.sql(expression) 305 if isinstance(expression, (exp.Select, exp.Union)) 306 else self.sql(expression, "this"), 307 level=1, 308 pad=0, 309 ) 310 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
326 def indent( 327 self, 328 sql: str, 329 level: int = 0, 330 pad: t.Optional[int] = None, 331 skip_first: bool = False, 332 skip_last: bool = False, 333 ) -> str: 334 if not self.pretty: 335 return sql 336 337 pad = self.pad if pad is None else pad 338 lines = sql.split("\n") 339 340 return "\n".join( 341 line 342 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 343 else f"{' ' * (level * self._indent + pad)}{line}" 344 for i, line in enumerate(lines) 345 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
347 def sql( 348 self, 349 expression: t.Optional[str | exp.Expression], 350 key: t.Optional[str] = None, 351 comment: bool = True, 352 ) -> str: 353 if not expression: 354 return "" 355 356 if isinstance(expression, str): 357 return expression 358 359 if key: 360 return self.sql(expression.args.get(key)) 361 362 transform = self.TRANSFORMS.get(expression.__class__) 363 364 if callable(transform): 365 sql = transform(self, expression) 366 elif transform: 367 sql = transform 368 elif isinstance(expression, exp.Expression): 369 exp_handler_name = f"{expression.key}_sql" 370 371 if hasattr(self, exp_handler_name): 372 sql = getattr(self, exp_handler_name)(expression) 373 elif isinstance(expression, exp.Func): 374 sql = self.function_fallback_sql(expression) 375 elif isinstance(expression, exp.Property): 376 sql = self.property_sql(expression) 377 else: 378 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 379 else: 380 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 381 382 return self.maybe_comment(sql, expression) if self._comments and comment else sql
389 def cache_sql(self, expression: exp.Cache) -> str: 390 lazy = " LAZY" if expression.args.get("lazy") else "" 391 table = self.sql(expression, "this") 392 options = expression.args.get("options") 393 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 394 sql = self.sql(expression, "expression") 395 sql = f" AS{self.sep()}{sql}" if sql else "" 396 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 397 return self.prepend_ctes(expression, sql)
399 def characterset_sql(self, expression: exp.CharacterSet) -> str: 400 if isinstance(expression.parent, exp.Cast): 401 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 402 default = "DEFAULT " if expression.args.get("default") else "" 403 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
416 def columndef_sql(self, expression: exp.ColumnDef) -> str: 417 column = self.sql(expression, "this") 418 kind = self.sql(expression, "kind") 419 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 420 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 421 kind = f" {kind}" if kind else "" 422 constraints = f" {constraints}" if constraints else "" 423 424 return f"{exists}{column}{kind}{constraints}"
def
commentcolumnconstraint_sql(self, expression: sqlglot.expressions.CommentColumnConstraint) -> str:
def
collatecolumnconstraint_sql(self, expression: sqlglot.expressions.CollateColumnConstraint) -> str:
def
encodecolumnconstraint_sql(self, expression: sqlglot.expressions.EncodeColumnConstraint) -> str:
def
defaultcolumnconstraint_sql(self, expression: sqlglot.expressions.DefaultColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
454 def generatedasidentitycolumnconstraint_sql( 455 self, expression: exp.GeneratedAsIdentityColumnConstraint 456 ) -> str: 457 this = "" 458 if expression.this is not None: 459 this = " ALWAYS " if expression.this else " BY DEFAULT " 460 start = expression.args.get("start") 461 start = f"START WITH {start}" if start else "" 462 increment = expression.args.get("increment") 463 increment = f"INCREMENT BY {increment}" if increment else "" 464 sequence_opts = "" 465 if start or increment: 466 sequence_opts = f"{start} {increment}" 467 sequence_opts = f" ({sequence_opts.strip()})" 468 return f"GENERATED{this}AS IDENTITY{sequence_opts}"
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
482 def create_sql(self, expression: exp.Create) -> str: 483 kind = self.sql(expression, "kind").upper() 484 properties = expression.args.get("properties") 485 properties_exp = expression.copy() 486 properties_locs = self.locate_properties(properties) if properties else {} 487 if properties_locs.get(exp.Properties.Location.POST_SCHEMA_ROOT) or properties_locs.get( 488 exp.Properties.Location.POST_SCHEMA_WITH 489 ): 490 properties_exp.set( 491 "properties", 492 exp.Properties( 493 expressions=[ 494 *properties_locs[exp.Properties.Location.POST_SCHEMA_ROOT], 495 *properties_locs[exp.Properties.Location.POST_SCHEMA_WITH], 496 ] 497 ), 498 ) 499 if kind == "TABLE" and properties_locs.get(exp.Properties.Location.PRE_SCHEMA): 500 this_name = self.sql(expression.this, "this") 501 this_properties = self.properties( 502 exp.Properties(expressions=properties_locs[exp.Properties.Location.PRE_SCHEMA]), 503 wrapped=False, 504 ) 505 this_schema = f"({self.expressions(expression.this)})" 506 this = f"{this_name}, {this_properties} {this_schema}" 507 properties_sql = "" 508 else: 509 this = self.sql(expression, "this") 510 properties_sql = self.sql(properties_exp, "properties") 511 begin = " BEGIN" if expression.args.get("begin") else "" 512 expression_sql = self.sql(expression, "expression") 513 if expression_sql: 514 expression_sql = f"{begin}{self.sep()}{expression_sql}" 515 516 if self.CREATE_FUNCTION_AS or kind != "FUNCTION": 517 expression_sql = f" AS{expression_sql}" 518 519 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 520 transient = ( 521 " TRANSIENT" if self.CREATE_TRANSIENT and expression.args.get("transient") else "" 522 ) 523 external = " EXTERNAL" if expression.args.get("external") else "" 524 replace = " OR REPLACE" if expression.args.get("replace") else "" 525 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 526 unique = " UNIQUE" if expression.args.get("unique") else "" 527 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 528 set_ = " SET" if expression.args.get("set") else "" 529 multiset = " MULTISET" if expression.args.get("multiset") else "" 530 global_temporary = " GLOBAL TEMPORARY" if expression.args.get("global_temporary") else "" 531 volatile = " VOLATILE" if expression.args.get("volatile") else "" 532 data = expression.args.get("data") 533 if data is None: 534 data = "" 535 elif data: 536 data = " WITH DATA" 537 else: 538 data = " WITH NO DATA" 539 statistics = expression.args.get("statistics") 540 if statistics is None: 541 statistics = "" 542 elif statistics: 543 statistics = " AND STATISTICS" 544 else: 545 statistics = " AND NO STATISTICS" 546 no_primary_index = " NO PRIMARY INDEX" if expression.args.get("no_primary_index") else "" 547 548 indexes = expression.args.get("indexes") 549 index_sql = "" 550 if indexes: 551 indexes_sql = [] 552 for index in indexes: 553 ind_unique = " UNIQUE" if index.args.get("unique") else "" 554 ind_primary = " PRIMARY" if index.args.get("primary") else "" 555 ind_amp = " AMP" if index.args.get("amp") else "" 556 ind_name = f" {index.name}" if index.name else "" 557 ind_columns = ( 558 f' ({self.expressions(index, key="columns", flat=True)})' 559 if index.args.get("columns") 560 else "" 561 ) 562 if index.args.get("primary") and properties_locs.get( 563 exp.Properties.Location.POST_INDEX 564 ): 565 postindex_props_sql = self.properties( 566 exp.Properties( 567 expressions=properties_locs[exp.Properties.Location.POST_INDEX] 568 ), 569 wrapped=False, 570 ) 571 ind_columns = f"{ind_columns} {postindex_props_sql}" 572 573 indexes_sql.append( 574 f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}" 575 ) 576 index_sql = "".join(indexes_sql) 577 578 postcreate_props_sql = "" 579 if properties_locs.get(exp.Properties.Location.POST_CREATE): 580 postcreate_props_sql = self.properties( 581 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 582 sep=" ", 583 prefix=" ", 584 wrapped=False, 585 ) 586 587 modifiers = "".join( 588 ( 589 replace, 590 temporary, 591 transient, 592 external, 593 unique, 594 materialized, 595 set_, 596 multiset, 597 global_temporary, 598 volatile, 599 postcreate_props_sql, 600 ) 601 ) 602 no_schema_binding = ( 603 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 604 ) 605 606 post_expression_modifiers = "".join((data, statistics, no_primary_index)) 607 608 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{post_expression_modifiers}{index_sql}{no_schema_binding}" 609 return self.prepend_ctes(expression, expression_sql)
642 def datatype_sql(self, expression: exp.DataType) -> str: 643 type_value = expression.this 644 type_sql = self.TYPE_MAPPING.get(type_value, type_value.value) 645 nested = "" 646 interior = self.expressions(expression, flat=True) 647 values = "" 648 if interior: 649 if expression.args.get("nested"): 650 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 651 if expression.args.get("values") is not None: 652 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 653 values = ( 654 f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}" 655 ) 656 else: 657 nested = f"({interior})" 658 659 return f"{type_sql}{nested}{values}"
661 def directory_sql(self, expression: exp.Directory) -> str: 662 local = "LOCAL " if expression.args.get("local") else "" 663 row_format = self.sql(expression, "row_format") 664 row_format = f" {row_format}" if row_format else "" 665 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
667 def delete_sql(self, expression: exp.Delete) -> str: 668 this = self.sql(expression, "this") 669 this = f" FROM {this}" if this else "" 670 using_sql = ( 671 f" USING {self.expressions(expression, 'using', sep=', USING ')}" 672 if expression.args.get("using") 673 else "" 674 ) 675 where_sql = self.sql(expression, "where") 676 sql = f"DELETE{this}{using_sql}{where_sql}" 677 return self.prepend_ctes(expression, sql)
679 def drop_sql(self, expression: exp.Drop) -> str: 680 this = self.sql(expression, "this") 681 kind = expression.args["kind"] 682 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 683 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 684 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 685 cascade = " CASCADE" if expression.args.get("cascade") else "" 686 return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}"
697 def fetch_sql(self, expression: exp.Fetch) -> str: 698 direction = expression.args.get("direction") 699 direction = f" {direction.upper()}" if direction else "" 700 count = expression.args.get("count") 701 count = f" {count}" if count else "" 702 return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY"
720 def identifier_sql(self, expression: exp.Identifier) -> str: 721 text = expression.name 722 text = text.lower() if self.normalize else text 723 text = text.replace(self.identifier_end, self._escaped_identifier_end) 724 if expression.args.get("quoted") or self.identify: 725 text = f"{self.identifier_start}{text}{self.identifier_end}" 726 return text
734 def properties_sql(self, expression: exp.Properties) -> str: 735 root_properties = [] 736 with_properties = [] 737 738 for p in expression.expressions: 739 p_loc = self.PROPERTIES_LOCATION[p.__class__] 740 if p_loc == exp.Properties.Location.POST_SCHEMA_WITH: 741 with_properties.append(p) 742 elif p_loc == exp.Properties.Location.POST_SCHEMA_ROOT: 743 root_properties.append(p) 744 745 return self.root_properties( 746 exp.Properties(expressions=root_properties) 747 ) + self.with_properties(exp.Properties(expressions=with_properties))
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
754 def properties( 755 self, 756 properties: exp.Properties, 757 prefix: str = "", 758 sep: str = ", ", 759 suffix: str = "", 760 wrapped: bool = True, 761 ) -> str: 762 if properties.expressions: 763 expressions = self.expressions(properties, sep=sep, indent=False) 764 expressions = self.wrap(expressions) if wrapped else expressions 765 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}" 766 return ""
def
locate_properties( self, properties: sqlglot.expressions.Properties) -> Dict[sqlglot.expressions.Properties.Location, list[sqlglot.expressions.Property]]:
771 def locate_properties( 772 self, properties: exp.Properties 773 ) -> t.Dict[exp.Properties.Location, list[exp.Property]]: 774 properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = { 775 key: [] for key in exp.Properties.Location 776 } 777 778 for p in properties.expressions: 779 p_loc = self.PROPERTIES_LOCATION[p.__class__] 780 if p_loc == exp.Properties.Location.PRE_SCHEMA: 781 properties_locs[exp.Properties.Location.PRE_SCHEMA].append(p) 782 elif p_loc == exp.Properties.Location.POST_INDEX: 783 properties_locs[exp.Properties.Location.POST_INDEX].append(p) 784 elif p_loc == exp.Properties.Location.POST_SCHEMA_ROOT: 785 properties_locs[exp.Properties.Location.POST_SCHEMA_ROOT].append(p) 786 elif p_loc == exp.Properties.Location.POST_SCHEMA_WITH: 787 properties_locs[exp.Properties.Location.POST_SCHEMA_WITH].append(p) 788 elif p_loc == exp.Properties.Location.POST_CREATE: 789 properties_locs[exp.Properties.Location.POST_CREATE].append(p) 790 elif p_loc == exp.Properties.Location.UNSUPPORTED: 791 self.unsupported(f"Unsupported property {p.key}") 792 793 return properties_locs
795 def property_sql(self, expression: exp.Property) -> str: 796 property_cls = expression.__class__ 797 if property_cls == exp.Property: 798 return f"{expression.name}={self.sql(expression, 'value')}" 799 800 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 801 if not property_name: 802 self.unsupported(f"Unsupported property {expression.key}") 803 804 return f"{property_name}={self.sql(expression, 'this')}"
827 def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str: 828 no = "NO " if expression.args.get("no") else "" 829 dual = "DUAL " if expression.args.get("dual") else "" 830 local = "" 831 if expression.args.get("local") is not None: 832 local = "LOCAL " if expression.args.get("local") else "NOT LOCAL " 833 return f"{no}{dual}{local}AFTER JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
844 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 845 if expression.args.get("no"): 846 return "NO MERGEBLOCKRATIO" 847 if expression.args.get("default"): 848 return "DEFAULT MERGEBLOCKRATIO" 849 850 percent = " PERCENT" if expression.args.get("percent") else "" 851 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
853 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 854 default = expression.args.get("default") 855 min = expression.args.get("min") 856 if default is not None or min is not None: 857 if default: 858 property = "DEFAULT" 859 elif min: 860 property = "MINIMUM" 861 else: 862 property = "MAXIMUM" 863 return f"{property} DATABLOCKSIZE" 864 else: 865 units = expression.args.get("units") 866 units = f" {units}" if units else "" 867 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
869 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 870 autotemp = expression.args.get("autotemp") 871 always = expression.args.get("always") 872 default = expression.args.get("default") 873 manual = expression.args.get("manual") 874 never = expression.args.get("never") 875 876 if autotemp is not None: 877 property = f"AUTOTEMP({self.expressions(autotemp)})" 878 elif always: 879 property = "ALWAYS" 880 elif default: 881 property = "DEFAULT" 882 elif manual: 883 property = "MANUAL" 884 elif never: 885 property = "NEVER" 886 return f"BLOCKCOMPRESSION={property}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
888 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 889 no = expression.args.get("no") 890 no = " NO" if no else "" 891 concurrent = expression.args.get("concurrent") 892 concurrent = " CONCURRENT" if concurrent else "" 893 894 for_ = "" 895 if expression.args.get("for_all"): 896 for_ = " FOR ALL" 897 elif expression.args.get("for_insert"): 898 for_ = " FOR INSERT" 899 elif expression.args.get("for_none"): 900 for_ = " FOR NONE" 901 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"
903 def insert_sql(self, expression: exp.Insert) -> str: 904 overwrite = expression.args.get("overwrite") 905 906 if isinstance(expression.this, exp.Directory): 907 this = "OVERWRITE " if overwrite else "INTO " 908 else: 909 this = "OVERWRITE TABLE " if overwrite else "INTO " 910 911 this = f"{this}{self.sql(expression, 'this')}" 912 exists = " IF EXISTS " if expression.args.get("exists") else " " 913 partition_sql = ( 914 self.sql(expression, "partition") if expression.args.get("partition") else "" 915 ) 916 expression_sql = self.sql(expression, "expression") 917 sep = self.sep() if partition_sql else "" 918 sql = f"INSERT {this}{exists}{partition_sql}{sep}{expression_sql}" 919 return self.prepend_ctes(expression, sql)
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
936 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 937 fields = expression.args.get("fields") 938 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 939 escaped = expression.args.get("escaped") 940 escaped = f" ESCAPED BY {escaped}" if escaped else "" 941 items = expression.args.get("collection_items") 942 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 943 keys = expression.args.get("map_keys") 944 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 945 lines = expression.args.get("lines") 946 lines = f" LINES TERMINATED BY {lines}" if lines else "" 947 null = expression.args.get("null") 948 null = f" NULL DEFINED AS {null}" if null else "" 949 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
951 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 952 table = ".".join( 953 part 954 for part in [ 955 self.sql(expression, "catalog"), 956 self.sql(expression, "db"), 957 self.sql(expression, "this"), 958 ] 959 if part 960 ) 961 962 alias = self.sql(expression, "alias") 963 alias = f"{sep}{alias}" if alias else "" 964 hints = self.expressions(expression, key="hints", sep=", ", flat=True) 965 hints = f" WITH ({hints})" if hints else "" 966 laterals = self.expressions(expression, key="laterals", sep="") 967 joins = self.expressions(expression, key="joins", sep="") 968 pivots = self.expressions(expression, key="pivots", sep="") 969 system_time = expression.args.get("system_time") 970 system_time = f" {self.sql(expression, 'system_time')}" if system_time else "" 971 972 if alias and pivots: 973 pivots = f"{pivots}{alias}" 974 alias = "" 975 976 return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}"
978 def tablesample_sql(self, expression: exp.TableSample) -> str: 979 if self.alias_post_tablesample and expression.this.alias: 980 this = self.sql(expression.this, "this") 981 alias = f" AS {self.sql(expression.this, 'alias')}" 982 else: 983 this = self.sql(expression, "this") 984 alias = "" 985 method = self.sql(expression, "method") 986 method = f" {method.upper()} " if method else "" 987 numerator = self.sql(expression, "bucket_numerator") 988 denominator = self.sql(expression, "bucket_denominator") 989 field = self.sql(expression, "bucket_field") 990 field = f" ON {field}" if field else "" 991 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 992 percent = self.sql(expression, "percent") 993 percent = f"{percent} PERCENT" if percent else "" 994 rows = self.sql(expression, "rows") 995 rows = f"{rows} ROWS" if rows else "" 996 size = self.sql(expression, "size") 997 seed = self.sql(expression, "seed") 998 seed = f" SEED ({seed})" if seed else "" 999 return f"{this} TABLESAMPLE{method}({bucket}{percent}{rows}{size}){seed}{alias}"
1001 def pivot_sql(self, expression: exp.Pivot) -> str: 1002 this = self.sql(expression, "this") 1003 unpivot = expression.args.get("unpivot") 1004 direction = "UNPIVOT" if unpivot else "PIVOT" 1005 expressions = self.expressions(expression, key="expressions") 1006 field = self.sql(expression, "field") 1007 return f"{this} {direction}({expressions} FOR {field})"
1012 def update_sql(self, expression: exp.Update) -> str: 1013 this = self.sql(expression, "this") 1014 set_sql = self.expressions(expression, flat=True) 1015 from_sql = self.sql(expression, "from") 1016 where_sql = self.sql(expression, "where") 1017 sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}" 1018 return self.prepend_ctes(expression, sql)
1020 def values_sql(self, expression: exp.Values) -> str: 1021 args = self.expressions(expression) 1022 alias = self.sql(expression, "alias") 1023 values = f"VALUES{self.seg('')}{args}" 1024 values = ( 1025 f"({values})" 1026 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1027 else values 1028 ) 1029 return f"{values} AS {alias}" if alias else values
1043 def group_sql(self, expression: exp.Group) -> str: 1044 group_by = self.op_expressions("GROUP BY", expression) 1045 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False) 1046 grouping_sets = ( 1047 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else "" 1048 ) 1049 1050 cube = expression.args.get("cube") 1051 if cube is True: 1052 cube = self.seg("WITH CUBE") 1053 else: 1054 cube = self.expressions(expression, key="cube", indent=False) 1055 cube = f"{self.seg('CUBE')} {self.wrap(cube)}" if cube else "" 1056 1057 rollup = expression.args.get("rollup") 1058 if rollup is True: 1059 rollup = self.seg("WITH ROLLUP") 1060 else: 1061 rollup = self.expressions(expression, key="rollup", indent=False) 1062 rollup = f"{self.seg('ROLLUP')} {self.wrap(rollup)}" if rollup else "" 1063 1064 return f"{group_by}{csv(grouping_sets, cube, rollup, sep=',')}"
1070 def join_sql(self, expression: exp.Join) -> str: 1071 op_sql = self.seg( 1072 " ".join( 1073 op 1074 for op in ( 1075 "NATURAL" if expression.args.get("natural") else None, 1076 expression.side, 1077 expression.kind, 1078 "JOIN", 1079 ) 1080 if op 1081 ) 1082 ) 1083 on_sql = self.sql(expression, "on") 1084 using = expression.args.get("using") 1085 1086 if not on_sql and using: 1087 on_sql = csv(*(self.sql(column) for column in using)) 1088 1089 if on_sql: 1090 on_sql = self.indent(on_sql, skip_first=True) 1091 space = self.seg(" " * self.pad) if self.pretty else " " 1092 if using: 1093 on_sql = f"{space}USING ({on_sql})" 1094 else: 1095 on_sql = f"{space}ON {on_sql}" 1096 1097 expression_sql = self.sql(expression, "expression") 1098 this_sql = self.sql(expression, "this") 1099 return f"{expression_sql}{op_sql} {this_sql}{on_sql}"
1106 def lateral_sql(self, expression: exp.Lateral) -> str: 1107 this = self.sql(expression, "this") 1108 1109 if isinstance(expression.this, exp.Subquery): 1110 return f"LATERAL {this}" 1111 1112 if expression.args.get("view"): 1113 alias = expression.args["alias"] 1114 columns = self.expressions(alias, key="columns", flat=True) 1115 table = f" {alias.name}" if alias.name else "" 1116 columns = f" AS {columns}" if columns else "" 1117 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 1118 return f"{op_sql}{self.sep()}{this}{table}{columns}" 1119 1120 alias = self.sql(expression, "alias") 1121 alias = f" AS {alias}" if alias else "" 1122 return f"LATERAL {this}{alias}"
1132 def lock_sql(self, expression: exp.Lock) -> str: 1133 if self.LOCKING_READS_SUPPORTED: 1134 lock_type = "UPDATE" if expression.args["update"] else "SHARE" 1135 return self.seg(f"FOR {lock_type}") 1136 1137 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 1138 return ""
1140 def literal_sql(self, expression: exp.Literal) -> str: 1141 text = expression.this or "" 1142 if expression.is_string: 1143 if self._replace_backslash: 1144 text = BACKSLASH_RE.sub(r"\\\\", text) 1145 text = text.replace(self.quote_end, self._escaped_quote_end) 1146 if self.pretty: 1147 text = text.replace("\n", self.SENTINEL_LINE_BREAK) 1148 text = f"{self.quote_start}{text}{self.quote_end}" 1149 return text
1151 def loaddata_sql(self, expression: exp.LoadData) -> str: 1152 local = " LOCAL" if expression.args.get("local") else "" 1153 inpath = f" INPATH {self.sql(expression, 'inpath')}" 1154 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 1155 this = f" INTO TABLE {self.sql(expression, 'this')}" 1156 partition = self.sql(expression, "partition") 1157 partition = f" {partition}" if partition else "" 1158 input_format = self.sql(expression, "input_format") 1159 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 1160 serde = self.sql(expression, "serde") 1161 serde = f" SERDE {serde}" if serde else "" 1162 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
1184 def ordered_sql(self, expression: exp.Ordered) -> str: 1185 desc = expression.args.get("desc") 1186 asc = not desc 1187 1188 nulls_first = expression.args.get("nulls_first") 1189 nulls_last = not nulls_first 1190 nulls_are_large = self.null_ordering == "nulls_are_large" 1191 nulls_are_small = self.null_ordering == "nulls_are_small" 1192 nulls_are_last = self.null_ordering == "nulls_are_last" 1193 1194 sort_order = " DESC" if desc else "" 1195 nulls_sort_change = "" 1196 if nulls_first and ( 1197 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 1198 ): 1199 nulls_sort_change = " NULLS FIRST" 1200 elif ( 1201 nulls_last 1202 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 1203 and not nulls_are_last 1204 ): 1205 nulls_sort_change = " NULLS LAST" 1206 1207 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 1208 self.unsupported( 1209 "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect" 1210 ) 1211 nulls_sort_change = "" 1212 1213 return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}"
1215 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 1216 partition = self.partition_by_sql(expression) 1217 order = self.sql(expression, "order") 1218 measures = self.sql(expression, "measures") 1219 measures = self.seg(f"MEASURES {measures}") if measures else "" 1220 rows = self.sql(expression, "rows") 1221 rows = self.seg(rows) if rows else "" 1222 after = self.sql(expression, "after") 1223 after = self.seg(after) if after else "" 1224 pattern = self.sql(expression, "pattern") 1225 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 1226 define = self.sql(expression, "define") 1227 define = self.seg(f"DEFINE {define}") if define else "" 1228 body = "".join( 1229 ( 1230 partition, 1231 order, 1232 measures, 1233 rows, 1234 after, 1235 pattern, 1236 define, 1237 ) 1238 ) 1239 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}"
1241 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 1242 return csv( 1243 *sqls, 1244 *[self.sql(sql) for sql in expression.args.get("joins") or []], 1245 self.sql(expression, "match"), 1246 *[self.sql(sql) for sql in expression.args.get("laterals") or []], 1247 self.sql(expression, "where"), 1248 self.sql(expression, "group"), 1249 self.sql(expression, "having"), 1250 self.sql(expression, "qualify"), 1251 self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True) 1252 if expression.args.get("windows") 1253 else "", 1254 self.sql(expression, "distribute"), 1255 self.sql(expression, "sort"), 1256 self.sql(expression, "cluster"), 1257 self.sql(expression, "order"), 1258 self.sql(expression, "limit"), 1259 self.sql(expression, "offset"), 1260 self.sql(expression, "lock"), 1261 sep="", 1262 )
1264 def select_sql(self, expression: exp.Select) -> str: 1265 hint = self.sql(expression, "hint") 1266 distinct = self.sql(expression, "distinct") 1267 distinct = f" {distinct}" if distinct else "" 1268 expressions = self.expressions(expression) 1269 expressions = f"{self.sep()}{expressions}" if expressions else expressions 1270 sql = self.query_modifiers( 1271 expression, 1272 f"SELECT{hint}{distinct}{expressions}", 1273 self.sql(expression, "into", comment=False), 1274 self.sql(expression, "from", comment=False), 1275 ) 1276 return self.prepend_ctes(expression, sql)
1284 def star_sql(self, expression: exp.Star) -> str: 1285 except_ = self.expressions(expression, key="except", flat=True) 1286 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else "" 1287 replace = self.expressions(expression, key="replace", flat=True) 1288 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else "" 1289 return f"*{except_}{replace}"
1307 def subquery_sql(self, expression: exp.Subquery) -> str: 1308 alias = self.sql(expression, "alias") 1309 1310 sql = self.query_modifiers( 1311 expression, 1312 self.wrap(expression), 1313 self.expressions(expression, key="pivots", sep=" "), 1314 f" AS {alias}" if alias else "", 1315 ) 1316 1317 return self.prepend_ctes(expression, sql)
1334 def unnest_sql(self, expression: exp.Unnest) -> str: 1335 args = self.expressions(expression, flat=True) 1336 alias = expression.args.get("alias") 1337 if alias and self.unnest_column_only: 1338 columns = alias.columns 1339 alias = self.sql(columns[0]) if columns else "" 1340 else: 1341 alias = self.sql(expression, "alias") 1342 alias = f" AS {alias}" if alias else alias 1343 ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else "" 1344 offset = expression.args.get("offset") 1345 offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else "" 1346 return f"UNNEST({args}){ordinality}{alias}{offset}"
1352 def window_sql(self, expression: exp.Window) -> str: 1353 this = self.sql(expression, "this") 1354 1355 partition = self.partition_by_sql(expression) 1356 1357 order = expression.args.get("order") 1358 order_sql = self.order_sql(order, flat=True) if order else "" 1359 1360 partition_sql = partition + " " if partition and order else partition 1361 1362 spec = expression.args.get("spec") 1363 spec_sql = " " + self.window_spec_sql(spec) if spec else "" 1364 1365 alias = self.sql(expression, "alias") 1366 this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}" 1367 1368 if not partition and not order and not spec and alias: 1369 return f"{this} {alias}" 1370 1371 window_args = alias + partition_sql + order_sql + spec_sql 1372 1373 return f"{this} ({window_args.strip()})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
1379 def window_spec_sql(self, expression: exp.WindowSpec) -> str: 1380 kind = self.sql(expression, "kind") 1381 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 1382 end = ( 1383 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 1384 or "CURRENT ROW" 1385 ) 1386 return f"{kind} BETWEEN {start} AND {end}"
1414 def case_sql(self, expression: exp.Case) -> str: 1415 this = self.sql(expression, "this") 1416 statements = [f"CASE {this}" if this else "CASE"] 1417 1418 for e in expression.args["ifs"]: 1419 statements.append(f"WHEN {self.sql(e, 'this')}") 1420 statements.append(f"THEN {self.sql(e, 'true')}") 1421 1422 default = self.sql(expression, "default") 1423 1424 if default: 1425 statements.append(f"ELSE {default}") 1426 1427 statements.append("END") 1428 1429 if self.pretty and self.text_width(statements) > self._max_text_width: 1430 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 1431 1432 return " ".join(statements)
1444 def trim_sql(self, expression: exp.Trim) -> str: 1445 trim_type = self.sql(expression, "position") 1446 1447 if trim_type == "LEADING": 1448 return f"{self.normalize_func('LTRIM')}({self.format_args(expression.this)})" 1449 elif trim_type == "TRAILING": 1450 return f"{self.normalize_func('RTRIM')}({self.format_args(expression.this)})" 1451 else: 1452 return f"{self.normalize_func('TRIM')}({self.format_args(expression.this, expression.expression)})"
1463 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 1464 expressions = self.expressions(expression, flat=True) 1465 reference = self.sql(expression, "reference") 1466 reference = f" {reference}" if reference else "" 1467 delete = self.sql(expression, "delete") 1468 delete = f" ON DELETE {delete}" if delete else "" 1469 update = self.sql(expression, "update") 1470 update = f" ON UPDATE {update}" if update else "" 1471 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
1488 def in_sql(self, expression: exp.In) -> str: 1489 query = expression.args.get("query") 1490 unnest = expression.args.get("unnest") 1491 field = expression.args.get("field") 1492 is_global = " GLOBAL" if expression.args.get("is_global") else "" 1493 1494 if query: 1495 in_sql = self.wrap(query) 1496 elif unnest: 1497 in_sql = self.in_unnest_op(unnest) 1498 elif field: 1499 in_sql = self.sql(field) 1500 else: 1501 in_sql = f"({self.expressions(expression, flat=True)})" 1502 1503 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
1508 def interval_sql(self, expression: exp.Interval) -> str: 1509 this = expression.args.get("this") 1510 if this: 1511 this = ( 1512 f" {this}" 1513 if isinstance(this, exp.Literal) or isinstance(this, exp.Paren) 1514 else f" ({this})" 1515 ) 1516 else: 1517 this = "" 1518 unit = expression.args.get("unit") 1519 unit = f" {unit}" if unit else "" 1520 return f"INTERVAL{this}{unit}"
1525 def reference_sql(self, expression: exp.Reference) -> str: 1526 this = self.sql(expression, "this") 1527 expressions = self.expressions(expression, flat=True) 1528 expressions = f"({expressions})" if expressions else "" 1529 options = self.expressions(expression, "options", flat=True, sep=" ") 1530 options = f" {options}" if options else "" 1531 return f"REFERENCES {this}{expressions}{options}"
1537 def paren_sql(self, expression: exp.Paren) -> str: 1538 if isinstance(expression.unnest(), exp.Select): 1539 sql = self.wrap(expression) 1540 else: 1541 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 1542 sql = f"({sql}{self.seg(')', sep='')}" 1543 1544 return self.prepend_ctes(expression, sql)
1574 def connector_sql(self, expression: exp.Connector, op: str) -> str: 1575 if not self.pretty: 1576 return self.binary(expression, op) 1577 1578 sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False)) 1579 sep = "\n" if self.text_width(sqls) > self._max_text_width else " " 1580 return f"{sep}{op} ".join(sqls)
1628 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 1629 this = self.sql(expression, "this") 1630 1631 dtype = self.sql(expression, "dtype") 1632 if dtype: 1633 collate = self.sql(expression, "collate") 1634 collate = f" COLLATE {collate}" if collate else "" 1635 using = self.sql(expression, "using") 1636 using = f" USING {using}" if using else "" 1637 return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}" 1638 1639 default = self.sql(expression, "default") 1640 if default: 1641 return f"ALTER COLUMN {this} SET DEFAULT {default}" 1642 1643 if not expression.args.get("drop"): 1644 self.unsupported("Unsupported ALTER COLUMN syntax") 1645 1646 return f"ALTER COLUMN {this} DROP DEFAULT"
1652 def altertable_sql(self, expression: exp.AlterTable) -> str: 1653 actions = expression.args["actions"] 1654 1655 if isinstance(actions[0], exp.ColumnDef): 1656 actions = self.expressions(expression, "actions", prefix="ADD COLUMN ") 1657 elif isinstance(actions[0], exp.Schema): 1658 actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ") 1659 elif isinstance(actions[0], exp.Delete): 1660 actions = self.expressions(expression, "actions", flat=True) 1661 else: 1662 actions = self.expressions(expression, "actions") 1663 1664 exists = " IF EXISTS" if expression.args.get("exists") else "" 1665 return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}"
1672 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 1673 this = self.sql(expression, "this") 1674 expression_ = self.sql(expression, "expression") 1675 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD" 1676 1677 enforced = expression.args.get("enforced") 1678 if enforced is not None: 1679 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}" 1680 1681 return f"{add_constraint} {expression_}"
1787 def function_fallback_sql(self, expression: exp.Func) -> str: 1788 args = [] 1789 for arg_value in expression.args.values(): 1790 if isinstance(arg_value, list): 1791 for value in arg_value: 1792 args.append(value) 1793 else: 1794 args.append(arg_value) 1795 1796 return f"{self.normalize_func(expression.sql_name())}({self.format_args(*args)})"
1798 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 1799 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None) 1800 if self.pretty and self.text_width(arg_sqls) > self._max_text_width: 1801 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 1802 return ", ".join(arg_sqls)
def
expressions( self, expression: sqlglot.expressions.Expression, key: Optional[str] = None, flat: bool = False, indent: bool = True, sep: str = ', ', prefix: str = '') -> str:
1810 def expressions( 1811 self, 1812 expression: exp.Expression, 1813 key: t.Optional[str] = None, 1814 flat: bool = False, 1815 indent: bool = True, 1816 sep: str = ", ", 1817 prefix: str = "", 1818 ) -> str: 1819 expressions = expression.args.get(key or "expressions") 1820 1821 if not expressions: 1822 return "" 1823 1824 if flat: 1825 return sep.join(self.sql(e) for e in expressions) 1826 1827 num_sqls = len(expressions) 1828 1829 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly 1830 pad = " " * self.pad 1831 stripped_sep = sep.strip() 1832 1833 result_sqls = [] 1834 for i, e in enumerate(expressions): 1835 sql = self.sql(e, comment=False) 1836 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 1837 1838 if self.pretty: 1839 if self._leading_comma: 1840 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}") 1841 else: 1842 result_sqls.append( 1843 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}" 1844 ) 1845 else: 1846 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 1847 1848 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls) 1849 return self.indent(result_sql, skip_first=False) if indent else result_sql
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
1857 def naked_property(self, expression: exp.Property) -> str: 1858 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 1859 if not property_name: 1860 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 1861 return f"{property_name} {self.sql(expression, 'this')}"
1876 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 1877 this = self.sql(expression, "this") 1878 expressions = self.no_identify(self.expressions, expression) 1879 expressions = ( 1880 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 1881 ) 1882 return f"{this}{expressions}"
def
userdefinedfunctionkwarg_sql(self, expression: sqlglot.expressions.UserDefinedFunctionKwarg) -> str:
1897 def when_sql(self, expression: exp.When) -> str: 1898 this = self.sql(expression, "this") 1899 then_expression = expression.args.get("then") 1900 if isinstance(then_expression, exp.Insert): 1901 then = f"INSERT {self.sql(then_expression, 'this')}" 1902 if "expression" in then_expression.args: 1903 then += f" VALUES {self.sql(then_expression, 'expression')}" 1904 elif isinstance(then_expression, exp.Update): 1905 if isinstance(then_expression.args.get("expressions"), exp.Star): 1906 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 1907 else: 1908 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 1909 else: 1910 then = self.sql(then_expression) 1911 return f"WHEN {this} THEN {then}"