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