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