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