sqlglot.generator
1from __future__ import annotations 2 3import logging 4import typing as t 5from collections import defaultdict 6 7from sqlglot import exp 8from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 9from sqlglot.helper import apply_index_offset, csv, seq_get 10from sqlglot.time import format_time 11from sqlglot.tokens import Tokenizer, TokenType 12 13logger = logging.getLogger("sqlglot") 14 15 16class Generator: 17 """ 18 Generator converts a given syntax tree to the corresponding SQL string. 19 20 Args: 21 pretty: Whether or not to format the produced SQL string. 22 Default: False. 23 identify: Determines when an identifier should be quoted. Possible values are: 24 False (default): Never quote, except in cases where it's mandatory by the dialect. 25 True or 'always': Always quote. 26 'safe': Only quote identifiers that are case insensitive. 27 normalize: Whether or not to normalize identifiers to lowercase. 28 Default: False. 29 pad: Determines the pad size in a formatted string. 30 Default: 2. 31 indent: Determines the indentation size in a formatted string. 32 Default: 2. 33 normalize_functions: Whether or not to normalize all function names. Possible values are: 34 "upper" or True (default): Convert names to uppercase. 35 "lower": Convert names to lowercase. 36 False: Disables function name normalization. 37 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 38 Default ErrorLevel.WARN. 39 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 40 This is only relevant if unsupported_level is ErrorLevel.RAISE. 41 Default: 3 42 leading_comma: Determines whether or not the comma is leading or trailing in select expressions. 43 This is only relevant when generating in pretty mode. 44 Default: False 45 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 46 The default is on the smaller end because the length only represents a segment and not the true 47 line length. 48 Default: 80 49 comments: Whether or not to preserve comments in the output SQL code. 50 Default: True 51 """ 52 53 TRANSFORMS = { 54 exp.DateAdd: lambda self, e: self.func( 55 "DATE_ADD", e.this, e.expression, exp.Literal.string(e.text("unit")) 56 ), 57 exp.TsOrDsAdd: lambda self, e: self.func( 58 "TS_OR_DS_ADD", e.this, e.expression, exp.Literal.string(e.text("unit")) 59 ), 60 exp.CaseSpecificColumnConstraint: lambda self, e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 61 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 62 exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 63 exp.CheckColumnConstraint: lambda self, e: f"CHECK ({self.sql(e, 'this')})", 64 exp.ClusteredColumnConstraint: lambda self, e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 65 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 66 exp.CopyGrantsProperty: lambda self, e: "COPY GRANTS", 67 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 68 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 69 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 70 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 71 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 72 exp.ExternalProperty: lambda self, e: "EXTERNAL", 73 exp.HeapProperty: lambda self, e: "HEAP", 74 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 75 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 76 exp.LanguageProperty: lambda self, e: self.naked_property(e), 77 exp.LocationProperty: lambda self, e: self.naked_property(e), 78 exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG", 79 exp.MaterializedProperty: lambda self, e: "MATERIALIZED", 80 exp.NoPrimaryIndexProperty: lambda self, e: "NO PRIMARY INDEX", 81 exp.NonClusteredColumnConstraint: lambda self, e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 82 exp.NotForReplicationColumnConstraint: lambda self, e: "NOT FOR REPLICATION", 83 exp.OnCommitProperty: lambda self, e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 84 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 85 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 86 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 87 exp.ReturnsProperty: lambda self, e: self.naked_property(e), 88 exp.SetProperty: lambda self, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 89 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 90 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 91 exp.StabilityProperty: lambda self, e: e.name, 92 exp.TemporaryProperty: lambda self, e: f"TEMPORARY", 93 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 94 exp.TransientProperty: lambda self, e: "TRANSIENT", 95 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 96 exp.UppercaseColumnConstraint: lambda self, e: f"UPPERCASE", 97 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 98 exp.VolatileProperty: lambda self, e: "VOLATILE", 99 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 100 } 101 102 # Whether or not null ordering is supported in order by 103 NULL_ORDERING_SUPPORTED = True 104 105 # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 106 LOCKING_READS_SUPPORTED = False 107 108 # Always do union distinct or union all 109 EXPLICIT_UNION = False 110 111 # Wrap derived values in parens, usually standard but spark doesn't support it 112 WRAP_DERIVED_VALUES = True 113 114 # Whether or not create function uses an AS before the RETURN 115 CREATE_FUNCTION_RETURN_AS = True 116 117 # Whether or not MERGE ... WHEN MATCHED BY SOURCE is allowed 118 MATCHED_BY_SOURCE = True 119 120 # Whether or not the INTERVAL expression works only with values like '1 day' 121 SINGLE_STRING_INTERVAL = False 122 123 # Whether or not the plural form of date parts like day (i.e. "days") is supported in INTERVALs 124 INTERVAL_ALLOWS_PLURAL_FORM = True 125 126 # Whether or not the TABLESAMPLE clause supports a method name, like BERNOULLI 127 TABLESAMPLE_WITH_METHOD = True 128 129 # Whether or not to treat the number in TABLESAMPLE (50) as a percentage 130 TABLESAMPLE_SIZE_IS_PERCENT = False 131 132 # Whether or not limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 133 LIMIT_FETCH = "ALL" 134 135 # Whether or not a table is allowed to be renamed with a db 136 RENAME_TABLE_WITH_DB = True 137 138 # The separator for grouping sets and rollups 139 GROUPINGS_SEP = "," 140 141 # The string used for creating an index on a table 142 INDEX_ON = "ON" 143 144 # Whether or not join hints should be generated 145 JOIN_HINTS = True 146 147 # Whether or not table hints should be generated 148 TABLE_HINTS = True 149 150 # Whether or not query hints should be generated 151 QUERY_HINTS = True 152 153 # What kind of separator to use for query hints 154 QUERY_HINT_SEP = ", " 155 156 # Whether or not comparing against booleans (e.g. x IS TRUE) is supported 157 IS_BOOL_ALLOWED = True 158 159 # Whether or not to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 160 DUPLICATE_KEY_UPDATE_WITH_SET = True 161 162 # Whether or not to generate the limit as TOP <value> instead of LIMIT <value> 163 LIMIT_IS_TOP = False 164 165 # Whether or not to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 166 RETURNING_END = True 167 168 # Whether or not to generate the (+) suffix for columns used in old-style join conditions 169 COLUMN_JOIN_MARKS_SUPPORTED = False 170 171 # Whether or not to generate an unquoted value for EXTRACT's date part argument 172 EXTRACT_ALLOWS_QUOTES = True 173 174 # Whether or not TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 175 TZ_TO_WITH_TIME_ZONE = False 176 177 # Whether or not the NVL2 function is supported 178 NVL2_SUPPORTED = True 179 180 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 181 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 182 183 # Whether or not VALUES statements can be used as derived tables. 184 # MySQL 5 and Redshift do not allow this, so when False, it will convert 185 # SELECT * VALUES into SELECT UNION 186 VALUES_AS_TABLE = True 187 188 # Whether or not the word COLUMN is included when adding a column with ALTER TABLE 189 ALTER_TABLE_ADD_COLUMN_KEYWORD = True 190 191 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 192 UNNEST_WITH_ORDINALITY = True 193 194 # Whether or not FILTER (WHERE cond) can be used for conditional aggregation 195 AGGREGATE_FILTER_SUPPORTED = True 196 197 # Whether or not JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 198 SEMI_ANTI_JOIN_WITH_SIDE = True 199 200 # Whether or not session variables / parameters are supported, e.g. @x in T-SQL 201 SUPPORTS_PARAMETERS = True 202 203 TYPE_MAPPING = { 204 exp.DataType.Type.NCHAR: "CHAR", 205 exp.DataType.Type.NVARCHAR: "VARCHAR", 206 exp.DataType.Type.MEDIUMTEXT: "TEXT", 207 exp.DataType.Type.LONGTEXT: "TEXT", 208 exp.DataType.Type.TINYTEXT: "TEXT", 209 exp.DataType.Type.MEDIUMBLOB: "BLOB", 210 exp.DataType.Type.LONGBLOB: "BLOB", 211 exp.DataType.Type.TINYBLOB: "BLOB", 212 exp.DataType.Type.INET: "INET", 213 } 214 215 STAR_MAPPING = { 216 "except": "EXCEPT", 217 "replace": "REPLACE", 218 } 219 220 TIME_PART_SINGULARS = { 221 "microseconds": "microsecond", 222 "seconds": "second", 223 "minutes": "minute", 224 "hours": "hour", 225 "days": "day", 226 "weeks": "week", 227 "months": "month", 228 "quarters": "quarter", 229 "years": "year", 230 } 231 232 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 233 234 STRUCT_DELIMITER = ("<", ">") 235 236 PARAMETER_TOKEN = "@" 237 238 PROPERTIES_LOCATION = { 239 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 240 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 241 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 242 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 243 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 244 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 245 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 246 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 247 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 248 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 249 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 250 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 251 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 252 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 253 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 254 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 255 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 256 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 257 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 258 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 259 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 260 exp.HeapProperty: exp.Properties.Location.POST_WITH, 261 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 262 exp.JournalProperty: exp.Properties.Location.POST_NAME, 263 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 264 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 265 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 266 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 267 exp.LogProperty: exp.Properties.Location.POST_NAME, 268 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 269 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 270 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 271 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 272 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 273 exp.Order: exp.Properties.Location.POST_SCHEMA, 274 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 275 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 276 exp.Property: exp.Properties.Location.POST_WITH, 277 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 278 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 279 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 280 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 281 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 282 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 283 exp.Set: exp.Properties.Location.POST_SCHEMA, 284 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 285 exp.SetProperty: exp.Properties.Location.POST_CREATE, 286 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 287 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 288 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 289 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 290 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 291 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 292 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 293 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 294 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 295 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 296 } 297 298 # Keywords that can't be used as unquoted identifier names 299 RESERVED_KEYWORDS: t.Set[str] = set() 300 301 # Expressions whose comments are separated from them for better formatting 302 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 303 exp.Create, 304 exp.Delete, 305 exp.Drop, 306 exp.From, 307 exp.Insert, 308 exp.Join, 309 exp.Select, 310 exp.Update, 311 exp.Where, 312 exp.With, 313 ) 314 315 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 316 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 317 exp.Column, 318 exp.Literal, 319 exp.Neg, 320 exp.Paren, 321 ) 322 323 UNESCAPED_SEQUENCE_TABLE = None # type: ignore 324 325 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 326 327 # Autofilled 328 INVERSE_TIME_MAPPING: t.Dict[str, str] = {} 329 INVERSE_TIME_TRIE: t.Dict = {} 330 INDEX_OFFSET = 0 331 UNNEST_COLUMN_ONLY = False 332 ALIAS_POST_TABLESAMPLE = False 333 IDENTIFIERS_CAN_START_WITH_DIGIT = False 334 STRICT_STRING_CONCAT = False 335 NORMALIZE_FUNCTIONS: bool | str = "upper" 336 NULL_ORDERING = "nulls_are_small" 337 338 can_identify: t.Callable[[str, str | bool], bool] 339 340 # Delimiters for quotes, identifiers and the corresponding escape characters 341 QUOTE_START = "'" 342 QUOTE_END = "'" 343 IDENTIFIER_START = '"' 344 IDENTIFIER_END = '"' 345 TOKENIZER_CLASS = Tokenizer 346 347 # Delimiters for bit, hex, byte and raw literals 348 BIT_START: t.Optional[str] = None 349 BIT_END: t.Optional[str] = None 350 HEX_START: t.Optional[str] = None 351 HEX_END: t.Optional[str] = None 352 BYTE_START: t.Optional[str] = None 353 BYTE_END: t.Optional[str] = None 354 355 __slots__ = ( 356 "pretty", 357 "identify", 358 "normalize", 359 "pad", 360 "_indent", 361 "normalize_functions", 362 "unsupported_level", 363 "max_unsupported", 364 "leading_comma", 365 "max_text_width", 366 "comments", 367 "unsupported_messages", 368 "_escaped_quote_end", 369 "_escaped_identifier_end", 370 "_cache", 371 ) 372 373 def __init__( 374 self, 375 pretty: t.Optional[bool] = None, 376 identify: str | bool = False, 377 normalize: bool = False, 378 pad: int = 2, 379 indent: int = 2, 380 normalize_functions: t.Optional[str | bool] = None, 381 unsupported_level: ErrorLevel = ErrorLevel.WARN, 382 max_unsupported: int = 3, 383 leading_comma: bool = False, 384 max_text_width: int = 80, 385 comments: bool = True, 386 ): 387 import sqlglot 388 389 self.pretty = pretty if pretty is not None else sqlglot.pretty 390 self.identify = identify 391 self.normalize = normalize 392 self.pad = pad 393 self._indent = indent 394 self.unsupported_level = unsupported_level 395 self.max_unsupported = max_unsupported 396 self.leading_comma = leading_comma 397 self.max_text_width = max_text_width 398 self.comments = comments 399 400 # This is both a Dialect property and a Generator argument, so we prioritize the latter 401 self.normalize_functions = ( 402 self.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 403 ) 404 405 self.unsupported_messages: t.List[str] = [] 406 self._escaped_quote_end: str = self.TOKENIZER_CLASS.STRING_ESCAPES[0] + self.QUOTE_END 407 self._escaped_identifier_end: str = ( 408 self.TOKENIZER_CLASS.IDENTIFIER_ESCAPES[0] + self.IDENTIFIER_END 409 ) 410 self._cache: t.Optional[t.Dict[int, str]] = None 411 412 def generate( 413 self, 414 expression: t.Optional[exp.Expression], 415 cache: t.Optional[t.Dict[int, str]] = None, 416 ) -> str: 417 """ 418 Generates the SQL string corresponding to the given syntax tree. 419 420 Args: 421 expression: The syntax tree. 422 cache: An optional sql string cache. This leverages the hash of an Expression 423 which can be slow to compute, so only use it if you set _hash on each node. 424 425 Returns: 426 The SQL string corresponding to `expression`. 427 """ 428 if cache is not None: 429 self._cache = cache 430 431 self.unsupported_messages = [] 432 sql = self.sql(expression).strip() 433 self._cache = None 434 435 if self.unsupported_level == ErrorLevel.IGNORE: 436 return sql 437 438 if self.unsupported_level == ErrorLevel.WARN: 439 for msg in self.unsupported_messages: 440 logger.warning(msg) 441 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 442 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 443 444 if self.pretty: 445 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 446 return sql 447 448 def unsupported(self, message: str) -> None: 449 if self.unsupported_level == ErrorLevel.IMMEDIATE: 450 raise UnsupportedError(message) 451 self.unsupported_messages.append(message) 452 453 def sep(self, sep: str = " ") -> str: 454 return f"{sep.strip()}\n" if self.pretty else sep 455 456 def seg(self, sql: str, sep: str = " ") -> str: 457 return f"{self.sep(sep)}{sql}" 458 459 def pad_comment(self, comment: str) -> str: 460 comment = " " + comment if comment[0].strip() else comment 461 comment = comment + " " if comment[-1].strip() else comment 462 return comment 463 464 def maybe_comment( 465 self, 466 sql: str, 467 expression: t.Optional[exp.Expression] = None, 468 comments: t.Optional[t.List[str]] = None, 469 ) -> str: 470 comments = ( 471 ((expression and expression.comments) if comments is None else comments) # type: ignore 472 if self.comments 473 else None 474 ) 475 476 if not comments or isinstance(expression, exp.Binary): 477 return sql 478 479 sep = "\n" if self.pretty else " " 480 comments_sql = sep.join( 481 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 482 ) 483 484 if not comments_sql: 485 return sql 486 487 if isinstance(expression, self.WITH_SEPARATED_COMMENTS): 488 return ( 489 f"{self.sep()}{comments_sql}{sql}" 490 if sql[0].isspace() 491 else f"{comments_sql}{self.sep()}{sql}" 492 ) 493 494 return f"{sql} {comments_sql}" 495 496 def wrap(self, expression: exp.Expression | str) -> str: 497 this_sql = self.indent( 498 self.sql(expression) 499 if isinstance(expression, (exp.Select, exp.Union)) 500 else self.sql(expression, "this"), 501 level=1, 502 pad=0, 503 ) 504 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 505 506 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 507 original = self.identify 508 self.identify = False 509 result = func(*args, **kwargs) 510 self.identify = original 511 return result 512 513 def normalize_func(self, name: str) -> str: 514 if self.normalize_functions == "upper" or self.normalize_functions is True: 515 return name.upper() 516 if self.normalize_functions == "lower": 517 return name.lower() 518 return name 519 520 def indent( 521 self, 522 sql: str, 523 level: int = 0, 524 pad: t.Optional[int] = None, 525 skip_first: bool = False, 526 skip_last: bool = False, 527 ) -> str: 528 if not self.pretty: 529 return sql 530 531 pad = self.pad if pad is None else pad 532 lines = sql.split("\n") 533 534 return "\n".join( 535 line 536 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 537 else f"{' ' * (level * self._indent + pad)}{line}" 538 for i, line in enumerate(lines) 539 ) 540 541 def sql( 542 self, 543 expression: t.Optional[str | exp.Expression], 544 key: t.Optional[str] = None, 545 comment: bool = True, 546 ) -> str: 547 if not expression: 548 return "" 549 550 if isinstance(expression, str): 551 return expression 552 553 if key: 554 value = expression.args.get(key) 555 if value: 556 return self.sql(value) 557 return "" 558 559 if self._cache is not None: 560 expression_id = hash(expression) 561 562 if expression_id in self._cache: 563 return self._cache[expression_id] 564 565 transform = self.TRANSFORMS.get(expression.__class__) 566 567 if callable(transform): 568 sql = transform(self, expression) 569 elif transform: 570 sql = transform 571 elif isinstance(expression, exp.Expression): 572 exp_handler_name = f"{expression.key}_sql" 573 574 if hasattr(self, exp_handler_name): 575 sql = getattr(self, exp_handler_name)(expression) 576 elif isinstance(expression, exp.Func): 577 sql = self.function_fallback_sql(expression) 578 elif isinstance(expression, exp.Property): 579 sql = self.property_sql(expression) 580 else: 581 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 582 else: 583 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 584 585 sql = self.maybe_comment(sql, expression) if self.comments and comment else sql 586 587 if self._cache is not None: 588 self._cache[expression_id] = sql 589 return sql 590 591 def uncache_sql(self, expression: exp.Uncache) -> str: 592 table = self.sql(expression, "this") 593 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 594 return f"UNCACHE TABLE{exists_sql} {table}" 595 596 def cache_sql(self, expression: exp.Cache) -> str: 597 lazy = " LAZY" if expression.args.get("lazy") else "" 598 table = self.sql(expression, "this") 599 options = expression.args.get("options") 600 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 601 sql = self.sql(expression, "expression") 602 sql = f" AS{self.sep()}{sql}" if sql else "" 603 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 604 return self.prepend_ctes(expression, sql) 605 606 def characterset_sql(self, expression: exp.CharacterSet) -> str: 607 if isinstance(expression.parent, exp.Cast): 608 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 609 default = "DEFAULT " if expression.args.get("default") else "" 610 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 611 612 def column_sql(self, expression: exp.Column) -> str: 613 join_mark = " (+)" if expression.args.get("join_mark") else "" 614 615 if join_mark and not self.COLUMN_JOIN_MARKS_SUPPORTED: 616 join_mark = "" 617 self.unsupported("Outer join syntax using the (+) operator is not supported.") 618 619 column = ".".join( 620 self.sql(part) 621 for part in ( 622 expression.args.get("catalog"), 623 expression.args.get("db"), 624 expression.args.get("table"), 625 expression.args.get("this"), 626 ) 627 if part 628 ) 629 630 return f"{column}{join_mark}" 631 632 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 633 this = self.sql(expression, "this") 634 this = f" {this}" if this else "" 635 position = self.sql(expression, "position") 636 return f"{position}{this}" 637 638 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 639 column = self.sql(expression, "this") 640 kind = self.sql(expression, "kind") 641 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 642 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 643 kind = f"{sep}{kind}" if kind else "" 644 constraints = f" {constraints}" if constraints else "" 645 position = self.sql(expression, "position") 646 position = f" {position}" if position else "" 647 648 return f"{exists}{column}{kind}{constraints}{position}" 649 650 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 651 this = self.sql(expression, "this") 652 kind_sql = self.sql(expression, "kind").strip() 653 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 654 655 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 656 this = self.sql(expression, "this") 657 if expression.args.get("not_null"): 658 persisted = " PERSISTED NOT NULL" 659 elif expression.args.get("persisted"): 660 persisted = " PERSISTED" 661 else: 662 persisted = "" 663 return f"AS {this}{persisted}" 664 665 def autoincrementcolumnconstraint_sql(self, _) -> str: 666 return self.token_sql(TokenType.AUTO_INCREMENT) 667 668 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 669 if isinstance(expression.this, list): 670 this = self.wrap(self.expressions(expression, key="this", flat=True)) 671 else: 672 this = self.sql(expression, "this") 673 674 return f"COMPRESS {this}" 675 676 def generatedasidentitycolumnconstraint_sql( 677 self, expression: exp.GeneratedAsIdentityColumnConstraint 678 ) -> str: 679 this = "" 680 if expression.this is not None: 681 on_null = " ON NULL" if expression.args.get("on_null") else "" 682 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 683 684 start = expression.args.get("start") 685 start = f"START WITH {start}" if start else "" 686 increment = expression.args.get("increment") 687 increment = f" INCREMENT BY {increment}" if increment else "" 688 minvalue = expression.args.get("minvalue") 689 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 690 maxvalue = expression.args.get("maxvalue") 691 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 692 cycle = expression.args.get("cycle") 693 cycle_sql = "" 694 695 if cycle is not None: 696 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 697 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 698 699 sequence_opts = "" 700 if start or increment or cycle_sql: 701 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 702 sequence_opts = f" ({sequence_opts.strip()})" 703 704 expr = self.sql(expression, "expression") 705 expr = f"({expr})" if expr else "IDENTITY" 706 707 return f"GENERATED{this} AS {expr}{sequence_opts}" 708 709 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 710 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 711 712 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 713 desc = expression.args.get("desc") 714 if desc is not None: 715 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 716 return f"PRIMARY KEY" 717 718 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 719 this = self.sql(expression, "this") 720 this = f" {this}" if this else "" 721 index_type = expression.args.get("index_type") 722 index_type = f" USING {index_type}" if index_type else "" 723 return f"UNIQUE{this}{index_type}" 724 725 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 726 return self.sql(expression, "this") 727 728 def create_sql(self, expression: exp.Create) -> str: 729 kind = self.sql(expression, "kind").upper() 730 properties = expression.args.get("properties") 731 properties_locs = self.locate_properties(properties) if properties else defaultdict() 732 733 this = self.createable_sql(expression, properties_locs) 734 735 properties_sql = "" 736 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 737 exp.Properties.Location.POST_WITH 738 ): 739 properties_sql = self.sql( 740 exp.Properties( 741 expressions=[ 742 *properties_locs[exp.Properties.Location.POST_SCHEMA], 743 *properties_locs[exp.Properties.Location.POST_WITH], 744 ] 745 ) 746 ) 747 748 begin = " BEGIN" if expression.args.get("begin") else "" 749 expression_sql = self.sql(expression, "expression") 750 if expression_sql: 751 expression_sql = f"{begin}{self.sep()}{expression_sql}" 752 753 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 754 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 755 postalias_props_sql = self.properties( 756 exp.Properties( 757 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 758 ), 759 wrapped=False, 760 ) 761 expression_sql = f" AS {postalias_props_sql}{expression_sql}" 762 else: 763 expression_sql = f" AS{expression_sql}" 764 765 postindex_props_sql = "" 766 if properties_locs.get(exp.Properties.Location.POST_INDEX): 767 postindex_props_sql = self.properties( 768 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 769 wrapped=False, 770 prefix=" ", 771 ) 772 773 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 774 indexes = f" {indexes}" if indexes else "" 775 index_sql = indexes + postindex_props_sql 776 777 replace = " OR REPLACE" if expression.args.get("replace") else "" 778 unique = " UNIQUE" if expression.args.get("unique") else "" 779 780 postcreate_props_sql = "" 781 if properties_locs.get(exp.Properties.Location.POST_CREATE): 782 postcreate_props_sql = self.properties( 783 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 784 sep=" ", 785 prefix=" ", 786 wrapped=False, 787 ) 788 789 modifiers = "".join((replace, unique, postcreate_props_sql)) 790 791 postexpression_props_sql = "" 792 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 793 postexpression_props_sql = self.properties( 794 exp.Properties( 795 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 796 ), 797 sep=" ", 798 prefix=" ", 799 wrapped=False, 800 ) 801 802 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 803 no_schema_binding = ( 804 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 805 ) 806 807 clone = self.sql(expression, "clone") 808 clone = f" {clone}" if clone else "" 809 810 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 811 return self.prepend_ctes(expression, expression_sql) 812 813 def clone_sql(self, expression: exp.Clone) -> str: 814 this = self.sql(expression, "this") 815 shallow = "SHALLOW " if expression.args.get("shallow") else "" 816 this = f"{shallow}CLONE {this}" 817 when = self.sql(expression, "when") 818 819 if when: 820 kind = self.sql(expression, "kind") 821 expr = self.sql(expression, "expression") 822 return f"{this} {when} ({kind} => {expr})" 823 824 return this 825 826 def describe_sql(self, expression: exp.Describe) -> str: 827 return f"DESCRIBE {self.sql(expression, 'this')}" 828 829 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 830 with_ = self.sql(expression, "with") 831 if with_: 832 sql = f"{with_}{self.sep()}{sql}" 833 return sql 834 835 def with_sql(self, expression: exp.With) -> str: 836 sql = self.expressions(expression, flat=True) 837 recursive = "RECURSIVE " if expression.args.get("recursive") else "" 838 839 return f"WITH {recursive}{sql}" 840 841 def cte_sql(self, expression: exp.CTE) -> str: 842 alias = self.sql(expression, "alias") 843 return f"{alias} AS {self.wrap(expression)}" 844 845 def tablealias_sql(self, expression: exp.TableAlias) -> str: 846 alias = self.sql(expression, "this") 847 columns = self.expressions(expression, key="columns", flat=True) 848 columns = f"({columns})" if columns else "" 849 return f"{alias}{columns}" 850 851 def bitstring_sql(self, expression: exp.BitString) -> str: 852 this = self.sql(expression, "this") 853 if self.BIT_START: 854 return f"{self.BIT_START}{this}{self.BIT_END}" 855 return f"{int(this, 2)}" 856 857 def hexstring_sql(self, expression: exp.HexString) -> str: 858 this = self.sql(expression, "this") 859 if self.HEX_START: 860 return f"{self.HEX_START}{this}{self.HEX_END}" 861 return f"{int(this, 16)}" 862 863 def bytestring_sql(self, expression: exp.ByteString) -> str: 864 this = self.sql(expression, "this") 865 if self.BYTE_START: 866 return f"{self.BYTE_START}{this}{self.BYTE_END}" 867 return this 868 869 def rawstring_sql(self, expression: exp.RawString) -> str: 870 string = self.escape_str(expression.this.replace("\\", "\\\\")) 871 return f"{self.QUOTE_START}{string}{self.QUOTE_END}" 872 873 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 874 this = self.sql(expression, "this") 875 specifier = self.sql(expression, "expression") 876 specifier = f" {specifier}" if specifier else "" 877 return f"{this}{specifier}" 878 879 def datatype_sql(self, expression: exp.DataType) -> str: 880 type_value = expression.this 881 882 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 883 type_sql = self.sql(expression, "kind") 884 else: 885 type_sql = ( 886 self.TYPE_MAPPING.get(type_value, type_value.value) 887 if isinstance(type_value, exp.DataType.Type) 888 else type_value 889 ) 890 891 nested = "" 892 interior = self.expressions(expression, flat=True) 893 values = "" 894 895 if interior: 896 if expression.args.get("nested"): 897 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 898 if expression.args.get("values") is not None: 899 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 900 values = self.expressions(expression, key="values", flat=True) 901 values = f"{delimiters[0]}{values}{delimiters[1]}" 902 elif type_value == exp.DataType.Type.INTERVAL: 903 nested = f" {interior}" 904 else: 905 nested = f"({interior})" 906 907 type_sql = f"{type_sql}{nested}{values}" 908 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 909 exp.DataType.Type.TIMETZ, 910 exp.DataType.Type.TIMESTAMPTZ, 911 ): 912 type_sql = f"{type_sql} WITH TIME ZONE" 913 914 return type_sql 915 916 def directory_sql(self, expression: exp.Directory) -> str: 917 local = "LOCAL " if expression.args.get("local") else "" 918 row_format = self.sql(expression, "row_format") 919 row_format = f" {row_format}" if row_format else "" 920 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 921 922 def delete_sql(self, expression: exp.Delete) -> str: 923 this = self.sql(expression, "this") 924 this = f" FROM {this}" if this else "" 925 using = self.sql(expression, "using") 926 using = f" USING {using}" if using else "" 927 where = self.sql(expression, "where") 928 returning = self.sql(expression, "returning") 929 limit = self.sql(expression, "limit") 930 tables = self.expressions(expression, key="tables") 931 tables = f" {tables}" if tables else "" 932 if self.RETURNING_END: 933 expression_sql = f"{this}{using}{where}{returning}{limit}" 934 else: 935 expression_sql = f"{returning}{this}{using}{where}{limit}" 936 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 937 938 def drop_sql(self, expression: exp.Drop) -> str: 939 this = self.sql(expression, "this") 940 kind = expression.args["kind"] 941 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 942 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 943 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 944 cascade = " CASCADE" if expression.args.get("cascade") else "" 945 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 946 purge = " PURGE" if expression.args.get("purge") else "" 947 return ( 948 f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}{constraints}{purge}" 949 ) 950 951 def except_sql(self, expression: exp.Except) -> str: 952 return self.prepend_ctes( 953 expression, 954 self.set_operation(expression, self.except_op(expression)), 955 ) 956 957 def except_op(self, expression: exp.Except) -> str: 958 return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}" 959 960 def fetch_sql(self, expression: exp.Fetch) -> str: 961 direction = expression.args.get("direction") 962 direction = f" {direction.upper()}" if direction else "" 963 count = expression.args.get("count") 964 count = f" {count}" if count else "" 965 if expression.args.get("percent"): 966 count = f"{count} PERCENT" 967 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 968 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 969 970 def filter_sql(self, expression: exp.Filter) -> str: 971 if self.AGGREGATE_FILTER_SUPPORTED: 972 this = self.sql(expression, "this") 973 where = self.sql(expression, "expression").strip() 974 return f"{this} FILTER({where})" 975 976 agg = expression.this.copy() 977 agg_arg = agg.this 978 cond = expression.expression.this 979 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 980 return self.sql(agg) 981 982 def hint_sql(self, expression: exp.Hint) -> str: 983 if not self.QUERY_HINTS: 984 self.unsupported("Hints are not supported") 985 return "" 986 987 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 988 989 def index_sql(self, expression: exp.Index) -> str: 990 unique = "UNIQUE " if expression.args.get("unique") else "" 991 primary = "PRIMARY " if expression.args.get("primary") else "" 992 amp = "AMP " if expression.args.get("amp") else "" 993 name = self.sql(expression, "this") 994 name = f"{name} " if name else "" 995 table = self.sql(expression, "table") 996 table = f"{self.INDEX_ON} {table}" if table else "" 997 using = self.sql(expression, "using") 998 using = f" USING {using}" if using else "" 999 index = "INDEX " if not table else "" 1000 columns = self.expressions(expression, key="columns", flat=True) 1001 columns = f"({columns})" if columns else "" 1002 partition_by = self.expressions(expression, key="partition_by", flat=True) 1003 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1004 where = self.sql(expression, "where") 1005 return f"{unique}{primary}{amp}{index}{name}{table}{using}{columns}{partition_by}{where}" 1006 1007 def identifier_sql(self, expression: exp.Identifier) -> str: 1008 text = expression.name 1009 lower = text.lower() 1010 text = lower if self.normalize and not expression.quoted else text 1011 text = text.replace(self.IDENTIFIER_END, self._escaped_identifier_end) 1012 if ( 1013 expression.quoted 1014 or self.can_identify(text, self.identify) 1015 or lower in self.RESERVED_KEYWORDS 1016 or (not self.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1017 ): 1018 text = f"{self.IDENTIFIER_START}{text}{self.IDENTIFIER_END}" 1019 return text 1020 1021 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1022 input_format = self.sql(expression, "input_format") 1023 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1024 output_format = self.sql(expression, "output_format") 1025 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1026 return self.sep().join((input_format, output_format)) 1027 1028 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1029 string = self.sql(exp.Literal.string(expression.name)) 1030 return f"{prefix}{string}" 1031 1032 def partition_sql(self, expression: exp.Partition) -> str: 1033 return f"PARTITION({self.expressions(expression, flat=True)})" 1034 1035 def properties_sql(self, expression: exp.Properties) -> str: 1036 root_properties = [] 1037 with_properties = [] 1038 1039 for p in expression.expressions: 1040 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1041 if p_loc == exp.Properties.Location.POST_WITH: 1042 with_properties.append(p.copy()) 1043 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1044 root_properties.append(p.copy()) 1045 1046 return self.root_properties( 1047 exp.Properties(expressions=root_properties) 1048 ) + self.with_properties(exp.Properties(expressions=with_properties)) 1049 1050 def root_properties(self, properties: exp.Properties) -> str: 1051 if properties.expressions: 1052 return self.sep() + self.expressions(properties, indent=False, sep=" ") 1053 return "" 1054 1055 def properties( 1056 self, 1057 properties: exp.Properties, 1058 prefix: str = "", 1059 sep: str = ", ", 1060 suffix: str = "", 1061 wrapped: bool = True, 1062 ) -> str: 1063 if properties.expressions: 1064 expressions = self.expressions(properties, sep=sep, indent=False) 1065 if expressions: 1066 expressions = self.wrap(expressions) if wrapped else expressions 1067 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}" 1068 return "" 1069 1070 def with_properties(self, properties: exp.Properties) -> str: 1071 return self.properties(properties, prefix=self.seg("WITH")) 1072 1073 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1074 properties_locs = defaultdict(list) 1075 for p in properties.expressions: 1076 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1077 if p_loc != exp.Properties.Location.UNSUPPORTED: 1078 properties_locs[p_loc].append(p.copy()) 1079 else: 1080 self.unsupported(f"Unsupported property {p.key}") 1081 1082 return properties_locs 1083 1084 def property_sql(self, expression: exp.Property) -> str: 1085 property_cls = expression.__class__ 1086 if property_cls == exp.Property: 1087 return f"{expression.name}={self.sql(expression, 'value')}" 1088 1089 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1090 if not property_name: 1091 self.unsupported(f"Unsupported property {expression.key}") 1092 1093 return f"{property_name}={self.sql(expression, 'this')}" 1094 1095 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1096 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1097 options = f" {options}" if options else "" 1098 return f"LIKE {self.sql(expression, 'this')}{options}" 1099 1100 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1101 no = "NO " if expression.args.get("no") else "" 1102 protection = " PROTECTION" if expression.args.get("protection") else "" 1103 return f"{no}FALLBACK{protection}" 1104 1105 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1106 no = "NO " if expression.args.get("no") else "" 1107 local = expression.args.get("local") 1108 local = f"{local} " if local else "" 1109 dual = "DUAL " if expression.args.get("dual") else "" 1110 before = "BEFORE " if expression.args.get("before") else "" 1111 after = "AFTER " if expression.args.get("after") else "" 1112 return f"{no}{local}{dual}{before}{after}JOURNAL" 1113 1114 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1115 freespace = self.sql(expression, "this") 1116 percent = " PERCENT" if expression.args.get("percent") else "" 1117 return f"FREESPACE={freespace}{percent}" 1118 1119 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1120 if expression.args.get("default"): 1121 property = "DEFAULT" 1122 elif expression.args.get("on"): 1123 property = "ON" 1124 else: 1125 property = "OFF" 1126 return f"CHECKSUM={property}" 1127 1128 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1129 if expression.args.get("no"): 1130 return "NO MERGEBLOCKRATIO" 1131 if expression.args.get("default"): 1132 return "DEFAULT MERGEBLOCKRATIO" 1133 1134 percent = " PERCENT" if expression.args.get("percent") else "" 1135 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1136 1137 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1138 default = expression.args.get("default") 1139 minimum = expression.args.get("minimum") 1140 maximum = expression.args.get("maximum") 1141 if default or minimum or maximum: 1142 if default: 1143 prop = "DEFAULT" 1144 elif minimum: 1145 prop = "MINIMUM" 1146 else: 1147 prop = "MAXIMUM" 1148 return f"{prop} DATABLOCKSIZE" 1149 units = expression.args.get("units") 1150 units = f" {units}" if units else "" 1151 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1152 1153 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1154 autotemp = expression.args.get("autotemp") 1155 always = expression.args.get("always") 1156 default = expression.args.get("default") 1157 manual = expression.args.get("manual") 1158 never = expression.args.get("never") 1159 1160 if autotemp is not None: 1161 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1162 elif always: 1163 prop = "ALWAYS" 1164 elif default: 1165 prop = "DEFAULT" 1166 elif manual: 1167 prop = "MANUAL" 1168 elif never: 1169 prop = "NEVER" 1170 return f"BLOCKCOMPRESSION={prop}" 1171 1172 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1173 no = expression.args.get("no") 1174 no = " NO" if no else "" 1175 concurrent = expression.args.get("concurrent") 1176 concurrent = " CONCURRENT" if concurrent else "" 1177 1178 for_ = "" 1179 if expression.args.get("for_all"): 1180 for_ = " FOR ALL" 1181 elif expression.args.get("for_insert"): 1182 for_ = " FOR INSERT" 1183 elif expression.args.get("for_none"): 1184 for_ = " FOR NONE" 1185 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}" 1186 1187 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1188 kind = expression.args.get("kind") 1189 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1190 for_or_in = expression.args.get("for_or_in") 1191 lock_type = expression.args.get("lock_type") 1192 override = " OVERRIDE" if expression.args.get("override") else "" 1193 return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}" 1194 1195 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1196 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1197 statistics = expression.args.get("statistics") 1198 statistics_sql = "" 1199 if statistics is not None: 1200 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1201 return f"{data_sql}{statistics_sql}" 1202 1203 def insert_sql(self, expression: exp.Insert) -> str: 1204 overwrite = expression.args.get("overwrite") 1205 1206 if isinstance(expression.this, exp.Directory): 1207 this = " OVERWRITE" if overwrite else " INTO" 1208 else: 1209 this = " OVERWRITE TABLE" if overwrite else " INTO" 1210 1211 alternative = expression.args.get("alternative") 1212 alternative = f" OR {alternative}" if alternative else "" 1213 ignore = " IGNORE" if expression.args.get("ignore") else "" 1214 1215 this = f"{this} {self.sql(expression, 'this')}" 1216 1217 exists = " IF EXISTS" if expression.args.get("exists") else "" 1218 partition_sql = ( 1219 f" {self.sql(expression, 'partition')}" if expression.args.get("partition") else "" 1220 ) 1221 where = self.sql(expression, "where") 1222 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1223 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1224 conflict = self.sql(expression, "conflict") 1225 by_name = " BY NAME" if expression.args.get("by_name") else "" 1226 returning = self.sql(expression, "returning") 1227 1228 if self.RETURNING_END: 1229 expression_sql = f"{expression_sql}{conflict}{returning}" 1230 else: 1231 expression_sql = f"{returning}{expression_sql}{conflict}" 1232 1233 sql = f"INSERT{alternative}{ignore}{this}{by_name}{exists}{partition_sql}{where}{expression_sql}" 1234 return self.prepend_ctes(expression, sql) 1235 1236 def intersect_sql(self, expression: exp.Intersect) -> str: 1237 return self.prepend_ctes( 1238 expression, 1239 self.set_operation(expression, self.intersect_op(expression)), 1240 ) 1241 1242 def intersect_op(self, expression: exp.Intersect) -> str: 1243 return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}" 1244 1245 def introducer_sql(self, expression: exp.Introducer) -> str: 1246 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1247 1248 def kill_sql(self, expression: exp.Kill) -> str: 1249 kind = self.sql(expression, "kind") 1250 kind = f" {kind}" if kind else "" 1251 this = self.sql(expression, "this") 1252 this = f" {this}" if this else "" 1253 return f"KILL{kind}{this}" 1254 1255 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1256 return expression.name.upper() 1257 1258 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1259 return expression.name.upper() 1260 1261 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1262 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1263 constraint = self.sql(expression, "constraint") 1264 if constraint: 1265 constraint = f"ON CONSTRAINT {constraint}" 1266 key = self.expressions(expression, key="key", flat=True) 1267 do = "" if expression.args.get("duplicate") else " DO " 1268 nothing = "NOTHING" if expression.args.get("nothing") else "" 1269 expressions = self.expressions(expression, flat=True) 1270 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1271 if expressions: 1272 expressions = f"UPDATE {set_keyword}{expressions}" 1273 return f"{self.seg(conflict)} {constraint}{key}{do}{nothing}{expressions}" 1274 1275 def returning_sql(self, expression: exp.Returning) -> str: 1276 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1277 1278 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1279 fields = expression.args.get("fields") 1280 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1281 escaped = expression.args.get("escaped") 1282 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1283 items = expression.args.get("collection_items") 1284 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1285 keys = expression.args.get("map_keys") 1286 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1287 lines = expression.args.get("lines") 1288 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1289 null = expression.args.get("null") 1290 null = f" NULL DEFINED AS {null}" if null else "" 1291 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1292 1293 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1294 return f"WITH ({self.expressions(expression, flat=True)})" 1295 1296 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1297 this = f"{self.sql(expression, 'this')} INDEX" 1298 target = self.sql(expression, "target") 1299 target = f" FOR {target}" if target else "" 1300 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1301 1302 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1303 table = ".".join( 1304 part 1305 for part in [ 1306 self.sql(expression, "catalog"), 1307 self.sql(expression, "db"), 1308 self.sql(expression, "this"), 1309 ] 1310 if part 1311 ) 1312 1313 version = self.sql(expression, "version") 1314 version = f" {version}" if version else "" 1315 alias = self.sql(expression, "alias") 1316 alias = f"{sep}{alias}" if alias else "" 1317 hints = self.expressions(expression, key="hints", sep=" ") 1318 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1319 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) 1320 pivots = f" {pivots}" if pivots else "" 1321 joins = self.expressions(expression, key="joins", sep="", skip_first=True) 1322 laterals = self.expressions(expression, key="laterals", sep="") 1323 1324 return f"{table}{version}{alias}{hints}{pivots}{joins}{laterals}" 1325 1326 def tablesample_sql( 1327 self, expression: exp.TableSample, seed_prefix: str = "SEED", sep=" AS " 1328 ) -> str: 1329 if self.ALIAS_POST_TABLESAMPLE and expression.this.alias: 1330 table = expression.this.copy() 1331 table.set("alias", None) 1332 this = self.sql(table) 1333 alias = f"{sep}{self.sql(expression.this, 'alias')}" 1334 else: 1335 this = self.sql(expression, "this") 1336 alias = "" 1337 method = self.sql(expression, "method") 1338 method = f"{method.upper()} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1339 numerator = self.sql(expression, "bucket_numerator") 1340 denominator = self.sql(expression, "bucket_denominator") 1341 field = self.sql(expression, "bucket_field") 1342 field = f" ON {field}" if field else "" 1343 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1344 percent = self.sql(expression, "percent") 1345 percent = f"{percent} PERCENT" if percent else "" 1346 rows = self.sql(expression, "rows") 1347 rows = f"{rows} ROWS" if rows else "" 1348 size = self.sql(expression, "size") 1349 if size and self.TABLESAMPLE_SIZE_IS_PERCENT: 1350 size = f"{size} PERCENT" 1351 seed = self.sql(expression, "seed") 1352 seed = f" {seed_prefix} ({seed})" if seed else "" 1353 kind = expression.args.get("kind", "TABLESAMPLE") 1354 return f"{this} {kind} {method}({bucket}{percent}{rows}{size}){seed}{alias}" 1355 1356 def pivot_sql(self, expression: exp.Pivot) -> str: 1357 expressions = self.expressions(expression, flat=True) 1358 1359 if expression.this: 1360 this = self.sql(expression, "this") 1361 on = f"{self.seg('ON')} {expressions}" 1362 using = self.expressions(expression, key="using", flat=True) 1363 using = f"{self.seg('USING')} {using}" if using else "" 1364 group = self.sql(expression, "group") 1365 return f"PIVOT {this}{on}{using}{group}" 1366 1367 alias = self.sql(expression, "alias") 1368 alias = f" AS {alias}" if alias else "" 1369 unpivot = expression.args.get("unpivot") 1370 direction = "UNPIVOT" if unpivot else "PIVOT" 1371 field = self.sql(expression, "field") 1372 include_nulls = expression.args.get("include_nulls") 1373 if include_nulls is not None: 1374 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1375 else: 1376 nulls = "" 1377 return f"{direction}{nulls}({expressions} FOR {field}){alias}" 1378 1379 def version_sql(self, expression: exp.Version) -> str: 1380 this = f"FOR {expression.name}" 1381 kind = expression.text("kind") 1382 expr = self.sql(expression, "expression") 1383 return f"{this} {kind} {expr}" 1384 1385 def tuple_sql(self, expression: exp.Tuple) -> str: 1386 return f"({self.expressions(expression, flat=True)})" 1387 1388 def update_sql(self, expression: exp.Update) -> str: 1389 this = self.sql(expression, "this") 1390 set_sql = self.expressions(expression, flat=True) 1391 from_sql = self.sql(expression, "from") 1392 where_sql = self.sql(expression, "where") 1393 returning = self.sql(expression, "returning") 1394 order = self.sql(expression, "order") 1395 limit = self.sql(expression, "limit") 1396 if self.RETURNING_END: 1397 expression_sql = f"{from_sql}{where_sql}{returning}" 1398 else: 1399 expression_sql = f"{returning}{from_sql}{where_sql}" 1400 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1401 return self.prepend_ctes(expression, sql) 1402 1403 def values_sql(self, expression: exp.Values) -> str: 1404 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 1405 if self.VALUES_AS_TABLE or not expression.find_ancestor(exp.From, exp.Join): 1406 args = self.expressions(expression) 1407 alias = self.sql(expression, "alias") 1408 values = f"VALUES{self.seg('')}{args}" 1409 values = ( 1410 f"({values})" 1411 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1412 else values 1413 ) 1414 return f"{values} AS {alias}" if alias else values 1415 1416 # Converts `VALUES...` expression into a series of select unions. 1417 expression = expression.copy() 1418 column_names = expression.alias and expression.args["alias"].columns 1419 1420 selects = [] 1421 1422 for i, tup in enumerate(expression.expressions): 1423 row = tup.expressions 1424 1425 if i == 0 and column_names: 1426 row = [ 1427 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 1428 ] 1429 1430 selects.append(exp.Select(expressions=row)) 1431 1432 if self.pretty: 1433 # This may result in poor performance for large-cardinality `VALUES` tables, due to 1434 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 1435 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 1436 subquery_expression: exp.Select | exp.Union = selects[0] 1437 if len(selects) > 1: 1438 for select in selects[1:]: 1439 subquery_expression = exp.union( 1440 subquery_expression, select, distinct=False, copy=False 1441 ) 1442 1443 return self.subquery_sql(subquery_expression.subquery(expression.alias, copy=False)) 1444 1445 alias = f" AS {expression.alias}" if expression.alias else "" 1446 unions = " UNION ALL ".join(self.sql(select) for select in selects) 1447 return f"({unions}){alias}" 1448 1449 def var_sql(self, expression: exp.Var) -> str: 1450 return self.sql(expression, "this") 1451 1452 def into_sql(self, expression: exp.Into) -> str: 1453 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1454 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 1455 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 1456 1457 def from_sql(self, expression: exp.From) -> str: 1458 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 1459 1460 def group_sql(self, expression: exp.Group) -> str: 1461 group_by = self.op_expressions("GROUP BY", expression) 1462 1463 if expression.args.get("all"): 1464 return f"{group_by} ALL" 1465 1466 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False) 1467 grouping_sets = ( 1468 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else "" 1469 ) 1470 1471 cube = expression.args.get("cube", []) 1472 if seq_get(cube, 0) is True: 1473 return f"{group_by}{self.seg('WITH CUBE')}" 1474 else: 1475 cube_sql = self.expressions(expression, key="cube", indent=False) 1476 cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else "" 1477 1478 rollup = expression.args.get("rollup", []) 1479 if seq_get(rollup, 0) is True: 1480 return f"{group_by}{self.seg('WITH ROLLUP')}" 1481 else: 1482 rollup_sql = self.expressions(expression, key="rollup", indent=False) 1483 rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else "" 1484 1485 groupings = csv( 1486 grouping_sets, 1487 cube_sql, 1488 rollup_sql, 1489 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 1490 sep=self.GROUPINGS_SEP, 1491 ) 1492 1493 if expression.args.get("expressions") and groupings: 1494 group_by = f"{group_by}{self.GROUPINGS_SEP}" 1495 1496 return f"{group_by}{groupings}" 1497 1498 def having_sql(self, expression: exp.Having) -> str: 1499 this = self.indent(self.sql(expression, "this")) 1500 return f"{self.seg('HAVING')}{self.sep()}{this}" 1501 1502 def connect_sql(self, expression: exp.Connect) -> str: 1503 start = self.sql(expression, "start") 1504 start = self.seg(f"START WITH {start}") if start else "" 1505 connect = self.sql(expression, "connect") 1506 connect = self.seg(f"CONNECT BY {connect}") 1507 return start + connect 1508 1509 def prior_sql(self, expression: exp.Prior) -> str: 1510 return f"PRIOR {self.sql(expression, 'this')}" 1511 1512 def join_sql(self, expression: exp.Join) -> str: 1513 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 1514 side = None 1515 else: 1516 side = expression.side 1517 1518 op_sql = " ".join( 1519 op 1520 for op in ( 1521 expression.method, 1522 "GLOBAL" if expression.args.get("global") else None, 1523 side, 1524 expression.kind, 1525 expression.hint if self.JOIN_HINTS else None, 1526 ) 1527 if op 1528 ) 1529 on_sql = self.sql(expression, "on") 1530 using = expression.args.get("using") 1531 1532 if not on_sql and using: 1533 on_sql = csv(*(self.sql(column) for column in using)) 1534 1535 this_sql = self.sql(expression, "this") 1536 1537 if on_sql: 1538 on_sql = self.indent(on_sql, skip_first=True) 1539 space = self.seg(" " * self.pad) if self.pretty else " " 1540 if using: 1541 on_sql = f"{space}USING ({on_sql})" 1542 else: 1543 on_sql = f"{space}ON {on_sql}" 1544 elif not op_sql: 1545 return f", {this_sql}" 1546 1547 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 1548 return f"{self.seg(op_sql)} {this_sql}{on_sql}" 1549 1550 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 1551 args = self.expressions(expression, flat=True) 1552 args = f"({args})" if len(args.split(",")) > 1 else args 1553 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 1554 1555 def lateral_sql(self, expression: exp.Lateral) -> str: 1556 this = self.sql(expression, "this") 1557 1558 if isinstance(expression.this, exp.Subquery): 1559 return f"LATERAL {this}" 1560 1561 if expression.args.get("view"): 1562 alias = expression.args["alias"] 1563 columns = self.expressions(alias, key="columns", flat=True) 1564 table = f" {alias.name}" if alias.name else "" 1565 columns = f" AS {columns}" if columns else "" 1566 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 1567 return f"{op_sql}{self.sep()}{this}{table}{columns}" 1568 1569 alias = self.sql(expression, "alias") 1570 alias = f" AS {alias}" if alias else "" 1571 return f"LATERAL {this}{alias}" 1572 1573 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 1574 this = self.sql(expression, "this") 1575 args = ", ".join( 1576 sql 1577 for sql in ( 1578 self.sql(expression, "offset"), 1579 self.sql(expression, "expression"), 1580 ) 1581 if sql 1582 ) 1583 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args}" 1584 1585 def offset_sql(self, expression: exp.Offset) -> str: 1586 this = self.sql(expression, "this") 1587 return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}" 1588 1589 def setitem_sql(self, expression: exp.SetItem) -> str: 1590 kind = self.sql(expression, "kind") 1591 kind = f"{kind} " if kind else "" 1592 this = self.sql(expression, "this") 1593 expressions = self.expressions(expression) 1594 collate = self.sql(expression, "collate") 1595 collate = f" COLLATE {collate}" if collate else "" 1596 global_ = "GLOBAL " if expression.args.get("global") else "" 1597 return f"{global_}{kind}{this}{expressions}{collate}" 1598 1599 def set_sql(self, expression: exp.Set) -> str: 1600 expressions = ( 1601 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 1602 ) 1603 tag = " TAG" if expression.args.get("tag") else "" 1604 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 1605 1606 def pragma_sql(self, expression: exp.Pragma) -> str: 1607 return f"PRAGMA {self.sql(expression, 'this')}" 1608 1609 def lock_sql(self, expression: exp.Lock) -> str: 1610 if not self.LOCKING_READS_SUPPORTED: 1611 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 1612 return "" 1613 1614 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 1615 expressions = self.expressions(expression, flat=True) 1616 expressions = f" OF {expressions}" if expressions else "" 1617 wait = expression.args.get("wait") 1618 1619 if wait is not None: 1620 if isinstance(wait, exp.Literal): 1621 wait = f" WAIT {self.sql(wait)}" 1622 else: 1623 wait = " NOWAIT" if wait else " SKIP LOCKED" 1624 1625 return f"{lock_type}{expressions}{wait or ''}" 1626 1627 def literal_sql(self, expression: exp.Literal) -> str: 1628 text = expression.this or "" 1629 if expression.is_string: 1630 text = f"{self.QUOTE_START}{self.escape_str(text)}{self.QUOTE_END}" 1631 return text 1632 1633 def escape_str(self, text: str) -> str: 1634 text = text.replace(self.QUOTE_END, self._escaped_quote_end) 1635 if self.UNESCAPED_SEQUENCE_TABLE: 1636 text = text.translate(self.UNESCAPED_SEQUENCE_TABLE) 1637 elif self.pretty: 1638 text = text.replace("\n", self.SENTINEL_LINE_BREAK) 1639 return text 1640 1641 def loaddata_sql(self, expression: exp.LoadData) -> str: 1642 local = " LOCAL" if expression.args.get("local") else "" 1643 inpath = f" INPATH {self.sql(expression, 'inpath')}" 1644 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 1645 this = f" INTO TABLE {self.sql(expression, 'this')}" 1646 partition = self.sql(expression, "partition") 1647 partition = f" {partition}" if partition else "" 1648 input_format = self.sql(expression, "input_format") 1649 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 1650 serde = self.sql(expression, "serde") 1651 serde = f" SERDE {serde}" if serde else "" 1652 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 1653 1654 def null_sql(self, *_) -> str: 1655 return "NULL" 1656 1657 def boolean_sql(self, expression: exp.Boolean) -> str: 1658 return "TRUE" if expression.this else "FALSE" 1659 1660 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 1661 this = self.sql(expression, "this") 1662 this = f"{this} " if this else this 1663 return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat) # type: ignore 1664 1665 def cluster_sql(self, expression: exp.Cluster) -> str: 1666 return self.op_expressions("CLUSTER BY", expression) 1667 1668 def distribute_sql(self, expression: exp.Distribute) -> str: 1669 return self.op_expressions("DISTRIBUTE BY", expression) 1670 1671 def sort_sql(self, expression: exp.Sort) -> str: 1672 return self.op_expressions("SORT BY", expression) 1673 1674 def ordered_sql(self, expression: exp.Ordered) -> str: 1675 desc = expression.args.get("desc") 1676 asc = not desc 1677 1678 nulls_first = expression.args.get("nulls_first") 1679 nulls_last = not nulls_first 1680 nulls_are_large = self.NULL_ORDERING == "nulls_are_large" 1681 nulls_are_small = self.NULL_ORDERING == "nulls_are_small" 1682 nulls_are_last = self.NULL_ORDERING == "nulls_are_last" 1683 1684 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 1685 nulls_sort_change = "" 1686 if nulls_first and ( 1687 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 1688 ): 1689 nulls_sort_change = " NULLS FIRST" 1690 elif ( 1691 nulls_last 1692 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 1693 and not nulls_are_last 1694 ): 1695 nulls_sort_change = " NULLS LAST" 1696 1697 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 1698 self.unsupported( 1699 "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect" 1700 ) 1701 nulls_sort_change = "" 1702 1703 return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}" 1704 1705 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 1706 partition = self.partition_by_sql(expression) 1707 order = self.sql(expression, "order") 1708 measures = self.expressions(expression, key="measures") 1709 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 1710 rows = self.sql(expression, "rows") 1711 rows = self.seg(rows) if rows else "" 1712 after = self.sql(expression, "after") 1713 after = self.seg(after) if after else "" 1714 pattern = self.sql(expression, "pattern") 1715 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 1716 definition_sqls = [ 1717 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 1718 for definition in expression.args.get("define", []) 1719 ] 1720 definitions = self.expressions(sqls=definition_sqls) 1721 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 1722 body = "".join( 1723 ( 1724 partition, 1725 order, 1726 measures, 1727 rows, 1728 after, 1729 pattern, 1730 define, 1731 ) 1732 ) 1733 alias = self.sql(expression, "alias") 1734 alias = f" {alias}" if alias else "" 1735 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 1736 1737 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 1738 limit: t.Optional[exp.Fetch | exp.Limit] = expression.args.get("limit") 1739 1740 # If the limit is generated as TOP, we need to ensure it's not generated twice 1741 with_offset_limit_modifiers = not isinstance(limit, exp.Limit) or not self.LIMIT_IS_TOP 1742 1743 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 1744 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 1745 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 1746 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 1747 1748 fetch = isinstance(limit, exp.Fetch) 1749 1750 offset_limit_modifiers = ( 1751 self.offset_limit_modifiers(expression, fetch, limit) 1752 if with_offset_limit_modifiers 1753 else [] 1754 ) 1755 1756 return csv( 1757 *sqls, 1758 *[self.sql(join) for join in expression.args.get("joins") or []], 1759 self.sql(expression, "connect"), 1760 self.sql(expression, "match"), 1761 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 1762 self.sql(expression, "where"), 1763 self.sql(expression, "group"), 1764 self.sql(expression, "having"), 1765 *self.after_having_modifiers(expression), 1766 self.sql(expression, "order"), 1767 *offset_limit_modifiers, 1768 *self.after_limit_modifiers(expression), 1769 sep="", 1770 ) 1771 1772 def offset_limit_modifiers( 1773 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 1774 ) -> t.List[str]: 1775 return [ 1776 self.sql(expression, "offset") if fetch else self.sql(limit), 1777 self.sql(limit) if fetch else self.sql(expression, "offset"), 1778 ] 1779 1780 def after_having_modifiers(self, expression: exp.Expression) -> t.List[str]: 1781 return [ 1782 self.sql(expression, "qualify"), 1783 self.seg("WINDOW ") + self.expressions(expression, key="windows", flat=True) 1784 if expression.args.get("windows") 1785 else "", 1786 self.sql(expression, "distribute"), 1787 self.sql(expression, "sort"), 1788 self.sql(expression, "cluster"), 1789 ] 1790 1791 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 1792 locks = self.expressions(expression, key="locks", sep=" ") 1793 locks = f" {locks}" if locks else "" 1794 return [locks, self.sql(expression, "sample")] 1795 1796 def select_sql(self, expression: exp.Select) -> str: 1797 hint = self.sql(expression, "hint") 1798 distinct = self.sql(expression, "distinct") 1799 distinct = f" {distinct}" if distinct else "" 1800 kind = self.sql(expression, "kind").upper() 1801 limit = expression.args.get("limit") 1802 top = ( 1803 self.limit_sql(limit, top=True) 1804 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP 1805 else "" 1806 ) 1807 1808 expressions = self.expressions(expression) 1809 1810 if kind: 1811 if kind in self.SELECT_KINDS: 1812 kind = f" AS {kind}" 1813 else: 1814 if kind == "STRUCT": 1815 expressions = self.expressions( 1816 sqls=[ 1817 self.sql( 1818 exp.Struct( 1819 expressions=[ 1820 exp.column(e.output_name).eq( 1821 e.this if isinstance(e, exp.Alias) else e 1822 ) 1823 for e in expression.expressions 1824 ] 1825 ) 1826 ) 1827 ] 1828 ) 1829 kind = "" 1830 1831 expressions = f"{self.sep()}{expressions}" if expressions else expressions 1832 sql = self.query_modifiers( 1833 expression, 1834 f"SELECT{top}{hint}{distinct}{kind}{expressions}", 1835 self.sql(expression, "into", comment=False), 1836 self.sql(expression, "from", comment=False), 1837 ) 1838 return self.prepend_ctes(expression, sql) 1839 1840 def schema_sql(self, expression: exp.Schema) -> str: 1841 this = self.sql(expression, "this") 1842 this = f"{this} " if this else "" 1843 sql = self.schema_columns_sql(expression) 1844 return f"{this}{sql}" 1845 1846 def schema_columns_sql(self, expression: exp.Schema) -> str: 1847 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 1848 1849 def star_sql(self, expression: exp.Star) -> str: 1850 except_ = self.expressions(expression, key="except", flat=True) 1851 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else "" 1852 replace = self.expressions(expression, key="replace", flat=True) 1853 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else "" 1854 return f"*{except_}{replace}" 1855 1856 def parameter_sql(self, expression: exp.Parameter) -> str: 1857 this = self.sql(expression, "this") 1858 return f"{self.PARAMETER_TOKEN}{this}" if self.SUPPORTS_PARAMETERS else this 1859 1860 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 1861 this = self.sql(expression, "this") 1862 kind = expression.text("kind") 1863 if kind: 1864 kind = f"{kind}." 1865 return f"@@{kind}{this}" 1866 1867 def placeholder_sql(self, expression: exp.Placeholder) -> str: 1868 return f":{expression.name}" if expression.name else "?" 1869 1870 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 1871 alias = self.sql(expression, "alias") 1872 alias = f"{sep}{alias}" if alias else "" 1873 1874 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) 1875 pivots = f" {pivots}" if pivots else "" 1876 1877 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 1878 return self.prepend_ctes(expression, sql) 1879 1880 def qualify_sql(self, expression: exp.Qualify) -> str: 1881 this = self.indent(self.sql(expression, "this")) 1882 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 1883 1884 def union_sql(self, expression: exp.Union) -> str: 1885 return self.prepend_ctes( 1886 expression, 1887 self.set_operation(expression, self.union_op(expression)), 1888 ) 1889 1890 def union_op(self, expression: exp.Union) -> str: 1891 kind = " DISTINCT" if self.EXPLICIT_UNION else "" 1892 kind = kind if expression.args.get("distinct") else " ALL" 1893 by_name = " BY NAME" if expression.args.get("by_name") else "" 1894 return f"UNION{kind}{by_name}" 1895 1896 def unnest_sql(self, expression: exp.Unnest) -> str: 1897 args = self.expressions(expression, flat=True) 1898 1899 alias = expression.args.get("alias") 1900 offset = expression.args.get("offset") 1901 1902 if self.UNNEST_WITH_ORDINALITY: 1903 if alias and isinstance(offset, exp.Expression): 1904 alias = alias.copy() 1905 alias.append("columns", offset.copy()) 1906 1907 if alias and self.UNNEST_COLUMN_ONLY: 1908 columns = alias.columns 1909 alias = self.sql(columns[0]) if columns else "" 1910 else: 1911 alias = self.sql(alias) 1912 1913 alias = f" AS {alias}" if alias else alias 1914 if self.UNNEST_WITH_ORDINALITY: 1915 suffix = f" WITH ORDINALITY{alias}" if offset else alias 1916 else: 1917 if isinstance(offset, exp.Expression): 1918 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 1919 elif offset: 1920 suffix = f"{alias} WITH OFFSET" 1921 else: 1922 suffix = alias 1923 1924 return f"UNNEST({args}){suffix}" 1925 1926 def where_sql(self, expression: exp.Where) -> str: 1927 this = self.indent(self.sql(expression, "this")) 1928 return f"{self.seg('WHERE')}{self.sep()}{this}" 1929 1930 def window_sql(self, expression: exp.Window) -> str: 1931 this = self.sql(expression, "this") 1932 partition = self.partition_by_sql(expression) 1933 order = expression.args.get("order") 1934 order = self.order_sql(order, flat=True) if order else "" 1935 spec = self.sql(expression, "spec") 1936 alias = self.sql(expression, "alias") 1937 over = self.sql(expression, "over") or "OVER" 1938 1939 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 1940 1941 first = expression.args.get("first") 1942 if first is None: 1943 first = "" 1944 else: 1945 first = "FIRST" if first else "LAST" 1946 1947 if not partition and not order and not spec and alias: 1948 return f"{this} {alias}" 1949 1950 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 1951 return f"{this} ({args})" 1952 1953 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 1954 partition = self.expressions(expression, key="partition_by", flat=True) 1955 return f"PARTITION BY {partition}" if partition else "" 1956 1957 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 1958 kind = self.sql(expression, "kind") 1959 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 1960 end = ( 1961 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 1962 or "CURRENT ROW" 1963 ) 1964 return f"{kind} BETWEEN {start} AND {end}" 1965 1966 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1967 this = self.sql(expression, "this") 1968 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 1969 return f"{this} WITHIN GROUP ({expression_sql})" 1970 1971 def between_sql(self, expression: exp.Between) -> str: 1972 this = self.sql(expression, "this") 1973 low = self.sql(expression, "low") 1974 high = self.sql(expression, "high") 1975 return f"{this} BETWEEN {low} AND {high}" 1976 1977 def bracket_sql(self, expression: exp.Bracket) -> str: 1978 expressions = apply_index_offset(expression.this, expression.expressions, self.INDEX_OFFSET) 1979 expressions_sql = ", ".join(self.sql(e) for e in expressions) 1980 1981 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 1982 1983 def safebracket_sql(self, expression: exp.SafeBracket) -> str: 1984 return self.bracket_sql(expression) 1985 1986 def all_sql(self, expression: exp.All) -> str: 1987 return f"ALL {self.wrap(expression)}" 1988 1989 def any_sql(self, expression: exp.Any) -> str: 1990 this = self.sql(expression, "this") 1991 if isinstance(expression.this, exp.Subqueryable): 1992 this = self.wrap(this) 1993 return f"ANY {this}" 1994 1995 def exists_sql(self, expression: exp.Exists) -> str: 1996 return f"EXISTS{self.wrap(expression)}" 1997 1998 def case_sql(self, expression: exp.Case) -> str: 1999 this = self.sql(expression, "this") 2000 statements = [f"CASE {this}" if this else "CASE"] 2001 2002 for e in expression.args["ifs"]: 2003 statements.append(f"WHEN {self.sql(e, 'this')}") 2004 statements.append(f"THEN {self.sql(e, 'true')}") 2005 2006 default = self.sql(expression, "default") 2007 2008 if default: 2009 statements.append(f"ELSE {default}") 2010 2011 statements.append("END") 2012 2013 if self.pretty and self.text_width(statements) > self.max_text_width: 2014 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2015 2016 return " ".join(statements) 2017 2018 def constraint_sql(self, expression: exp.Constraint) -> str: 2019 this = self.sql(expression, "this") 2020 expressions = self.expressions(expression, flat=True) 2021 return f"CONSTRAINT {this} {expressions}" 2022 2023 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2024 order = expression.args.get("order") 2025 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2026 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2027 2028 def extract_sql(self, expression: exp.Extract) -> str: 2029 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2030 expression_sql = self.sql(expression, "expression") 2031 return f"EXTRACT({this} FROM {expression_sql})" 2032 2033 def trim_sql(self, expression: exp.Trim) -> str: 2034 trim_type = self.sql(expression, "position") 2035 2036 if trim_type == "LEADING": 2037 return self.func("LTRIM", expression.this) 2038 elif trim_type == "TRAILING": 2039 return self.func("RTRIM", expression.this) 2040 else: 2041 return self.func("TRIM", expression.this, expression.expression) 2042 2043 def safeconcat_sql(self, expression: exp.SafeConcat) -> str: 2044 expressions = expression.expressions 2045 if self.STRICT_STRING_CONCAT: 2046 expressions = (exp.cast(e, "text") for e in expressions) 2047 return self.func("CONCAT", *expressions) 2048 2049 def check_sql(self, expression: exp.Check) -> str: 2050 this = self.sql(expression, key="this") 2051 return f"CHECK ({this})" 2052 2053 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2054 expressions = self.expressions(expression, flat=True) 2055 reference = self.sql(expression, "reference") 2056 reference = f" {reference}" if reference else "" 2057 delete = self.sql(expression, "delete") 2058 delete = f" ON DELETE {delete}" if delete else "" 2059 update = self.sql(expression, "update") 2060 update = f" ON UPDATE {update}" if update else "" 2061 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2062 2063 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2064 expressions = self.expressions(expression, flat=True) 2065 options = self.expressions(expression, key="options", flat=True, sep=" ") 2066 options = f" {options}" if options else "" 2067 return f"PRIMARY KEY ({expressions}){options}" 2068 2069 def if_sql(self, expression: exp.If) -> str: 2070 expression = expression.copy() 2071 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2072 2073 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2074 modifier = expression.args.get("modifier") 2075 modifier = f" {modifier}" if modifier else "" 2076 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2077 2078 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2079 return f"{self.sql(expression, 'this')}: {self.sql(expression, 'expression')}" 2080 2081 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2082 return f"{self.sql(expression, 'this')} FORMAT JSON" 2083 2084 def jsonobject_sql(self, expression: exp.JSONObject) -> str: 2085 null_handling = expression.args.get("null_handling") 2086 null_handling = f" {null_handling}" if null_handling else "" 2087 unique_keys = expression.args.get("unique_keys") 2088 if unique_keys is not None: 2089 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2090 else: 2091 unique_keys = "" 2092 return_type = self.sql(expression, "return_type") 2093 return_type = f" RETURNING {return_type}" if return_type else "" 2094 encoding = self.sql(expression, "encoding") 2095 encoding = f" ENCODING {encoding}" if encoding else "" 2096 return self.func( 2097 "JSON_OBJECT", 2098 *expression.expressions, 2099 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2100 ) 2101 2102 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2103 null_handling = expression.args.get("null_handling") 2104 null_handling = f" {null_handling}" if null_handling else "" 2105 return_type = self.sql(expression, "return_type") 2106 return_type = f" RETURNING {return_type}" if return_type else "" 2107 strict = " STRICT" if expression.args.get("strict") else "" 2108 return self.func( 2109 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2110 ) 2111 2112 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2113 this = self.sql(expression, "this") 2114 order = self.sql(expression, "order") 2115 null_handling = expression.args.get("null_handling") 2116 null_handling = f" {null_handling}" if null_handling else "" 2117 return_type = self.sql(expression, "return_type") 2118 return_type = f" RETURNING {return_type}" if return_type else "" 2119 strict = " STRICT" if expression.args.get("strict") else "" 2120 return self.func( 2121 "JSON_ARRAYAGG", 2122 this, 2123 suffix=f"{order}{null_handling}{return_type}{strict})", 2124 ) 2125 2126 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2127 this = self.sql(expression, "this") 2128 kind = self.sql(expression, "kind") 2129 kind = f" {kind}" if kind else "" 2130 path = self.sql(expression, "path") 2131 path = f" PATH {path}" if path else "" 2132 return f"{this}{kind}{path}" 2133 2134 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2135 this = self.sql(expression, "this") 2136 path = self.sql(expression, "path") 2137 path = f", {path}" if path else "" 2138 error_handling = expression.args.get("error_handling") 2139 error_handling = f" {error_handling}" if error_handling else "" 2140 empty_handling = expression.args.get("empty_handling") 2141 empty_handling = f" {empty_handling}" if empty_handling else "" 2142 columns = f" COLUMNS ({self.expressions(expression, skip_first=True)})" 2143 return self.func( 2144 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling}{columns})" 2145 ) 2146 2147 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2148 this = self.sql(expression, "this") 2149 kind = self.sql(expression, "kind") 2150 path = self.sql(expression, "path") 2151 path = f" {path}" if path else "" 2152 as_json = " AS JSON" if expression.args.get("as_json") else "" 2153 return f"{this} {kind}{path}{as_json}" 2154 2155 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2156 this = self.sql(expression, "this") 2157 path = self.sql(expression, "path") 2158 path = f", {path}" if path else "" 2159 expressions = self.expressions(expression) 2160 with_ = ( 2161 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2162 if expressions 2163 else "" 2164 ) 2165 return f"OPENJSON({this}{path}){with_}" 2166 2167 def in_sql(self, expression: exp.In) -> str: 2168 query = expression.args.get("query") 2169 unnest = expression.args.get("unnest") 2170 field = expression.args.get("field") 2171 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2172 2173 if query: 2174 in_sql = self.wrap(query) 2175 elif unnest: 2176 in_sql = self.in_unnest_op(unnest) 2177 elif field: 2178 in_sql = self.sql(field) 2179 else: 2180 in_sql = f"({self.expressions(expression, flat=True)})" 2181 2182 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2183 2184 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2185 return f"(SELECT {self.sql(unnest)})" 2186 2187 def interval_sql(self, expression: exp.Interval) -> str: 2188 unit = self.sql(expression, "unit") 2189 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2190 unit = self.TIME_PART_SINGULARS.get(unit.lower(), unit) 2191 unit = f" {unit}" if unit else "" 2192 2193 if self.SINGLE_STRING_INTERVAL: 2194 this = expression.this.name if expression.this else "" 2195 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2196 2197 this = self.sql(expression, "this") 2198 if this: 2199 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2200 this = f" {this}" if unwrapped else f" ({this})" 2201 2202 return f"INTERVAL{this}{unit}" 2203 2204 def return_sql(self, expression: exp.Return) -> str: 2205 return f"RETURN {self.sql(expression, 'this')}" 2206 2207 def reference_sql(self, expression: exp.Reference) -> str: 2208 this = self.sql(expression, "this") 2209 expressions = self.expressions(expression, flat=True) 2210 expressions = f"({expressions})" if expressions else "" 2211 options = self.expressions(expression, key="options", flat=True, sep=" ") 2212 options = f" {options}" if options else "" 2213 return f"REFERENCES {this}{expressions}{options}" 2214 2215 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2216 return self.func(expression.name, *expression.expressions) 2217 2218 def paren_sql(self, expression: exp.Paren) -> str: 2219 if isinstance(expression.unnest(), exp.Select): 2220 sql = self.wrap(expression) 2221 else: 2222 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2223 sql = f"({sql}{self.seg(')', sep='')}" 2224 2225 return self.prepend_ctes(expression, sql) 2226 2227 def neg_sql(self, expression: exp.Neg) -> str: 2228 # This makes sure we don't convert "- - 5" to "--5", which is a comment 2229 this_sql = self.sql(expression, "this") 2230 sep = " " if this_sql[0] == "-" else "" 2231 return f"-{sep}{this_sql}" 2232 2233 def not_sql(self, expression: exp.Not) -> str: 2234 return f"NOT {self.sql(expression, 'this')}" 2235 2236 def alias_sql(self, expression: exp.Alias) -> str: 2237 alias = self.sql(expression, "alias") 2238 alias = f" AS {alias}" if alias else "" 2239 return f"{self.sql(expression, 'this')}{alias}" 2240 2241 def aliases_sql(self, expression: exp.Aliases) -> str: 2242 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 2243 2244 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 2245 this = self.sql(expression, "this") 2246 zone = self.sql(expression, "zone") 2247 return f"{this} AT TIME ZONE {zone}" 2248 2249 def add_sql(self, expression: exp.Add) -> str: 2250 return self.binary(expression, "+") 2251 2252 def and_sql(self, expression: exp.And) -> str: 2253 return self.connector_sql(expression, "AND") 2254 2255 def xor_sql(self, expression: exp.Xor) -> str: 2256 return self.connector_sql(expression, "XOR") 2257 2258 def connector_sql(self, expression: exp.Connector, op: str) -> str: 2259 if not self.pretty: 2260 return self.binary(expression, op) 2261 2262 sqls = tuple( 2263 self.maybe_comment(self.sql(e), e, e.parent.comments or []) if i != 1 else self.sql(e) 2264 for i, e in enumerate(expression.flatten(unnest=False)) 2265 ) 2266 2267 sep = "\n" if self.text_width(sqls) > self.max_text_width else " " 2268 return f"{sep}{op} ".join(sqls) 2269 2270 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 2271 return self.binary(expression, "&") 2272 2273 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 2274 return self.binary(expression, "<<") 2275 2276 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 2277 return f"~{self.sql(expression, 'this')}" 2278 2279 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 2280 return self.binary(expression, "|") 2281 2282 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 2283 return self.binary(expression, ">>") 2284 2285 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 2286 return self.binary(expression, "^") 2287 2288 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 2289 format_sql = self.sql(expression, "format") 2290 format_sql = f" FORMAT {format_sql}" if format_sql else "" 2291 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')}{format_sql})" 2292 2293 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 2294 zone = self.sql(expression, "this") 2295 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 2296 2297 def collate_sql(self, expression: exp.Collate) -> str: 2298 return self.binary(expression, "COLLATE") 2299 2300 def command_sql(self, expression: exp.Command) -> str: 2301 return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}" 2302 2303 def comment_sql(self, expression: exp.Comment) -> str: 2304 this = self.sql(expression, "this") 2305 kind = expression.args["kind"] 2306 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 2307 expression_sql = self.sql(expression, "expression") 2308 return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}" 2309 2310 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 2311 this = self.sql(expression, "this") 2312 delete = " DELETE" if expression.args.get("delete") else "" 2313 recompress = self.sql(expression, "recompress") 2314 recompress = f" RECOMPRESS {recompress}" if recompress else "" 2315 to_disk = self.sql(expression, "to_disk") 2316 to_disk = f" TO DISK {to_disk}" if to_disk else "" 2317 to_volume = self.sql(expression, "to_volume") 2318 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 2319 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 2320 2321 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 2322 where = self.sql(expression, "where") 2323 group = self.sql(expression, "group") 2324 aggregates = self.expressions(expression, key="aggregates") 2325 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 2326 2327 if not (where or group or aggregates) and len(expression.expressions) == 1: 2328 return f"TTL {self.expressions(expression, flat=True)}" 2329 2330 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 2331 2332 def transaction_sql(self, expression: exp.Transaction) -> str: 2333 return "BEGIN" 2334 2335 def commit_sql(self, expression: exp.Commit) -> str: 2336 chain = expression.args.get("chain") 2337 if chain is not None: 2338 chain = " AND CHAIN" if chain else " AND NO CHAIN" 2339 2340 return f"COMMIT{chain or ''}" 2341 2342 def rollback_sql(self, expression: exp.Rollback) -> str: 2343 savepoint = expression.args.get("savepoint") 2344 savepoint = f" TO {savepoint}" if savepoint else "" 2345 return f"ROLLBACK{savepoint}" 2346 2347 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 2348 this = self.sql(expression, "this") 2349 2350 dtype = self.sql(expression, "dtype") 2351 if dtype: 2352 collate = self.sql(expression, "collate") 2353 collate = f" COLLATE {collate}" if collate else "" 2354 using = self.sql(expression, "using") 2355 using = f" USING {using}" if using else "" 2356 return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}" 2357 2358 default = self.sql(expression, "default") 2359 if default: 2360 return f"ALTER COLUMN {this} SET DEFAULT {default}" 2361 2362 if not expression.args.get("drop"): 2363 self.unsupported("Unsupported ALTER COLUMN syntax") 2364 2365 return f"ALTER COLUMN {this} DROP DEFAULT" 2366 2367 def renametable_sql(self, expression: exp.RenameTable) -> str: 2368 if not self.RENAME_TABLE_WITH_DB: 2369 # Remove db from tables 2370 expression = expression.transform( 2371 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 2372 ) 2373 this = self.sql(expression, "this") 2374 return f"RENAME TO {this}" 2375 2376 def altertable_sql(self, expression: exp.AlterTable) -> str: 2377 actions = expression.args["actions"] 2378 2379 if isinstance(actions[0], exp.ColumnDef): 2380 if self.ALTER_TABLE_ADD_COLUMN_KEYWORD: 2381 actions = self.expressions( 2382 expression, 2383 key="actions", 2384 prefix="ADD COLUMN ", 2385 ) 2386 else: 2387 actions = f"ADD {self.expressions(expression, key='actions')}" 2388 elif isinstance(actions[0], exp.Schema): 2389 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 2390 elif isinstance(actions[0], exp.Delete): 2391 actions = self.expressions(expression, key="actions", flat=True) 2392 else: 2393 actions = self.expressions(expression, key="actions") 2394 2395 exists = " IF EXISTS" if expression.args.get("exists") else "" 2396 only = " ONLY" if expression.args.get("only") else "" 2397 return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')} {actions}" 2398 2399 def droppartition_sql(self, expression: exp.DropPartition) -> str: 2400 expressions = self.expressions(expression) 2401 exists = " IF EXISTS " if expression.args.get("exists") else " " 2402 return f"DROP{exists}{expressions}" 2403 2404 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 2405 this = self.sql(expression, "this") 2406 expression_ = self.sql(expression, "expression") 2407 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD" 2408 2409 enforced = expression.args.get("enforced") 2410 if enforced is not None: 2411 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}" 2412 2413 return f"{add_constraint} {expression_}" 2414 2415 def distinct_sql(self, expression: exp.Distinct) -> str: 2416 this = self.expressions(expression, flat=True) 2417 this = f" {this}" if this else "" 2418 2419 on = self.sql(expression, "on") 2420 on = f" ON {on}" if on else "" 2421 return f"DISTINCT{this}{on}" 2422 2423 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 2424 return f"{self.sql(expression, 'this')} IGNORE NULLS" 2425 2426 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 2427 return f"{self.sql(expression, 'this')} RESPECT NULLS" 2428 2429 def intdiv_sql(self, expression: exp.IntDiv) -> str: 2430 return self.sql( 2431 exp.Cast( 2432 this=exp.Div(this=expression.this.copy(), expression=expression.expression.copy()), 2433 to=exp.DataType(this=exp.DataType.Type.INT), 2434 ) 2435 ) 2436 2437 def dpipe_sql(self, expression: exp.DPipe) -> str: 2438 return self.binary(expression, "||") 2439 2440 def safedpipe_sql(self, expression: exp.SafeDPipe) -> str: 2441 if self.STRICT_STRING_CONCAT: 2442 return self.func("CONCAT", *(exp.cast(e, "text") for e in expression.flatten())) 2443 return self.dpipe_sql(expression) 2444 2445 def div_sql(self, expression: exp.Div) -> str: 2446 return self.binary(expression, "/") 2447 2448 def overlaps_sql(self, expression: exp.Overlaps) -> str: 2449 return self.binary(expression, "OVERLAPS") 2450 2451 def distance_sql(self, expression: exp.Distance) -> str: 2452 return self.binary(expression, "<->") 2453 2454 def dot_sql(self, expression: exp.Dot) -> str: 2455 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 2456 2457 def eq_sql(self, expression: exp.EQ) -> str: 2458 return self.binary(expression, "=") 2459 2460 def escape_sql(self, expression: exp.Escape) -> str: 2461 return self.binary(expression, "ESCAPE") 2462 2463 def glob_sql(self, expression: exp.Glob) -> str: 2464 return self.binary(expression, "GLOB") 2465 2466 def gt_sql(self, expression: exp.GT) -> str: 2467 return self.binary(expression, ">") 2468 2469 def gte_sql(self, expression: exp.GTE) -> str: 2470 return self.binary(expression, ">=") 2471 2472 def ilike_sql(self, expression: exp.ILike) -> str: 2473 return self.binary(expression, "ILIKE") 2474 2475 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 2476 return self.binary(expression, "ILIKE ANY") 2477 2478 def is_sql(self, expression: exp.Is) -> str: 2479 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 2480 return self.sql( 2481 expression.this if expression.expression.this else exp.not_(expression.this) 2482 ) 2483 return self.binary(expression, "IS") 2484 2485 def like_sql(self, expression: exp.Like) -> str: 2486 return self.binary(expression, "LIKE") 2487 2488 def likeany_sql(self, expression: exp.LikeAny) -> str: 2489 return self.binary(expression, "LIKE ANY") 2490 2491 def similarto_sql(self, expression: exp.SimilarTo) -> str: 2492 return self.binary(expression, "SIMILAR TO") 2493 2494 def lt_sql(self, expression: exp.LT) -> str: 2495 return self.binary(expression, "<") 2496 2497 def lte_sql(self, expression: exp.LTE) -> str: 2498 return self.binary(expression, "<=") 2499 2500 def mod_sql(self, expression: exp.Mod) -> str: 2501 return self.binary(expression, "%") 2502 2503 def mul_sql(self, expression: exp.Mul) -> str: 2504 return self.binary(expression, "*") 2505 2506 def neq_sql(self, expression: exp.NEQ) -> str: 2507 return self.binary(expression, "<>") 2508 2509 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 2510 return self.binary(expression, "IS NOT DISTINCT FROM") 2511 2512 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 2513 return self.binary(expression, "IS DISTINCT FROM") 2514 2515 def or_sql(self, expression: exp.Or) -> str: 2516 return self.connector_sql(expression, "OR") 2517 2518 def slice_sql(self, expression: exp.Slice) -> str: 2519 return self.binary(expression, ":") 2520 2521 def sub_sql(self, expression: exp.Sub) -> str: 2522 return self.binary(expression, "-") 2523 2524 def trycast_sql(self, expression: exp.TryCast) -> str: 2525 return self.cast_sql(expression, safe_prefix="TRY_") 2526 2527 def use_sql(self, expression: exp.Use) -> str: 2528 kind = self.sql(expression, "kind") 2529 kind = f" {kind}" if kind else "" 2530 this = self.sql(expression, "this") 2531 this = f" {this}" if this else "" 2532 return f"USE{kind}{this}" 2533 2534 def binary(self, expression: exp.Binary, op: str) -> str: 2535 op = self.maybe_comment(op, comments=expression.comments) 2536 return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}" 2537 2538 def function_fallback_sql(self, expression: exp.Func) -> str: 2539 args = [] 2540 2541 for key in expression.arg_types: 2542 arg_value = expression.args.get(key) 2543 2544 if isinstance(arg_value, list): 2545 for value in arg_value: 2546 args.append(value) 2547 elif arg_value is not None: 2548 args.append(arg_value) 2549 2550 if self.normalize_functions: 2551 name = expression.sql_name() 2552 else: 2553 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 2554 2555 return self.func(name, *args) 2556 2557 def func( 2558 self, 2559 name: str, 2560 *args: t.Optional[exp.Expression | str], 2561 prefix: str = "(", 2562 suffix: str = ")", 2563 ) -> str: 2564 return f"{self.normalize_func(name)}{prefix}{self.format_args(*args)}{suffix}" 2565 2566 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 2567 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None) 2568 if self.pretty and self.text_width(arg_sqls) > self.max_text_width: 2569 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 2570 return ", ".join(arg_sqls) 2571 2572 def text_width(self, args: t.Iterable) -> int: 2573 return sum(len(arg) for arg in args) 2574 2575 def format_time(self, expression: exp.Expression) -> t.Optional[str]: 2576 return format_time( 2577 self.sql(expression, "format"), self.INVERSE_TIME_MAPPING, self.INVERSE_TIME_TRIE 2578 ) 2579 2580 def expressions( 2581 self, 2582 expression: t.Optional[exp.Expression] = None, 2583 key: t.Optional[str] = None, 2584 sqls: t.Optional[t.List[str]] = None, 2585 flat: bool = False, 2586 indent: bool = True, 2587 skip_first: bool = False, 2588 sep: str = ", ", 2589 prefix: str = "", 2590 ) -> str: 2591 expressions = expression.args.get(key or "expressions") if expression else sqls 2592 2593 if not expressions: 2594 return "" 2595 2596 if flat: 2597 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 2598 2599 num_sqls = len(expressions) 2600 2601 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly 2602 pad = " " * self.pad 2603 stripped_sep = sep.strip() 2604 2605 result_sqls = [] 2606 for i, e in enumerate(expressions): 2607 sql = self.sql(e, comment=False) 2608 if not sql: 2609 continue 2610 2611 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 2612 2613 if self.pretty: 2614 if self.leading_comma: 2615 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}") 2616 else: 2617 result_sqls.append( 2618 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}" 2619 ) 2620 else: 2621 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 2622 2623 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls) 2624 return self.indent(result_sql, skip_first=skip_first) if indent else result_sql 2625 2626 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 2627 flat = flat or isinstance(expression.parent, exp.Properties) 2628 expressions_sql = self.expressions(expression, flat=flat) 2629 if flat: 2630 return f"{op} {expressions_sql}" 2631 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 2632 2633 def naked_property(self, expression: exp.Property) -> str: 2634 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 2635 if not property_name: 2636 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 2637 return f"{property_name} {self.sql(expression, 'this')}" 2638 2639 def set_operation(self, expression: exp.Expression, op: str) -> str: 2640 this = self.sql(expression, "this") 2641 op = self.seg(op) 2642 return self.query_modifiers( 2643 expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}" 2644 ) 2645 2646 def tag_sql(self, expression: exp.Tag) -> str: 2647 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 2648 2649 def token_sql(self, token_type: TokenType) -> str: 2650 return self.TOKEN_MAPPING.get(token_type, token_type.name) 2651 2652 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 2653 this = self.sql(expression, "this") 2654 expressions = self.no_identify(self.expressions, expression) 2655 expressions = ( 2656 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 2657 ) 2658 return f"{this}{expressions}" 2659 2660 def joinhint_sql(self, expression: exp.JoinHint) -> str: 2661 this = self.sql(expression, "this") 2662 expressions = self.expressions(expression, flat=True) 2663 return f"{this}({expressions})" 2664 2665 def kwarg_sql(self, expression: exp.Kwarg) -> str: 2666 return self.binary(expression, "=>") 2667 2668 def when_sql(self, expression: exp.When) -> str: 2669 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 2670 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 2671 condition = self.sql(expression, "condition") 2672 condition = f" AND {condition}" if condition else "" 2673 2674 then_expression = expression.args.get("then") 2675 if isinstance(then_expression, exp.Insert): 2676 then = f"INSERT {self.sql(then_expression, 'this')}" 2677 if "expression" in then_expression.args: 2678 then += f" VALUES {self.sql(then_expression, 'expression')}" 2679 elif isinstance(then_expression, exp.Update): 2680 if isinstance(then_expression.args.get("expressions"), exp.Star): 2681 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 2682 else: 2683 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 2684 else: 2685 then = self.sql(then_expression) 2686 return f"WHEN {matched}{source}{condition} THEN {then}" 2687 2688 def merge_sql(self, expression: exp.Merge) -> str: 2689 table = expression.this 2690 table_alias = "" 2691 2692 hints = table.args.get("hints") 2693 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 2694 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 2695 table = table.copy() 2696 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 2697 2698 this = self.sql(table) 2699 using = f"USING {self.sql(expression, 'using')}" 2700 on = f"ON {self.sql(expression, 'on')}" 2701 expressions = self.expressions(expression, sep=" ") 2702 2703 return f"MERGE INTO {this}{table_alias} {using} {on} {expressions}" 2704 2705 def tochar_sql(self, expression: exp.ToChar) -> str: 2706 if expression.args.get("format"): 2707 self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function") 2708 2709 return self.sql(exp.cast(expression.this, "text")) 2710 2711 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 2712 this = self.sql(expression, "this") 2713 kind = self.sql(expression, "kind") 2714 settings_sql = self.expressions(expression, key="settings", sep=" ") 2715 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 2716 return f"{this}({kind}{args})" 2717 2718 def dictrange_sql(self, expression: exp.DictRange) -> str: 2719 this = self.sql(expression, "this") 2720 max = self.sql(expression, "max") 2721 min = self.sql(expression, "min") 2722 return f"{this}(MIN {min} MAX {max})" 2723 2724 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 2725 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 2726 2727 def oncluster_sql(self, expression: exp.OnCluster) -> str: 2728 return "" 2729 2730 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 2731 expressions = self.expressions(expression, key="expressions", flat=True) 2732 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 2733 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 2734 buckets = self.sql(expression, "buckets") 2735 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 2736 2737 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 2738 this = self.sql(expression, "this") 2739 having = self.sql(expression, "having") 2740 2741 if having: 2742 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 2743 2744 return self.func("ANY_VALUE", this) 2745 2746 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 2747 transform = self.func("TRANSFORM", *expression.expressions) 2748 row_format_before = self.sql(expression, "row_format_before") 2749 row_format_before = f" {row_format_before}" if row_format_before else "" 2750 record_writer = self.sql(expression, "record_writer") 2751 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 2752 using = f" USING {self.sql(expression, 'command_script')}" 2753 schema = self.sql(expression, "schema") 2754 schema = f" AS {schema}" if schema else "" 2755 row_format_after = self.sql(expression, "row_format_after") 2756 row_format_after = f" {row_format_after}" if row_format_after else "" 2757 record_reader = self.sql(expression, "record_reader") 2758 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 2759 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 2760 2761 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 2762 key_block_size = self.sql(expression, "key_block_size") 2763 if key_block_size: 2764 return f"KEY_BLOCK_SIZE = {key_block_size}" 2765 2766 using = self.sql(expression, "using") 2767 if using: 2768 return f"USING {using}" 2769 2770 parser = self.sql(expression, "parser") 2771 if parser: 2772 return f"WITH PARSER {parser}" 2773 2774 comment = self.sql(expression, "comment") 2775 if comment: 2776 return f"COMMENT {comment}" 2777 2778 visible = expression.args.get("visible") 2779 if visible is not None: 2780 return "VISIBLE" if visible else "INVISIBLE" 2781 2782 engine_attr = self.sql(expression, "engine_attr") 2783 if engine_attr: 2784 return f"ENGINE_ATTRIBUTE = {engine_attr}" 2785 2786 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 2787 if secondary_engine_attr: 2788 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 2789 2790 self.unsupported("Unsupported index constraint option.") 2791 return "" 2792 2793 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 2794 kind = self.sql(expression, "kind") 2795 kind = f"{kind} INDEX" if kind else "INDEX" 2796 this = self.sql(expression, "this") 2797 this = f" {this}" if this else "" 2798 index_type = self.sql(expression, "index_type") 2799 index_type = f" USING {index_type}" if index_type else "" 2800 schema = self.sql(expression, "schema") 2801 schema = f" {schema}" if schema else "" 2802 options = self.expressions(expression, key="options", sep=" ") 2803 options = f" {options}" if options else "" 2804 return f"{kind}{this}{index_type}{schema}{options}" 2805 2806 def nvl2_sql(self, expression: exp.Nvl2) -> str: 2807 if self.NVL2_SUPPORTED: 2808 return self.function_fallback_sql(expression) 2809 2810 case = exp.Case().when( 2811 expression.this.is_(exp.null()).not_(copy=False), 2812 expression.args["true"].copy(), 2813 copy=False, 2814 ) 2815 else_cond = expression.args.get("false") 2816 if else_cond: 2817 case.else_(else_cond.copy(), copy=False) 2818 2819 return self.sql(case) 2820 2821 def comprehension_sql(self, expression: exp.Comprehension) -> str: 2822 this = self.sql(expression, "this") 2823 expr = self.sql(expression, "expression") 2824 iterator = self.sql(expression, "iterator") 2825 condition = self.sql(expression, "condition") 2826 condition = f" IF {condition}" if condition else "" 2827 return f"{this} FOR {expr} IN {iterator}{condition}" 2828 2829 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 2830 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 2831 2832 2833def cached_generator( 2834 cache: t.Optional[t.Dict[int, str]] = None 2835) -> t.Callable[[exp.Expression], str]: 2836 """Returns a cached generator.""" 2837 cache = {} if cache is None else cache 2838 generator = Generator(normalize=True, identify="safe") 2839 return lambda e: generator.generate(e, cache)
logger =
<Logger sqlglot (WARNING)>
class
Generator:
17class Generator: 18 """ 19 Generator converts a given syntax tree to the corresponding SQL string. 20 21 Args: 22 pretty: Whether or not to format the produced SQL string. 23 Default: False. 24 identify: Determines when an identifier should be quoted. Possible values are: 25 False (default): Never quote, except in cases where it's mandatory by the dialect. 26 True or 'always': Always quote. 27 'safe': Only quote identifiers that are case insensitive. 28 normalize: Whether or not to normalize identifiers to lowercase. 29 Default: False. 30 pad: Determines the pad size in a formatted string. 31 Default: 2. 32 indent: Determines the indentation size in a formatted string. 33 Default: 2. 34 normalize_functions: Whether or not to normalize all function names. Possible values are: 35 "upper" or True (default): Convert names to uppercase. 36 "lower": Convert names to lowercase. 37 False: Disables function name normalization. 38 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 39 Default ErrorLevel.WARN. 40 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 41 This is only relevant if unsupported_level is ErrorLevel.RAISE. 42 Default: 3 43 leading_comma: Determines whether or not the comma is leading or trailing in select expressions. 44 This is only relevant when generating in pretty mode. 45 Default: False 46 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 47 The default is on the smaller end because the length only represents a segment and not the true 48 line length. 49 Default: 80 50 comments: Whether or not to preserve comments in the output SQL code. 51 Default: True 52 """ 53 54 TRANSFORMS = { 55 exp.DateAdd: lambda self, e: self.func( 56 "DATE_ADD", e.this, e.expression, exp.Literal.string(e.text("unit")) 57 ), 58 exp.TsOrDsAdd: lambda self, e: self.func( 59 "TS_OR_DS_ADD", e.this, e.expression, exp.Literal.string(e.text("unit")) 60 ), 61 exp.CaseSpecificColumnConstraint: lambda self, e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 62 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 63 exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 64 exp.CheckColumnConstraint: lambda self, e: f"CHECK ({self.sql(e, 'this')})", 65 exp.ClusteredColumnConstraint: lambda self, e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 66 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 67 exp.CopyGrantsProperty: lambda self, e: "COPY GRANTS", 68 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 69 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 70 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 71 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 72 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 73 exp.ExternalProperty: lambda self, e: "EXTERNAL", 74 exp.HeapProperty: lambda self, e: "HEAP", 75 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 76 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 77 exp.LanguageProperty: lambda self, e: self.naked_property(e), 78 exp.LocationProperty: lambda self, e: self.naked_property(e), 79 exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG", 80 exp.MaterializedProperty: lambda self, e: "MATERIALIZED", 81 exp.NoPrimaryIndexProperty: lambda self, e: "NO PRIMARY INDEX", 82 exp.NonClusteredColumnConstraint: lambda self, e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 83 exp.NotForReplicationColumnConstraint: lambda self, e: "NOT FOR REPLICATION", 84 exp.OnCommitProperty: lambda self, e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 85 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 86 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 87 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 88 exp.ReturnsProperty: lambda self, e: self.naked_property(e), 89 exp.SetProperty: lambda self, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 90 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 91 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 92 exp.StabilityProperty: lambda self, e: e.name, 93 exp.TemporaryProperty: lambda self, e: f"TEMPORARY", 94 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 95 exp.TransientProperty: lambda self, e: "TRANSIENT", 96 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 97 exp.UppercaseColumnConstraint: lambda self, e: f"UPPERCASE", 98 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 99 exp.VolatileProperty: lambda self, e: "VOLATILE", 100 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 101 } 102 103 # Whether or not null ordering is supported in order by 104 NULL_ORDERING_SUPPORTED = True 105 106 # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 107 LOCKING_READS_SUPPORTED = False 108 109 # Always do union distinct or union all 110 EXPLICIT_UNION = False 111 112 # Wrap derived values in parens, usually standard but spark doesn't support it 113 WRAP_DERIVED_VALUES = True 114 115 # Whether or not create function uses an AS before the RETURN 116 CREATE_FUNCTION_RETURN_AS = True 117 118 # Whether or not MERGE ... WHEN MATCHED BY SOURCE is allowed 119 MATCHED_BY_SOURCE = True 120 121 # Whether or not the INTERVAL expression works only with values like '1 day' 122 SINGLE_STRING_INTERVAL = False 123 124 # Whether or not the plural form of date parts like day (i.e. "days") is supported in INTERVALs 125 INTERVAL_ALLOWS_PLURAL_FORM = True 126 127 # Whether or not the TABLESAMPLE clause supports a method name, like BERNOULLI 128 TABLESAMPLE_WITH_METHOD = True 129 130 # Whether or not to treat the number in TABLESAMPLE (50) as a percentage 131 TABLESAMPLE_SIZE_IS_PERCENT = False 132 133 # Whether or not limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 134 LIMIT_FETCH = "ALL" 135 136 # Whether or not a table is allowed to be renamed with a db 137 RENAME_TABLE_WITH_DB = True 138 139 # The separator for grouping sets and rollups 140 GROUPINGS_SEP = "," 141 142 # The string used for creating an index on a table 143 INDEX_ON = "ON" 144 145 # Whether or not join hints should be generated 146 JOIN_HINTS = True 147 148 # Whether or not table hints should be generated 149 TABLE_HINTS = True 150 151 # Whether or not query hints should be generated 152 QUERY_HINTS = True 153 154 # What kind of separator to use for query hints 155 QUERY_HINT_SEP = ", " 156 157 # Whether or not comparing against booleans (e.g. x IS TRUE) is supported 158 IS_BOOL_ALLOWED = True 159 160 # Whether or not to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 161 DUPLICATE_KEY_UPDATE_WITH_SET = True 162 163 # Whether or not to generate the limit as TOP <value> instead of LIMIT <value> 164 LIMIT_IS_TOP = False 165 166 # Whether or not to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 167 RETURNING_END = True 168 169 # Whether or not to generate the (+) suffix for columns used in old-style join conditions 170 COLUMN_JOIN_MARKS_SUPPORTED = False 171 172 # Whether or not to generate an unquoted value for EXTRACT's date part argument 173 EXTRACT_ALLOWS_QUOTES = True 174 175 # Whether or not TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 176 TZ_TO_WITH_TIME_ZONE = False 177 178 # Whether or not the NVL2 function is supported 179 NVL2_SUPPORTED = True 180 181 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 182 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 183 184 # Whether or not VALUES statements can be used as derived tables. 185 # MySQL 5 and Redshift do not allow this, so when False, it will convert 186 # SELECT * VALUES into SELECT UNION 187 VALUES_AS_TABLE = True 188 189 # Whether or not the word COLUMN is included when adding a column with ALTER TABLE 190 ALTER_TABLE_ADD_COLUMN_KEYWORD = True 191 192 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 193 UNNEST_WITH_ORDINALITY = True 194 195 # Whether or not FILTER (WHERE cond) can be used for conditional aggregation 196 AGGREGATE_FILTER_SUPPORTED = True 197 198 # Whether or not JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 199 SEMI_ANTI_JOIN_WITH_SIDE = True 200 201 # Whether or not session variables / parameters are supported, e.g. @x in T-SQL 202 SUPPORTS_PARAMETERS = True 203 204 TYPE_MAPPING = { 205 exp.DataType.Type.NCHAR: "CHAR", 206 exp.DataType.Type.NVARCHAR: "VARCHAR", 207 exp.DataType.Type.MEDIUMTEXT: "TEXT", 208 exp.DataType.Type.LONGTEXT: "TEXT", 209 exp.DataType.Type.TINYTEXT: "TEXT", 210 exp.DataType.Type.MEDIUMBLOB: "BLOB", 211 exp.DataType.Type.LONGBLOB: "BLOB", 212 exp.DataType.Type.TINYBLOB: "BLOB", 213 exp.DataType.Type.INET: "INET", 214 } 215 216 STAR_MAPPING = { 217 "except": "EXCEPT", 218 "replace": "REPLACE", 219 } 220 221 TIME_PART_SINGULARS = { 222 "microseconds": "microsecond", 223 "seconds": "second", 224 "minutes": "minute", 225 "hours": "hour", 226 "days": "day", 227 "weeks": "week", 228 "months": "month", 229 "quarters": "quarter", 230 "years": "year", 231 } 232 233 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 234 235 STRUCT_DELIMITER = ("<", ">") 236 237 PARAMETER_TOKEN = "@" 238 239 PROPERTIES_LOCATION = { 240 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 241 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 242 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 243 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 244 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 245 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 246 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 247 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 248 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 249 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 250 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 251 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 252 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 253 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 254 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 255 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 256 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 257 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 258 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 259 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 260 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 261 exp.HeapProperty: exp.Properties.Location.POST_WITH, 262 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 263 exp.JournalProperty: exp.Properties.Location.POST_NAME, 264 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 265 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 266 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 267 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 268 exp.LogProperty: exp.Properties.Location.POST_NAME, 269 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 270 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 271 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 272 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 273 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 274 exp.Order: exp.Properties.Location.POST_SCHEMA, 275 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 276 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 277 exp.Property: exp.Properties.Location.POST_WITH, 278 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 279 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 280 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 281 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 282 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 283 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 284 exp.Set: exp.Properties.Location.POST_SCHEMA, 285 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 286 exp.SetProperty: exp.Properties.Location.POST_CREATE, 287 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 288 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 289 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 290 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 291 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 292 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 293 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 294 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 295 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 296 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 297 } 298 299 # Keywords that can't be used as unquoted identifier names 300 RESERVED_KEYWORDS: t.Set[str] = set() 301 302 # Expressions whose comments are separated from them for better formatting 303 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 304 exp.Create, 305 exp.Delete, 306 exp.Drop, 307 exp.From, 308 exp.Insert, 309 exp.Join, 310 exp.Select, 311 exp.Update, 312 exp.Where, 313 exp.With, 314 ) 315 316 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 317 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 318 exp.Column, 319 exp.Literal, 320 exp.Neg, 321 exp.Paren, 322 ) 323 324 UNESCAPED_SEQUENCE_TABLE = None # type: ignore 325 326 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 327 328 # Autofilled 329 INVERSE_TIME_MAPPING: t.Dict[str, str] = {} 330 INVERSE_TIME_TRIE: t.Dict = {} 331 INDEX_OFFSET = 0 332 UNNEST_COLUMN_ONLY = False 333 ALIAS_POST_TABLESAMPLE = False 334 IDENTIFIERS_CAN_START_WITH_DIGIT = False 335 STRICT_STRING_CONCAT = False 336 NORMALIZE_FUNCTIONS: bool | str = "upper" 337 NULL_ORDERING = "nulls_are_small" 338 339 can_identify: t.Callable[[str, str | bool], bool] 340 341 # Delimiters for quotes, identifiers and the corresponding escape characters 342 QUOTE_START = "'" 343 QUOTE_END = "'" 344 IDENTIFIER_START = '"' 345 IDENTIFIER_END = '"' 346 TOKENIZER_CLASS = Tokenizer 347 348 # Delimiters for bit, hex, byte and raw literals 349 BIT_START: t.Optional[str] = None 350 BIT_END: t.Optional[str] = None 351 HEX_START: t.Optional[str] = None 352 HEX_END: t.Optional[str] = None 353 BYTE_START: t.Optional[str] = None 354 BYTE_END: t.Optional[str] = None 355 356 __slots__ = ( 357 "pretty", 358 "identify", 359 "normalize", 360 "pad", 361 "_indent", 362 "normalize_functions", 363 "unsupported_level", 364 "max_unsupported", 365 "leading_comma", 366 "max_text_width", 367 "comments", 368 "unsupported_messages", 369 "_escaped_quote_end", 370 "_escaped_identifier_end", 371 "_cache", 372 ) 373 374 def __init__( 375 self, 376 pretty: t.Optional[bool] = None, 377 identify: str | bool = False, 378 normalize: bool = False, 379 pad: int = 2, 380 indent: int = 2, 381 normalize_functions: t.Optional[str | bool] = None, 382 unsupported_level: ErrorLevel = ErrorLevel.WARN, 383 max_unsupported: int = 3, 384 leading_comma: bool = False, 385 max_text_width: int = 80, 386 comments: bool = True, 387 ): 388 import sqlglot 389 390 self.pretty = pretty if pretty is not None else sqlglot.pretty 391 self.identify = identify 392 self.normalize = normalize 393 self.pad = pad 394 self._indent = indent 395 self.unsupported_level = unsupported_level 396 self.max_unsupported = max_unsupported 397 self.leading_comma = leading_comma 398 self.max_text_width = max_text_width 399 self.comments = comments 400 401 # This is both a Dialect property and a Generator argument, so we prioritize the latter 402 self.normalize_functions = ( 403 self.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 404 ) 405 406 self.unsupported_messages: t.List[str] = [] 407 self._escaped_quote_end: str = self.TOKENIZER_CLASS.STRING_ESCAPES[0] + self.QUOTE_END 408 self._escaped_identifier_end: str = ( 409 self.TOKENIZER_CLASS.IDENTIFIER_ESCAPES[0] + self.IDENTIFIER_END 410 ) 411 self._cache: t.Optional[t.Dict[int, str]] = None 412 413 def generate( 414 self, 415 expression: t.Optional[exp.Expression], 416 cache: t.Optional[t.Dict[int, str]] = None, 417 ) -> str: 418 """ 419 Generates the SQL string corresponding to the given syntax tree. 420 421 Args: 422 expression: The syntax tree. 423 cache: An optional sql string cache. This leverages the hash of an Expression 424 which can be slow to compute, so only use it if you set _hash on each node. 425 426 Returns: 427 The SQL string corresponding to `expression`. 428 """ 429 if cache is not None: 430 self._cache = cache 431 432 self.unsupported_messages = [] 433 sql = self.sql(expression).strip() 434 self._cache = None 435 436 if self.unsupported_level == ErrorLevel.IGNORE: 437 return sql 438 439 if self.unsupported_level == ErrorLevel.WARN: 440 for msg in self.unsupported_messages: 441 logger.warning(msg) 442 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 443 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 444 445 if self.pretty: 446 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 447 return sql 448 449 def unsupported(self, message: str) -> None: 450 if self.unsupported_level == ErrorLevel.IMMEDIATE: 451 raise UnsupportedError(message) 452 self.unsupported_messages.append(message) 453 454 def sep(self, sep: str = " ") -> str: 455 return f"{sep.strip()}\n" if self.pretty else sep 456 457 def seg(self, sql: str, sep: str = " ") -> str: 458 return f"{self.sep(sep)}{sql}" 459 460 def pad_comment(self, comment: str) -> str: 461 comment = " " + comment if comment[0].strip() else comment 462 comment = comment + " " if comment[-1].strip() else comment 463 return comment 464 465 def maybe_comment( 466 self, 467 sql: str, 468 expression: t.Optional[exp.Expression] = None, 469 comments: t.Optional[t.List[str]] = None, 470 ) -> str: 471 comments = ( 472 ((expression and expression.comments) if comments is None else comments) # type: ignore 473 if self.comments 474 else None 475 ) 476 477 if not comments or isinstance(expression, exp.Binary): 478 return sql 479 480 sep = "\n" if self.pretty else " " 481 comments_sql = sep.join( 482 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 483 ) 484 485 if not comments_sql: 486 return sql 487 488 if isinstance(expression, self.WITH_SEPARATED_COMMENTS): 489 return ( 490 f"{self.sep()}{comments_sql}{sql}" 491 if sql[0].isspace() 492 else f"{comments_sql}{self.sep()}{sql}" 493 ) 494 495 return f"{sql} {comments_sql}" 496 497 def wrap(self, expression: exp.Expression | str) -> str: 498 this_sql = self.indent( 499 self.sql(expression) 500 if isinstance(expression, (exp.Select, exp.Union)) 501 else self.sql(expression, "this"), 502 level=1, 503 pad=0, 504 ) 505 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 506 507 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 508 original = self.identify 509 self.identify = False 510 result = func(*args, **kwargs) 511 self.identify = original 512 return result 513 514 def normalize_func(self, name: str) -> str: 515 if self.normalize_functions == "upper" or self.normalize_functions is True: 516 return name.upper() 517 if self.normalize_functions == "lower": 518 return name.lower() 519 return name 520 521 def indent( 522 self, 523 sql: str, 524 level: int = 0, 525 pad: t.Optional[int] = None, 526 skip_first: bool = False, 527 skip_last: bool = False, 528 ) -> str: 529 if not self.pretty: 530 return sql 531 532 pad = self.pad if pad is None else pad 533 lines = sql.split("\n") 534 535 return "\n".join( 536 line 537 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 538 else f"{' ' * (level * self._indent + pad)}{line}" 539 for i, line in enumerate(lines) 540 ) 541 542 def sql( 543 self, 544 expression: t.Optional[str | exp.Expression], 545 key: t.Optional[str] = None, 546 comment: bool = True, 547 ) -> str: 548 if not expression: 549 return "" 550 551 if isinstance(expression, str): 552 return expression 553 554 if key: 555 value = expression.args.get(key) 556 if value: 557 return self.sql(value) 558 return "" 559 560 if self._cache is not None: 561 expression_id = hash(expression) 562 563 if expression_id in self._cache: 564 return self._cache[expression_id] 565 566 transform = self.TRANSFORMS.get(expression.__class__) 567 568 if callable(transform): 569 sql = transform(self, expression) 570 elif transform: 571 sql = transform 572 elif isinstance(expression, exp.Expression): 573 exp_handler_name = f"{expression.key}_sql" 574 575 if hasattr(self, exp_handler_name): 576 sql = getattr(self, exp_handler_name)(expression) 577 elif isinstance(expression, exp.Func): 578 sql = self.function_fallback_sql(expression) 579 elif isinstance(expression, exp.Property): 580 sql = self.property_sql(expression) 581 else: 582 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 583 else: 584 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 585 586 sql = self.maybe_comment(sql, expression) if self.comments and comment else sql 587 588 if self._cache is not None: 589 self._cache[expression_id] = sql 590 return sql 591 592 def uncache_sql(self, expression: exp.Uncache) -> str: 593 table = self.sql(expression, "this") 594 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 595 return f"UNCACHE TABLE{exists_sql} {table}" 596 597 def cache_sql(self, expression: exp.Cache) -> str: 598 lazy = " LAZY" if expression.args.get("lazy") else "" 599 table = self.sql(expression, "this") 600 options = expression.args.get("options") 601 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 602 sql = self.sql(expression, "expression") 603 sql = f" AS{self.sep()}{sql}" if sql else "" 604 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 605 return self.prepend_ctes(expression, sql) 606 607 def characterset_sql(self, expression: exp.CharacterSet) -> str: 608 if isinstance(expression.parent, exp.Cast): 609 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 610 default = "DEFAULT " if expression.args.get("default") else "" 611 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 612 613 def column_sql(self, expression: exp.Column) -> str: 614 join_mark = " (+)" if expression.args.get("join_mark") else "" 615 616 if join_mark and not self.COLUMN_JOIN_MARKS_SUPPORTED: 617 join_mark = "" 618 self.unsupported("Outer join syntax using the (+) operator is not supported.") 619 620 column = ".".join( 621 self.sql(part) 622 for part in ( 623 expression.args.get("catalog"), 624 expression.args.get("db"), 625 expression.args.get("table"), 626 expression.args.get("this"), 627 ) 628 if part 629 ) 630 631 return f"{column}{join_mark}" 632 633 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 634 this = self.sql(expression, "this") 635 this = f" {this}" if this else "" 636 position = self.sql(expression, "position") 637 return f"{position}{this}" 638 639 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 640 column = self.sql(expression, "this") 641 kind = self.sql(expression, "kind") 642 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 643 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 644 kind = f"{sep}{kind}" if kind else "" 645 constraints = f" {constraints}" if constraints else "" 646 position = self.sql(expression, "position") 647 position = f" {position}" if position else "" 648 649 return f"{exists}{column}{kind}{constraints}{position}" 650 651 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 652 this = self.sql(expression, "this") 653 kind_sql = self.sql(expression, "kind").strip() 654 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 655 656 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 657 this = self.sql(expression, "this") 658 if expression.args.get("not_null"): 659 persisted = " PERSISTED NOT NULL" 660 elif expression.args.get("persisted"): 661 persisted = " PERSISTED" 662 else: 663 persisted = "" 664 return f"AS {this}{persisted}" 665 666 def autoincrementcolumnconstraint_sql(self, _) -> str: 667 return self.token_sql(TokenType.AUTO_INCREMENT) 668 669 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 670 if isinstance(expression.this, list): 671 this = self.wrap(self.expressions(expression, key="this", flat=True)) 672 else: 673 this = self.sql(expression, "this") 674 675 return f"COMPRESS {this}" 676 677 def generatedasidentitycolumnconstraint_sql( 678 self, expression: exp.GeneratedAsIdentityColumnConstraint 679 ) -> str: 680 this = "" 681 if expression.this is not None: 682 on_null = " ON NULL" if expression.args.get("on_null") else "" 683 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 684 685 start = expression.args.get("start") 686 start = f"START WITH {start}" if start else "" 687 increment = expression.args.get("increment") 688 increment = f" INCREMENT BY {increment}" if increment else "" 689 minvalue = expression.args.get("minvalue") 690 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 691 maxvalue = expression.args.get("maxvalue") 692 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 693 cycle = expression.args.get("cycle") 694 cycle_sql = "" 695 696 if cycle is not None: 697 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 698 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 699 700 sequence_opts = "" 701 if start or increment or cycle_sql: 702 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 703 sequence_opts = f" ({sequence_opts.strip()})" 704 705 expr = self.sql(expression, "expression") 706 expr = f"({expr})" if expr else "IDENTITY" 707 708 return f"GENERATED{this} AS {expr}{sequence_opts}" 709 710 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 711 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 712 713 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 714 desc = expression.args.get("desc") 715 if desc is not None: 716 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 717 return f"PRIMARY KEY" 718 719 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 720 this = self.sql(expression, "this") 721 this = f" {this}" if this else "" 722 index_type = expression.args.get("index_type") 723 index_type = f" USING {index_type}" if index_type else "" 724 return f"UNIQUE{this}{index_type}" 725 726 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 727 return self.sql(expression, "this") 728 729 def create_sql(self, expression: exp.Create) -> str: 730 kind = self.sql(expression, "kind").upper() 731 properties = expression.args.get("properties") 732 properties_locs = self.locate_properties(properties) if properties else defaultdict() 733 734 this = self.createable_sql(expression, properties_locs) 735 736 properties_sql = "" 737 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 738 exp.Properties.Location.POST_WITH 739 ): 740 properties_sql = self.sql( 741 exp.Properties( 742 expressions=[ 743 *properties_locs[exp.Properties.Location.POST_SCHEMA], 744 *properties_locs[exp.Properties.Location.POST_WITH], 745 ] 746 ) 747 ) 748 749 begin = " BEGIN" if expression.args.get("begin") else "" 750 expression_sql = self.sql(expression, "expression") 751 if expression_sql: 752 expression_sql = f"{begin}{self.sep()}{expression_sql}" 753 754 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 755 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 756 postalias_props_sql = self.properties( 757 exp.Properties( 758 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 759 ), 760 wrapped=False, 761 ) 762 expression_sql = f" AS {postalias_props_sql}{expression_sql}" 763 else: 764 expression_sql = f" AS{expression_sql}" 765 766 postindex_props_sql = "" 767 if properties_locs.get(exp.Properties.Location.POST_INDEX): 768 postindex_props_sql = self.properties( 769 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 770 wrapped=False, 771 prefix=" ", 772 ) 773 774 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 775 indexes = f" {indexes}" if indexes else "" 776 index_sql = indexes + postindex_props_sql 777 778 replace = " OR REPLACE" if expression.args.get("replace") else "" 779 unique = " UNIQUE" if expression.args.get("unique") else "" 780 781 postcreate_props_sql = "" 782 if properties_locs.get(exp.Properties.Location.POST_CREATE): 783 postcreate_props_sql = self.properties( 784 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 785 sep=" ", 786 prefix=" ", 787 wrapped=False, 788 ) 789 790 modifiers = "".join((replace, unique, postcreate_props_sql)) 791 792 postexpression_props_sql = "" 793 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 794 postexpression_props_sql = self.properties( 795 exp.Properties( 796 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 797 ), 798 sep=" ", 799 prefix=" ", 800 wrapped=False, 801 ) 802 803 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 804 no_schema_binding = ( 805 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 806 ) 807 808 clone = self.sql(expression, "clone") 809 clone = f" {clone}" if clone else "" 810 811 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 812 return self.prepend_ctes(expression, expression_sql) 813 814 def clone_sql(self, expression: exp.Clone) -> str: 815 this = self.sql(expression, "this") 816 shallow = "SHALLOW " if expression.args.get("shallow") else "" 817 this = f"{shallow}CLONE {this}" 818 when = self.sql(expression, "when") 819 820 if when: 821 kind = self.sql(expression, "kind") 822 expr = self.sql(expression, "expression") 823 return f"{this} {when} ({kind} => {expr})" 824 825 return this 826 827 def describe_sql(self, expression: exp.Describe) -> str: 828 return f"DESCRIBE {self.sql(expression, 'this')}" 829 830 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 831 with_ = self.sql(expression, "with") 832 if with_: 833 sql = f"{with_}{self.sep()}{sql}" 834 return sql 835 836 def with_sql(self, expression: exp.With) -> str: 837 sql = self.expressions(expression, flat=True) 838 recursive = "RECURSIVE " if expression.args.get("recursive") else "" 839 840 return f"WITH {recursive}{sql}" 841 842 def cte_sql(self, expression: exp.CTE) -> str: 843 alias = self.sql(expression, "alias") 844 return f"{alias} AS {self.wrap(expression)}" 845 846 def tablealias_sql(self, expression: exp.TableAlias) -> str: 847 alias = self.sql(expression, "this") 848 columns = self.expressions(expression, key="columns", flat=True) 849 columns = f"({columns})" if columns else "" 850 return f"{alias}{columns}" 851 852 def bitstring_sql(self, expression: exp.BitString) -> str: 853 this = self.sql(expression, "this") 854 if self.BIT_START: 855 return f"{self.BIT_START}{this}{self.BIT_END}" 856 return f"{int(this, 2)}" 857 858 def hexstring_sql(self, expression: exp.HexString) -> str: 859 this = self.sql(expression, "this") 860 if self.HEX_START: 861 return f"{self.HEX_START}{this}{self.HEX_END}" 862 return f"{int(this, 16)}" 863 864 def bytestring_sql(self, expression: exp.ByteString) -> str: 865 this = self.sql(expression, "this") 866 if self.BYTE_START: 867 return f"{self.BYTE_START}{this}{self.BYTE_END}" 868 return this 869 870 def rawstring_sql(self, expression: exp.RawString) -> str: 871 string = self.escape_str(expression.this.replace("\\", "\\\\")) 872 return f"{self.QUOTE_START}{string}{self.QUOTE_END}" 873 874 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 875 this = self.sql(expression, "this") 876 specifier = self.sql(expression, "expression") 877 specifier = f" {specifier}" if specifier else "" 878 return f"{this}{specifier}" 879 880 def datatype_sql(self, expression: exp.DataType) -> str: 881 type_value = expression.this 882 883 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 884 type_sql = self.sql(expression, "kind") 885 else: 886 type_sql = ( 887 self.TYPE_MAPPING.get(type_value, type_value.value) 888 if isinstance(type_value, exp.DataType.Type) 889 else type_value 890 ) 891 892 nested = "" 893 interior = self.expressions(expression, flat=True) 894 values = "" 895 896 if interior: 897 if expression.args.get("nested"): 898 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 899 if expression.args.get("values") is not None: 900 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 901 values = self.expressions(expression, key="values", flat=True) 902 values = f"{delimiters[0]}{values}{delimiters[1]}" 903 elif type_value == exp.DataType.Type.INTERVAL: 904 nested = f" {interior}" 905 else: 906 nested = f"({interior})" 907 908 type_sql = f"{type_sql}{nested}{values}" 909 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 910 exp.DataType.Type.TIMETZ, 911 exp.DataType.Type.TIMESTAMPTZ, 912 ): 913 type_sql = f"{type_sql} WITH TIME ZONE" 914 915 return type_sql 916 917 def directory_sql(self, expression: exp.Directory) -> str: 918 local = "LOCAL " if expression.args.get("local") else "" 919 row_format = self.sql(expression, "row_format") 920 row_format = f" {row_format}" if row_format else "" 921 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 922 923 def delete_sql(self, expression: exp.Delete) -> str: 924 this = self.sql(expression, "this") 925 this = f" FROM {this}" if this else "" 926 using = self.sql(expression, "using") 927 using = f" USING {using}" if using else "" 928 where = self.sql(expression, "where") 929 returning = self.sql(expression, "returning") 930 limit = self.sql(expression, "limit") 931 tables = self.expressions(expression, key="tables") 932 tables = f" {tables}" if tables else "" 933 if self.RETURNING_END: 934 expression_sql = f"{this}{using}{where}{returning}{limit}" 935 else: 936 expression_sql = f"{returning}{this}{using}{where}{limit}" 937 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 938 939 def drop_sql(self, expression: exp.Drop) -> str: 940 this = self.sql(expression, "this") 941 kind = expression.args["kind"] 942 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 943 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 944 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 945 cascade = " CASCADE" if expression.args.get("cascade") else "" 946 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 947 purge = " PURGE" if expression.args.get("purge") else "" 948 return ( 949 f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}{constraints}{purge}" 950 ) 951 952 def except_sql(self, expression: exp.Except) -> str: 953 return self.prepend_ctes( 954 expression, 955 self.set_operation(expression, self.except_op(expression)), 956 ) 957 958 def except_op(self, expression: exp.Except) -> str: 959 return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}" 960 961 def fetch_sql(self, expression: exp.Fetch) -> str: 962 direction = expression.args.get("direction") 963 direction = f" {direction.upper()}" if direction else "" 964 count = expression.args.get("count") 965 count = f" {count}" if count else "" 966 if expression.args.get("percent"): 967 count = f"{count} PERCENT" 968 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 969 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 970 971 def filter_sql(self, expression: exp.Filter) -> str: 972 if self.AGGREGATE_FILTER_SUPPORTED: 973 this = self.sql(expression, "this") 974 where = self.sql(expression, "expression").strip() 975 return f"{this} FILTER({where})" 976 977 agg = expression.this.copy() 978 agg_arg = agg.this 979 cond = expression.expression.this 980 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 981 return self.sql(agg) 982 983 def hint_sql(self, expression: exp.Hint) -> str: 984 if not self.QUERY_HINTS: 985 self.unsupported("Hints are not supported") 986 return "" 987 988 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 989 990 def index_sql(self, expression: exp.Index) -> str: 991 unique = "UNIQUE " if expression.args.get("unique") else "" 992 primary = "PRIMARY " if expression.args.get("primary") else "" 993 amp = "AMP " if expression.args.get("amp") else "" 994 name = self.sql(expression, "this") 995 name = f"{name} " if name else "" 996 table = self.sql(expression, "table") 997 table = f"{self.INDEX_ON} {table}" if table else "" 998 using = self.sql(expression, "using") 999 using = f" USING {using}" if using else "" 1000 index = "INDEX " if not table else "" 1001 columns = self.expressions(expression, key="columns", flat=True) 1002 columns = f"({columns})" if columns else "" 1003 partition_by = self.expressions(expression, key="partition_by", flat=True) 1004 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1005 where = self.sql(expression, "where") 1006 return f"{unique}{primary}{amp}{index}{name}{table}{using}{columns}{partition_by}{where}" 1007 1008 def identifier_sql(self, expression: exp.Identifier) -> str: 1009 text = expression.name 1010 lower = text.lower() 1011 text = lower if self.normalize and not expression.quoted else text 1012 text = text.replace(self.IDENTIFIER_END, self._escaped_identifier_end) 1013 if ( 1014 expression.quoted 1015 or self.can_identify(text, self.identify) 1016 or lower in self.RESERVED_KEYWORDS 1017 or (not self.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1018 ): 1019 text = f"{self.IDENTIFIER_START}{text}{self.IDENTIFIER_END}" 1020 return text 1021 1022 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1023 input_format = self.sql(expression, "input_format") 1024 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1025 output_format = self.sql(expression, "output_format") 1026 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1027 return self.sep().join((input_format, output_format)) 1028 1029 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1030 string = self.sql(exp.Literal.string(expression.name)) 1031 return f"{prefix}{string}" 1032 1033 def partition_sql(self, expression: exp.Partition) -> str: 1034 return f"PARTITION({self.expressions(expression, flat=True)})" 1035 1036 def properties_sql(self, expression: exp.Properties) -> str: 1037 root_properties = [] 1038 with_properties = [] 1039 1040 for p in expression.expressions: 1041 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1042 if p_loc == exp.Properties.Location.POST_WITH: 1043 with_properties.append(p.copy()) 1044 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1045 root_properties.append(p.copy()) 1046 1047 return self.root_properties( 1048 exp.Properties(expressions=root_properties) 1049 ) + self.with_properties(exp.Properties(expressions=with_properties)) 1050 1051 def root_properties(self, properties: exp.Properties) -> str: 1052 if properties.expressions: 1053 return self.sep() + self.expressions(properties, indent=False, sep=" ") 1054 return "" 1055 1056 def properties( 1057 self, 1058 properties: exp.Properties, 1059 prefix: str = "", 1060 sep: str = ", ", 1061 suffix: str = "", 1062 wrapped: bool = True, 1063 ) -> str: 1064 if properties.expressions: 1065 expressions = self.expressions(properties, sep=sep, indent=False) 1066 if expressions: 1067 expressions = self.wrap(expressions) if wrapped else expressions 1068 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}" 1069 return "" 1070 1071 def with_properties(self, properties: exp.Properties) -> str: 1072 return self.properties(properties, prefix=self.seg("WITH")) 1073 1074 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1075 properties_locs = defaultdict(list) 1076 for p in properties.expressions: 1077 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1078 if p_loc != exp.Properties.Location.UNSUPPORTED: 1079 properties_locs[p_loc].append(p.copy()) 1080 else: 1081 self.unsupported(f"Unsupported property {p.key}") 1082 1083 return properties_locs 1084 1085 def property_sql(self, expression: exp.Property) -> str: 1086 property_cls = expression.__class__ 1087 if property_cls == exp.Property: 1088 return f"{expression.name}={self.sql(expression, 'value')}" 1089 1090 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1091 if not property_name: 1092 self.unsupported(f"Unsupported property {expression.key}") 1093 1094 return f"{property_name}={self.sql(expression, 'this')}" 1095 1096 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1097 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1098 options = f" {options}" if options else "" 1099 return f"LIKE {self.sql(expression, 'this')}{options}" 1100 1101 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1102 no = "NO " if expression.args.get("no") else "" 1103 protection = " PROTECTION" if expression.args.get("protection") else "" 1104 return f"{no}FALLBACK{protection}" 1105 1106 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1107 no = "NO " if expression.args.get("no") else "" 1108 local = expression.args.get("local") 1109 local = f"{local} " if local else "" 1110 dual = "DUAL " if expression.args.get("dual") else "" 1111 before = "BEFORE " if expression.args.get("before") else "" 1112 after = "AFTER " if expression.args.get("after") else "" 1113 return f"{no}{local}{dual}{before}{after}JOURNAL" 1114 1115 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1116 freespace = self.sql(expression, "this") 1117 percent = " PERCENT" if expression.args.get("percent") else "" 1118 return f"FREESPACE={freespace}{percent}" 1119 1120 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1121 if expression.args.get("default"): 1122 property = "DEFAULT" 1123 elif expression.args.get("on"): 1124 property = "ON" 1125 else: 1126 property = "OFF" 1127 return f"CHECKSUM={property}" 1128 1129 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1130 if expression.args.get("no"): 1131 return "NO MERGEBLOCKRATIO" 1132 if expression.args.get("default"): 1133 return "DEFAULT MERGEBLOCKRATIO" 1134 1135 percent = " PERCENT" if expression.args.get("percent") else "" 1136 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1137 1138 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1139 default = expression.args.get("default") 1140 minimum = expression.args.get("minimum") 1141 maximum = expression.args.get("maximum") 1142 if default or minimum or maximum: 1143 if default: 1144 prop = "DEFAULT" 1145 elif minimum: 1146 prop = "MINIMUM" 1147 else: 1148 prop = "MAXIMUM" 1149 return f"{prop} DATABLOCKSIZE" 1150 units = expression.args.get("units") 1151 units = f" {units}" if units else "" 1152 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1153 1154 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1155 autotemp = expression.args.get("autotemp") 1156 always = expression.args.get("always") 1157 default = expression.args.get("default") 1158 manual = expression.args.get("manual") 1159 never = expression.args.get("never") 1160 1161 if autotemp is not None: 1162 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1163 elif always: 1164 prop = "ALWAYS" 1165 elif default: 1166 prop = "DEFAULT" 1167 elif manual: 1168 prop = "MANUAL" 1169 elif never: 1170 prop = "NEVER" 1171 return f"BLOCKCOMPRESSION={prop}" 1172 1173 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1174 no = expression.args.get("no") 1175 no = " NO" if no else "" 1176 concurrent = expression.args.get("concurrent") 1177 concurrent = " CONCURRENT" if concurrent else "" 1178 1179 for_ = "" 1180 if expression.args.get("for_all"): 1181 for_ = " FOR ALL" 1182 elif expression.args.get("for_insert"): 1183 for_ = " FOR INSERT" 1184 elif expression.args.get("for_none"): 1185 for_ = " FOR NONE" 1186 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}" 1187 1188 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1189 kind = expression.args.get("kind") 1190 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1191 for_or_in = expression.args.get("for_or_in") 1192 lock_type = expression.args.get("lock_type") 1193 override = " OVERRIDE" if expression.args.get("override") else "" 1194 return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}" 1195 1196 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1197 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1198 statistics = expression.args.get("statistics") 1199 statistics_sql = "" 1200 if statistics is not None: 1201 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1202 return f"{data_sql}{statistics_sql}" 1203 1204 def insert_sql(self, expression: exp.Insert) -> str: 1205 overwrite = expression.args.get("overwrite") 1206 1207 if isinstance(expression.this, exp.Directory): 1208 this = " OVERWRITE" if overwrite else " INTO" 1209 else: 1210 this = " OVERWRITE TABLE" if overwrite else " INTO" 1211 1212 alternative = expression.args.get("alternative") 1213 alternative = f" OR {alternative}" if alternative else "" 1214 ignore = " IGNORE" if expression.args.get("ignore") else "" 1215 1216 this = f"{this} {self.sql(expression, 'this')}" 1217 1218 exists = " IF EXISTS" if expression.args.get("exists") else "" 1219 partition_sql = ( 1220 f" {self.sql(expression, 'partition')}" if expression.args.get("partition") else "" 1221 ) 1222 where = self.sql(expression, "where") 1223 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1224 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1225 conflict = self.sql(expression, "conflict") 1226 by_name = " BY NAME" if expression.args.get("by_name") else "" 1227 returning = self.sql(expression, "returning") 1228 1229 if self.RETURNING_END: 1230 expression_sql = f"{expression_sql}{conflict}{returning}" 1231 else: 1232 expression_sql = f"{returning}{expression_sql}{conflict}" 1233 1234 sql = f"INSERT{alternative}{ignore}{this}{by_name}{exists}{partition_sql}{where}{expression_sql}" 1235 return self.prepend_ctes(expression, sql) 1236 1237 def intersect_sql(self, expression: exp.Intersect) -> str: 1238 return self.prepend_ctes( 1239 expression, 1240 self.set_operation(expression, self.intersect_op(expression)), 1241 ) 1242 1243 def intersect_op(self, expression: exp.Intersect) -> str: 1244 return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}" 1245 1246 def introducer_sql(self, expression: exp.Introducer) -> str: 1247 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1248 1249 def kill_sql(self, expression: exp.Kill) -> str: 1250 kind = self.sql(expression, "kind") 1251 kind = f" {kind}" if kind else "" 1252 this = self.sql(expression, "this") 1253 this = f" {this}" if this else "" 1254 return f"KILL{kind}{this}" 1255 1256 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1257 return expression.name.upper() 1258 1259 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1260 return expression.name.upper() 1261 1262 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1263 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1264 constraint = self.sql(expression, "constraint") 1265 if constraint: 1266 constraint = f"ON CONSTRAINT {constraint}" 1267 key = self.expressions(expression, key="key", flat=True) 1268 do = "" if expression.args.get("duplicate") else " DO " 1269 nothing = "NOTHING" if expression.args.get("nothing") else "" 1270 expressions = self.expressions(expression, flat=True) 1271 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1272 if expressions: 1273 expressions = f"UPDATE {set_keyword}{expressions}" 1274 return f"{self.seg(conflict)} {constraint}{key}{do}{nothing}{expressions}" 1275 1276 def returning_sql(self, expression: exp.Returning) -> str: 1277 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1278 1279 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1280 fields = expression.args.get("fields") 1281 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1282 escaped = expression.args.get("escaped") 1283 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1284 items = expression.args.get("collection_items") 1285 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1286 keys = expression.args.get("map_keys") 1287 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1288 lines = expression.args.get("lines") 1289 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1290 null = expression.args.get("null") 1291 null = f" NULL DEFINED AS {null}" if null else "" 1292 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1293 1294 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1295 return f"WITH ({self.expressions(expression, flat=True)})" 1296 1297 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1298 this = f"{self.sql(expression, 'this')} INDEX" 1299 target = self.sql(expression, "target") 1300 target = f" FOR {target}" if target else "" 1301 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1302 1303 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1304 table = ".".join( 1305 part 1306 for part in [ 1307 self.sql(expression, "catalog"), 1308 self.sql(expression, "db"), 1309 self.sql(expression, "this"), 1310 ] 1311 if part 1312 ) 1313 1314 version = self.sql(expression, "version") 1315 version = f" {version}" if version else "" 1316 alias = self.sql(expression, "alias") 1317 alias = f"{sep}{alias}" if alias else "" 1318 hints = self.expressions(expression, key="hints", sep=" ") 1319 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1320 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) 1321 pivots = f" {pivots}" if pivots else "" 1322 joins = self.expressions(expression, key="joins", sep="", skip_first=True) 1323 laterals = self.expressions(expression, key="laterals", sep="") 1324 1325 return f"{table}{version}{alias}{hints}{pivots}{joins}{laterals}" 1326 1327 def tablesample_sql( 1328 self, expression: exp.TableSample, seed_prefix: str = "SEED", sep=" AS " 1329 ) -> str: 1330 if self.ALIAS_POST_TABLESAMPLE and expression.this.alias: 1331 table = expression.this.copy() 1332 table.set("alias", None) 1333 this = self.sql(table) 1334 alias = f"{sep}{self.sql(expression.this, 'alias')}" 1335 else: 1336 this = self.sql(expression, "this") 1337 alias = "" 1338 method = self.sql(expression, "method") 1339 method = f"{method.upper()} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1340 numerator = self.sql(expression, "bucket_numerator") 1341 denominator = self.sql(expression, "bucket_denominator") 1342 field = self.sql(expression, "bucket_field") 1343 field = f" ON {field}" if field else "" 1344 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1345 percent = self.sql(expression, "percent") 1346 percent = f"{percent} PERCENT" if percent else "" 1347 rows = self.sql(expression, "rows") 1348 rows = f"{rows} ROWS" if rows else "" 1349 size = self.sql(expression, "size") 1350 if size and self.TABLESAMPLE_SIZE_IS_PERCENT: 1351 size = f"{size} PERCENT" 1352 seed = self.sql(expression, "seed") 1353 seed = f" {seed_prefix} ({seed})" if seed else "" 1354 kind = expression.args.get("kind", "TABLESAMPLE") 1355 return f"{this} {kind} {method}({bucket}{percent}{rows}{size}){seed}{alias}" 1356 1357 def pivot_sql(self, expression: exp.Pivot) -> str: 1358 expressions = self.expressions(expression, flat=True) 1359 1360 if expression.this: 1361 this = self.sql(expression, "this") 1362 on = f"{self.seg('ON')} {expressions}" 1363 using = self.expressions(expression, key="using", flat=True) 1364 using = f"{self.seg('USING')} {using}" if using else "" 1365 group = self.sql(expression, "group") 1366 return f"PIVOT {this}{on}{using}{group}" 1367 1368 alias = self.sql(expression, "alias") 1369 alias = f" AS {alias}" if alias else "" 1370 unpivot = expression.args.get("unpivot") 1371 direction = "UNPIVOT" if unpivot else "PIVOT" 1372 field = self.sql(expression, "field") 1373 include_nulls = expression.args.get("include_nulls") 1374 if include_nulls is not None: 1375 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1376 else: 1377 nulls = "" 1378 return f"{direction}{nulls}({expressions} FOR {field}){alias}" 1379 1380 def version_sql(self, expression: exp.Version) -> str: 1381 this = f"FOR {expression.name}" 1382 kind = expression.text("kind") 1383 expr = self.sql(expression, "expression") 1384 return f"{this} {kind} {expr}" 1385 1386 def tuple_sql(self, expression: exp.Tuple) -> str: 1387 return f"({self.expressions(expression, flat=True)})" 1388 1389 def update_sql(self, expression: exp.Update) -> str: 1390 this = self.sql(expression, "this") 1391 set_sql = self.expressions(expression, flat=True) 1392 from_sql = self.sql(expression, "from") 1393 where_sql = self.sql(expression, "where") 1394 returning = self.sql(expression, "returning") 1395 order = self.sql(expression, "order") 1396 limit = self.sql(expression, "limit") 1397 if self.RETURNING_END: 1398 expression_sql = f"{from_sql}{where_sql}{returning}" 1399 else: 1400 expression_sql = f"{returning}{from_sql}{where_sql}" 1401 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1402 return self.prepend_ctes(expression, sql) 1403 1404 def values_sql(self, expression: exp.Values) -> str: 1405 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 1406 if self.VALUES_AS_TABLE or not expression.find_ancestor(exp.From, exp.Join): 1407 args = self.expressions(expression) 1408 alias = self.sql(expression, "alias") 1409 values = f"VALUES{self.seg('')}{args}" 1410 values = ( 1411 f"({values})" 1412 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1413 else values 1414 ) 1415 return f"{values} AS {alias}" if alias else values 1416 1417 # Converts `VALUES...` expression into a series of select unions. 1418 expression = expression.copy() 1419 column_names = expression.alias and expression.args["alias"].columns 1420 1421 selects = [] 1422 1423 for i, tup in enumerate(expression.expressions): 1424 row = tup.expressions 1425 1426 if i == 0 and column_names: 1427 row = [ 1428 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 1429 ] 1430 1431 selects.append(exp.Select(expressions=row)) 1432 1433 if self.pretty: 1434 # This may result in poor performance for large-cardinality `VALUES` tables, due to 1435 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 1436 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 1437 subquery_expression: exp.Select | exp.Union = selects[0] 1438 if len(selects) > 1: 1439 for select in selects[1:]: 1440 subquery_expression = exp.union( 1441 subquery_expression, select, distinct=False, copy=False 1442 ) 1443 1444 return self.subquery_sql(subquery_expression.subquery(expression.alias, copy=False)) 1445 1446 alias = f" AS {expression.alias}" if expression.alias else "" 1447 unions = " UNION ALL ".join(self.sql(select) for select in selects) 1448 return f"({unions}){alias}" 1449 1450 def var_sql(self, expression: exp.Var) -> str: 1451 return self.sql(expression, "this") 1452 1453 def into_sql(self, expression: exp.Into) -> str: 1454 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1455 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 1456 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 1457 1458 def from_sql(self, expression: exp.From) -> str: 1459 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 1460 1461 def group_sql(self, expression: exp.Group) -> str: 1462 group_by = self.op_expressions("GROUP BY", expression) 1463 1464 if expression.args.get("all"): 1465 return f"{group_by} ALL" 1466 1467 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False) 1468 grouping_sets = ( 1469 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else "" 1470 ) 1471 1472 cube = expression.args.get("cube", []) 1473 if seq_get(cube, 0) is True: 1474 return f"{group_by}{self.seg('WITH CUBE')}" 1475 else: 1476 cube_sql = self.expressions(expression, key="cube", indent=False) 1477 cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else "" 1478 1479 rollup = expression.args.get("rollup", []) 1480 if seq_get(rollup, 0) is True: 1481 return f"{group_by}{self.seg('WITH ROLLUP')}" 1482 else: 1483 rollup_sql = self.expressions(expression, key="rollup", indent=False) 1484 rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else "" 1485 1486 groupings = csv( 1487 grouping_sets, 1488 cube_sql, 1489 rollup_sql, 1490 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 1491 sep=self.GROUPINGS_SEP, 1492 ) 1493 1494 if expression.args.get("expressions") and groupings: 1495 group_by = f"{group_by}{self.GROUPINGS_SEP}" 1496 1497 return f"{group_by}{groupings}" 1498 1499 def having_sql(self, expression: exp.Having) -> str: 1500 this = self.indent(self.sql(expression, "this")) 1501 return f"{self.seg('HAVING')}{self.sep()}{this}" 1502 1503 def connect_sql(self, expression: exp.Connect) -> str: 1504 start = self.sql(expression, "start") 1505 start = self.seg(f"START WITH {start}") if start else "" 1506 connect = self.sql(expression, "connect") 1507 connect = self.seg(f"CONNECT BY {connect}") 1508 return start + connect 1509 1510 def prior_sql(self, expression: exp.Prior) -> str: 1511 return f"PRIOR {self.sql(expression, 'this')}" 1512 1513 def join_sql(self, expression: exp.Join) -> str: 1514 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 1515 side = None 1516 else: 1517 side = expression.side 1518 1519 op_sql = " ".join( 1520 op 1521 for op in ( 1522 expression.method, 1523 "GLOBAL" if expression.args.get("global") else None, 1524 side, 1525 expression.kind, 1526 expression.hint if self.JOIN_HINTS else None, 1527 ) 1528 if op 1529 ) 1530 on_sql = self.sql(expression, "on") 1531 using = expression.args.get("using") 1532 1533 if not on_sql and using: 1534 on_sql = csv(*(self.sql(column) for column in using)) 1535 1536 this_sql = self.sql(expression, "this") 1537 1538 if on_sql: 1539 on_sql = self.indent(on_sql, skip_first=True) 1540 space = self.seg(" " * self.pad) if self.pretty else " " 1541 if using: 1542 on_sql = f"{space}USING ({on_sql})" 1543 else: 1544 on_sql = f"{space}ON {on_sql}" 1545 elif not op_sql: 1546 return f", {this_sql}" 1547 1548 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 1549 return f"{self.seg(op_sql)} {this_sql}{on_sql}" 1550 1551 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 1552 args = self.expressions(expression, flat=True) 1553 args = f"({args})" if len(args.split(",")) > 1 else args 1554 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 1555 1556 def lateral_sql(self, expression: exp.Lateral) -> str: 1557 this = self.sql(expression, "this") 1558 1559 if isinstance(expression.this, exp.Subquery): 1560 return f"LATERAL {this}" 1561 1562 if expression.args.get("view"): 1563 alias = expression.args["alias"] 1564 columns = self.expressions(alias, key="columns", flat=True) 1565 table = f" {alias.name}" if alias.name else "" 1566 columns = f" AS {columns}" if columns else "" 1567 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 1568 return f"{op_sql}{self.sep()}{this}{table}{columns}" 1569 1570 alias = self.sql(expression, "alias") 1571 alias = f" AS {alias}" if alias else "" 1572 return f"LATERAL {this}{alias}" 1573 1574 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 1575 this = self.sql(expression, "this") 1576 args = ", ".join( 1577 sql 1578 for sql in ( 1579 self.sql(expression, "offset"), 1580 self.sql(expression, "expression"), 1581 ) 1582 if sql 1583 ) 1584 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args}" 1585 1586 def offset_sql(self, expression: exp.Offset) -> str: 1587 this = self.sql(expression, "this") 1588 return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}" 1589 1590 def setitem_sql(self, expression: exp.SetItem) -> str: 1591 kind = self.sql(expression, "kind") 1592 kind = f"{kind} " if kind else "" 1593 this = self.sql(expression, "this") 1594 expressions = self.expressions(expression) 1595 collate = self.sql(expression, "collate") 1596 collate = f" COLLATE {collate}" if collate else "" 1597 global_ = "GLOBAL " if expression.args.get("global") else "" 1598 return f"{global_}{kind}{this}{expressions}{collate}" 1599 1600 def set_sql(self, expression: exp.Set) -> str: 1601 expressions = ( 1602 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 1603 ) 1604 tag = " TAG" if expression.args.get("tag") else "" 1605 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 1606 1607 def pragma_sql(self, expression: exp.Pragma) -> str: 1608 return f"PRAGMA {self.sql(expression, 'this')}" 1609 1610 def lock_sql(self, expression: exp.Lock) -> str: 1611 if not self.LOCKING_READS_SUPPORTED: 1612 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 1613 return "" 1614 1615 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 1616 expressions = self.expressions(expression, flat=True) 1617 expressions = f" OF {expressions}" if expressions else "" 1618 wait = expression.args.get("wait") 1619 1620 if wait is not None: 1621 if isinstance(wait, exp.Literal): 1622 wait = f" WAIT {self.sql(wait)}" 1623 else: 1624 wait = " NOWAIT" if wait else " SKIP LOCKED" 1625 1626 return f"{lock_type}{expressions}{wait or ''}" 1627 1628 def literal_sql(self, expression: exp.Literal) -> str: 1629 text = expression.this or "" 1630 if expression.is_string: 1631 text = f"{self.QUOTE_START}{self.escape_str(text)}{self.QUOTE_END}" 1632 return text 1633 1634 def escape_str(self, text: str) -> str: 1635 text = text.replace(self.QUOTE_END, self._escaped_quote_end) 1636 if self.UNESCAPED_SEQUENCE_TABLE: 1637 text = text.translate(self.UNESCAPED_SEQUENCE_TABLE) 1638 elif self.pretty: 1639 text = text.replace("\n", self.SENTINEL_LINE_BREAK) 1640 return text 1641 1642 def loaddata_sql(self, expression: exp.LoadData) -> str: 1643 local = " LOCAL" if expression.args.get("local") else "" 1644 inpath = f" INPATH {self.sql(expression, 'inpath')}" 1645 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 1646 this = f" INTO TABLE {self.sql(expression, 'this')}" 1647 partition = self.sql(expression, "partition") 1648 partition = f" {partition}" if partition else "" 1649 input_format = self.sql(expression, "input_format") 1650 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 1651 serde = self.sql(expression, "serde") 1652 serde = f" SERDE {serde}" if serde else "" 1653 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 1654 1655 def null_sql(self, *_) -> str: 1656 return "NULL" 1657 1658 def boolean_sql(self, expression: exp.Boolean) -> str: 1659 return "TRUE" if expression.this else "FALSE" 1660 1661 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 1662 this = self.sql(expression, "this") 1663 this = f"{this} " if this else this 1664 return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat) # type: ignore 1665 1666 def cluster_sql(self, expression: exp.Cluster) -> str: 1667 return self.op_expressions("CLUSTER BY", expression) 1668 1669 def distribute_sql(self, expression: exp.Distribute) -> str: 1670 return self.op_expressions("DISTRIBUTE BY", expression) 1671 1672 def sort_sql(self, expression: exp.Sort) -> str: 1673 return self.op_expressions("SORT BY", expression) 1674 1675 def ordered_sql(self, expression: exp.Ordered) -> str: 1676 desc = expression.args.get("desc") 1677 asc = not desc 1678 1679 nulls_first = expression.args.get("nulls_first") 1680 nulls_last = not nulls_first 1681 nulls_are_large = self.NULL_ORDERING == "nulls_are_large" 1682 nulls_are_small = self.NULL_ORDERING == "nulls_are_small" 1683 nulls_are_last = self.NULL_ORDERING == "nulls_are_last" 1684 1685 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 1686 nulls_sort_change = "" 1687 if nulls_first and ( 1688 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 1689 ): 1690 nulls_sort_change = " NULLS FIRST" 1691 elif ( 1692 nulls_last 1693 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 1694 and not nulls_are_last 1695 ): 1696 nulls_sort_change = " NULLS LAST" 1697 1698 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 1699 self.unsupported( 1700 "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect" 1701 ) 1702 nulls_sort_change = "" 1703 1704 return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}" 1705 1706 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 1707 partition = self.partition_by_sql(expression) 1708 order = self.sql(expression, "order") 1709 measures = self.expressions(expression, key="measures") 1710 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 1711 rows = self.sql(expression, "rows") 1712 rows = self.seg(rows) if rows else "" 1713 after = self.sql(expression, "after") 1714 after = self.seg(after) if after else "" 1715 pattern = self.sql(expression, "pattern") 1716 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 1717 definition_sqls = [ 1718 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 1719 for definition in expression.args.get("define", []) 1720 ] 1721 definitions = self.expressions(sqls=definition_sqls) 1722 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 1723 body = "".join( 1724 ( 1725 partition, 1726 order, 1727 measures, 1728 rows, 1729 after, 1730 pattern, 1731 define, 1732 ) 1733 ) 1734 alias = self.sql(expression, "alias") 1735 alias = f" {alias}" if alias else "" 1736 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 1737 1738 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 1739 limit: t.Optional[exp.Fetch | exp.Limit] = expression.args.get("limit") 1740 1741 # If the limit is generated as TOP, we need to ensure it's not generated twice 1742 with_offset_limit_modifiers = not isinstance(limit, exp.Limit) or not self.LIMIT_IS_TOP 1743 1744 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 1745 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 1746 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 1747 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 1748 1749 fetch = isinstance(limit, exp.Fetch) 1750 1751 offset_limit_modifiers = ( 1752 self.offset_limit_modifiers(expression, fetch, limit) 1753 if with_offset_limit_modifiers 1754 else [] 1755 ) 1756 1757 return csv( 1758 *sqls, 1759 *[self.sql(join) for join in expression.args.get("joins") or []], 1760 self.sql(expression, "connect"), 1761 self.sql(expression, "match"), 1762 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 1763 self.sql(expression, "where"), 1764 self.sql(expression, "group"), 1765 self.sql(expression, "having"), 1766 *self.after_having_modifiers(expression), 1767 self.sql(expression, "order"), 1768 *offset_limit_modifiers, 1769 *self.after_limit_modifiers(expression), 1770 sep="", 1771 ) 1772 1773 def offset_limit_modifiers( 1774 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 1775 ) -> t.List[str]: 1776 return [ 1777 self.sql(expression, "offset") if fetch else self.sql(limit), 1778 self.sql(limit) if fetch else self.sql(expression, "offset"), 1779 ] 1780 1781 def after_having_modifiers(self, expression: exp.Expression) -> t.List[str]: 1782 return [ 1783 self.sql(expression, "qualify"), 1784 self.seg("WINDOW ") + self.expressions(expression, key="windows", flat=True) 1785 if expression.args.get("windows") 1786 else "", 1787 self.sql(expression, "distribute"), 1788 self.sql(expression, "sort"), 1789 self.sql(expression, "cluster"), 1790 ] 1791 1792 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 1793 locks = self.expressions(expression, key="locks", sep=" ") 1794 locks = f" {locks}" if locks else "" 1795 return [locks, self.sql(expression, "sample")] 1796 1797 def select_sql(self, expression: exp.Select) -> str: 1798 hint = self.sql(expression, "hint") 1799 distinct = self.sql(expression, "distinct") 1800 distinct = f" {distinct}" if distinct else "" 1801 kind = self.sql(expression, "kind").upper() 1802 limit = expression.args.get("limit") 1803 top = ( 1804 self.limit_sql(limit, top=True) 1805 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP 1806 else "" 1807 ) 1808 1809 expressions = self.expressions(expression) 1810 1811 if kind: 1812 if kind in self.SELECT_KINDS: 1813 kind = f" AS {kind}" 1814 else: 1815 if kind == "STRUCT": 1816 expressions = self.expressions( 1817 sqls=[ 1818 self.sql( 1819 exp.Struct( 1820 expressions=[ 1821 exp.column(e.output_name).eq( 1822 e.this if isinstance(e, exp.Alias) else e 1823 ) 1824 for e in expression.expressions 1825 ] 1826 ) 1827 ) 1828 ] 1829 ) 1830 kind = "" 1831 1832 expressions = f"{self.sep()}{expressions}" if expressions else expressions 1833 sql = self.query_modifiers( 1834 expression, 1835 f"SELECT{top}{hint}{distinct}{kind}{expressions}", 1836 self.sql(expression, "into", comment=False), 1837 self.sql(expression, "from", comment=False), 1838 ) 1839 return self.prepend_ctes(expression, sql) 1840 1841 def schema_sql(self, expression: exp.Schema) -> str: 1842 this = self.sql(expression, "this") 1843 this = f"{this} " if this else "" 1844 sql = self.schema_columns_sql(expression) 1845 return f"{this}{sql}" 1846 1847 def schema_columns_sql(self, expression: exp.Schema) -> str: 1848 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 1849 1850 def star_sql(self, expression: exp.Star) -> str: 1851 except_ = self.expressions(expression, key="except", flat=True) 1852 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else "" 1853 replace = self.expressions(expression, key="replace", flat=True) 1854 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else "" 1855 return f"*{except_}{replace}" 1856 1857 def parameter_sql(self, expression: exp.Parameter) -> str: 1858 this = self.sql(expression, "this") 1859 return f"{self.PARAMETER_TOKEN}{this}" if self.SUPPORTS_PARAMETERS else this 1860 1861 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 1862 this = self.sql(expression, "this") 1863 kind = expression.text("kind") 1864 if kind: 1865 kind = f"{kind}." 1866 return f"@@{kind}{this}" 1867 1868 def placeholder_sql(self, expression: exp.Placeholder) -> str: 1869 return f":{expression.name}" if expression.name else "?" 1870 1871 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 1872 alias = self.sql(expression, "alias") 1873 alias = f"{sep}{alias}" if alias else "" 1874 1875 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) 1876 pivots = f" {pivots}" if pivots else "" 1877 1878 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 1879 return self.prepend_ctes(expression, sql) 1880 1881 def qualify_sql(self, expression: exp.Qualify) -> str: 1882 this = self.indent(self.sql(expression, "this")) 1883 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 1884 1885 def union_sql(self, expression: exp.Union) -> str: 1886 return self.prepend_ctes( 1887 expression, 1888 self.set_operation(expression, self.union_op(expression)), 1889 ) 1890 1891 def union_op(self, expression: exp.Union) -> str: 1892 kind = " DISTINCT" if self.EXPLICIT_UNION else "" 1893 kind = kind if expression.args.get("distinct") else " ALL" 1894 by_name = " BY NAME" if expression.args.get("by_name") else "" 1895 return f"UNION{kind}{by_name}" 1896 1897 def unnest_sql(self, expression: exp.Unnest) -> str: 1898 args = self.expressions(expression, flat=True) 1899 1900 alias = expression.args.get("alias") 1901 offset = expression.args.get("offset") 1902 1903 if self.UNNEST_WITH_ORDINALITY: 1904 if alias and isinstance(offset, exp.Expression): 1905 alias = alias.copy() 1906 alias.append("columns", offset.copy()) 1907 1908 if alias and self.UNNEST_COLUMN_ONLY: 1909 columns = alias.columns 1910 alias = self.sql(columns[0]) if columns else "" 1911 else: 1912 alias = self.sql(alias) 1913 1914 alias = f" AS {alias}" if alias else alias 1915 if self.UNNEST_WITH_ORDINALITY: 1916 suffix = f" WITH ORDINALITY{alias}" if offset else alias 1917 else: 1918 if isinstance(offset, exp.Expression): 1919 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 1920 elif offset: 1921 suffix = f"{alias} WITH OFFSET" 1922 else: 1923 suffix = alias 1924 1925 return f"UNNEST({args}){suffix}" 1926 1927 def where_sql(self, expression: exp.Where) -> str: 1928 this = self.indent(self.sql(expression, "this")) 1929 return f"{self.seg('WHERE')}{self.sep()}{this}" 1930 1931 def window_sql(self, expression: exp.Window) -> str: 1932 this = self.sql(expression, "this") 1933 partition = self.partition_by_sql(expression) 1934 order = expression.args.get("order") 1935 order = self.order_sql(order, flat=True) if order else "" 1936 spec = self.sql(expression, "spec") 1937 alias = self.sql(expression, "alias") 1938 over = self.sql(expression, "over") or "OVER" 1939 1940 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 1941 1942 first = expression.args.get("first") 1943 if first is None: 1944 first = "" 1945 else: 1946 first = "FIRST" if first else "LAST" 1947 1948 if not partition and not order and not spec and alias: 1949 return f"{this} {alias}" 1950 1951 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 1952 return f"{this} ({args})" 1953 1954 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 1955 partition = self.expressions(expression, key="partition_by", flat=True) 1956 return f"PARTITION BY {partition}" if partition else "" 1957 1958 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 1959 kind = self.sql(expression, "kind") 1960 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 1961 end = ( 1962 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 1963 or "CURRENT ROW" 1964 ) 1965 return f"{kind} BETWEEN {start} AND {end}" 1966 1967 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1968 this = self.sql(expression, "this") 1969 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 1970 return f"{this} WITHIN GROUP ({expression_sql})" 1971 1972 def between_sql(self, expression: exp.Between) -> str: 1973 this = self.sql(expression, "this") 1974 low = self.sql(expression, "low") 1975 high = self.sql(expression, "high") 1976 return f"{this} BETWEEN {low} AND {high}" 1977 1978 def bracket_sql(self, expression: exp.Bracket) -> str: 1979 expressions = apply_index_offset(expression.this, expression.expressions, self.INDEX_OFFSET) 1980 expressions_sql = ", ".join(self.sql(e) for e in expressions) 1981 1982 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 1983 1984 def safebracket_sql(self, expression: exp.SafeBracket) -> str: 1985 return self.bracket_sql(expression) 1986 1987 def all_sql(self, expression: exp.All) -> str: 1988 return f"ALL {self.wrap(expression)}" 1989 1990 def any_sql(self, expression: exp.Any) -> str: 1991 this = self.sql(expression, "this") 1992 if isinstance(expression.this, exp.Subqueryable): 1993 this = self.wrap(this) 1994 return f"ANY {this}" 1995 1996 def exists_sql(self, expression: exp.Exists) -> str: 1997 return f"EXISTS{self.wrap(expression)}" 1998 1999 def case_sql(self, expression: exp.Case) -> str: 2000 this = self.sql(expression, "this") 2001 statements = [f"CASE {this}" if this else "CASE"] 2002 2003 for e in expression.args["ifs"]: 2004 statements.append(f"WHEN {self.sql(e, 'this')}") 2005 statements.append(f"THEN {self.sql(e, 'true')}") 2006 2007 default = self.sql(expression, "default") 2008 2009 if default: 2010 statements.append(f"ELSE {default}") 2011 2012 statements.append("END") 2013 2014 if self.pretty and self.text_width(statements) > self.max_text_width: 2015 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2016 2017 return " ".join(statements) 2018 2019 def constraint_sql(self, expression: exp.Constraint) -> str: 2020 this = self.sql(expression, "this") 2021 expressions = self.expressions(expression, flat=True) 2022 return f"CONSTRAINT {this} {expressions}" 2023 2024 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2025 order = expression.args.get("order") 2026 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2027 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2028 2029 def extract_sql(self, expression: exp.Extract) -> str: 2030 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2031 expression_sql = self.sql(expression, "expression") 2032 return f"EXTRACT({this} FROM {expression_sql})" 2033 2034 def trim_sql(self, expression: exp.Trim) -> str: 2035 trim_type = self.sql(expression, "position") 2036 2037 if trim_type == "LEADING": 2038 return self.func("LTRIM", expression.this) 2039 elif trim_type == "TRAILING": 2040 return self.func("RTRIM", expression.this) 2041 else: 2042 return self.func("TRIM", expression.this, expression.expression) 2043 2044 def safeconcat_sql(self, expression: exp.SafeConcat) -> str: 2045 expressions = expression.expressions 2046 if self.STRICT_STRING_CONCAT: 2047 expressions = (exp.cast(e, "text") for e in expressions) 2048 return self.func("CONCAT", *expressions) 2049 2050 def check_sql(self, expression: exp.Check) -> str: 2051 this = self.sql(expression, key="this") 2052 return f"CHECK ({this})" 2053 2054 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2055 expressions = self.expressions(expression, flat=True) 2056 reference = self.sql(expression, "reference") 2057 reference = f" {reference}" if reference else "" 2058 delete = self.sql(expression, "delete") 2059 delete = f" ON DELETE {delete}" if delete else "" 2060 update = self.sql(expression, "update") 2061 update = f" ON UPDATE {update}" if update else "" 2062 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2063 2064 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2065 expressions = self.expressions(expression, flat=True) 2066 options = self.expressions(expression, key="options", flat=True, sep=" ") 2067 options = f" {options}" if options else "" 2068 return f"PRIMARY KEY ({expressions}){options}" 2069 2070 def if_sql(self, expression: exp.If) -> str: 2071 expression = expression.copy() 2072 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2073 2074 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2075 modifier = expression.args.get("modifier") 2076 modifier = f" {modifier}" if modifier else "" 2077 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2078 2079 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2080 return f"{self.sql(expression, 'this')}: {self.sql(expression, 'expression')}" 2081 2082 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2083 return f"{self.sql(expression, 'this')} FORMAT JSON" 2084 2085 def jsonobject_sql(self, expression: exp.JSONObject) -> str: 2086 null_handling = expression.args.get("null_handling") 2087 null_handling = f" {null_handling}" if null_handling else "" 2088 unique_keys = expression.args.get("unique_keys") 2089 if unique_keys is not None: 2090 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2091 else: 2092 unique_keys = "" 2093 return_type = self.sql(expression, "return_type") 2094 return_type = f" RETURNING {return_type}" if return_type else "" 2095 encoding = self.sql(expression, "encoding") 2096 encoding = f" ENCODING {encoding}" if encoding else "" 2097 return self.func( 2098 "JSON_OBJECT", 2099 *expression.expressions, 2100 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2101 ) 2102 2103 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2104 null_handling = expression.args.get("null_handling") 2105 null_handling = f" {null_handling}" if null_handling else "" 2106 return_type = self.sql(expression, "return_type") 2107 return_type = f" RETURNING {return_type}" if return_type else "" 2108 strict = " STRICT" if expression.args.get("strict") else "" 2109 return self.func( 2110 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2111 ) 2112 2113 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2114 this = self.sql(expression, "this") 2115 order = self.sql(expression, "order") 2116 null_handling = expression.args.get("null_handling") 2117 null_handling = f" {null_handling}" if null_handling else "" 2118 return_type = self.sql(expression, "return_type") 2119 return_type = f" RETURNING {return_type}" if return_type else "" 2120 strict = " STRICT" if expression.args.get("strict") else "" 2121 return self.func( 2122 "JSON_ARRAYAGG", 2123 this, 2124 suffix=f"{order}{null_handling}{return_type}{strict})", 2125 ) 2126 2127 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2128 this = self.sql(expression, "this") 2129 kind = self.sql(expression, "kind") 2130 kind = f" {kind}" if kind else "" 2131 path = self.sql(expression, "path") 2132 path = f" PATH {path}" if path else "" 2133 return f"{this}{kind}{path}" 2134 2135 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2136 this = self.sql(expression, "this") 2137 path = self.sql(expression, "path") 2138 path = f", {path}" if path else "" 2139 error_handling = expression.args.get("error_handling") 2140 error_handling = f" {error_handling}" if error_handling else "" 2141 empty_handling = expression.args.get("empty_handling") 2142 empty_handling = f" {empty_handling}" if empty_handling else "" 2143 columns = f" COLUMNS ({self.expressions(expression, skip_first=True)})" 2144 return self.func( 2145 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling}{columns})" 2146 ) 2147 2148 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2149 this = self.sql(expression, "this") 2150 kind = self.sql(expression, "kind") 2151 path = self.sql(expression, "path") 2152 path = f" {path}" if path else "" 2153 as_json = " AS JSON" if expression.args.get("as_json") else "" 2154 return f"{this} {kind}{path}{as_json}" 2155 2156 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2157 this = self.sql(expression, "this") 2158 path = self.sql(expression, "path") 2159 path = f", {path}" if path else "" 2160 expressions = self.expressions(expression) 2161 with_ = ( 2162 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2163 if expressions 2164 else "" 2165 ) 2166 return f"OPENJSON({this}{path}){with_}" 2167 2168 def in_sql(self, expression: exp.In) -> str: 2169 query = expression.args.get("query") 2170 unnest = expression.args.get("unnest") 2171 field = expression.args.get("field") 2172 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2173 2174 if query: 2175 in_sql = self.wrap(query) 2176 elif unnest: 2177 in_sql = self.in_unnest_op(unnest) 2178 elif field: 2179 in_sql = self.sql(field) 2180 else: 2181 in_sql = f"({self.expressions(expression, flat=True)})" 2182 2183 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2184 2185 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2186 return f"(SELECT {self.sql(unnest)})" 2187 2188 def interval_sql(self, expression: exp.Interval) -> str: 2189 unit = self.sql(expression, "unit") 2190 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2191 unit = self.TIME_PART_SINGULARS.get(unit.lower(), unit) 2192 unit = f" {unit}" if unit else "" 2193 2194 if self.SINGLE_STRING_INTERVAL: 2195 this = expression.this.name if expression.this else "" 2196 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2197 2198 this = self.sql(expression, "this") 2199 if this: 2200 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2201 this = f" {this}" if unwrapped else f" ({this})" 2202 2203 return f"INTERVAL{this}{unit}" 2204 2205 def return_sql(self, expression: exp.Return) -> str: 2206 return f"RETURN {self.sql(expression, 'this')}" 2207 2208 def reference_sql(self, expression: exp.Reference) -> str: 2209 this = self.sql(expression, "this") 2210 expressions = self.expressions(expression, flat=True) 2211 expressions = f"({expressions})" if expressions else "" 2212 options = self.expressions(expression, key="options", flat=True, sep=" ") 2213 options = f" {options}" if options else "" 2214 return f"REFERENCES {this}{expressions}{options}" 2215 2216 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2217 return self.func(expression.name, *expression.expressions) 2218 2219 def paren_sql(self, expression: exp.Paren) -> str: 2220 if isinstance(expression.unnest(), exp.Select): 2221 sql = self.wrap(expression) 2222 else: 2223 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2224 sql = f"({sql}{self.seg(')', sep='')}" 2225 2226 return self.prepend_ctes(expression, sql) 2227 2228 def neg_sql(self, expression: exp.Neg) -> str: 2229 # This makes sure we don't convert "- - 5" to "--5", which is a comment 2230 this_sql = self.sql(expression, "this") 2231 sep = " " if this_sql[0] == "-" else "" 2232 return f"-{sep}{this_sql}" 2233 2234 def not_sql(self, expression: exp.Not) -> str: 2235 return f"NOT {self.sql(expression, 'this')}" 2236 2237 def alias_sql(self, expression: exp.Alias) -> str: 2238 alias = self.sql(expression, "alias") 2239 alias = f" AS {alias}" if alias else "" 2240 return f"{self.sql(expression, 'this')}{alias}" 2241 2242 def aliases_sql(self, expression: exp.Aliases) -> str: 2243 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 2244 2245 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 2246 this = self.sql(expression, "this") 2247 zone = self.sql(expression, "zone") 2248 return f"{this} AT TIME ZONE {zone}" 2249 2250 def add_sql(self, expression: exp.Add) -> str: 2251 return self.binary(expression, "+") 2252 2253 def and_sql(self, expression: exp.And) -> str: 2254 return self.connector_sql(expression, "AND") 2255 2256 def xor_sql(self, expression: exp.Xor) -> str: 2257 return self.connector_sql(expression, "XOR") 2258 2259 def connector_sql(self, expression: exp.Connector, op: str) -> str: 2260 if not self.pretty: 2261 return self.binary(expression, op) 2262 2263 sqls = tuple( 2264 self.maybe_comment(self.sql(e), e, e.parent.comments or []) if i != 1 else self.sql(e) 2265 for i, e in enumerate(expression.flatten(unnest=False)) 2266 ) 2267 2268 sep = "\n" if self.text_width(sqls) > self.max_text_width else " " 2269 return f"{sep}{op} ".join(sqls) 2270 2271 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 2272 return self.binary(expression, "&") 2273 2274 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 2275 return self.binary(expression, "<<") 2276 2277 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 2278 return f"~{self.sql(expression, 'this')}" 2279 2280 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 2281 return self.binary(expression, "|") 2282 2283 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 2284 return self.binary(expression, ">>") 2285 2286 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 2287 return self.binary(expression, "^") 2288 2289 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 2290 format_sql = self.sql(expression, "format") 2291 format_sql = f" FORMAT {format_sql}" if format_sql else "" 2292 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')}{format_sql})" 2293 2294 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 2295 zone = self.sql(expression, "this") 2296 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 2297 2298 def collate_sql(self, expression: exp.Collate) -> str: 2299 return self.binary(expression, "COLLATE") 2300 2301 def command_sql(self, expression: exp.Command) -> str: 2302 return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}" 2303 2304 def comment_sql(self, expression: exp.Comment) -> str: 2305 this = self.sql(expression, "this") 2306 kind = expression.args["kind"] 2307 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 2308 expression_sql = self.sql(expression, "expression") 2309 return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}" 2310 2311 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 2312 this = self.sql(expression, "this") 2313 delete = " DELETE" if expression.args.get("delete") else "" 2314 recompress = self.sql(expression, "recompress") 2315 recompress = f" RECOMPRESS {recompress}" if recompress else "" 2316 to_disk = self.sql(expression, "to_disk") 2317 to_disk = f" TO DISK {to_disk}" if to_disk else "" 2318 to_volume = self.sql(expression, "to_volume") 2319 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 2320 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 2321 2322 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 2323 where = self.sql(expression, "where") 2324 group = self.sql(expression, "group") 2325 aggregates = self.expressions(expression, key="aggregates") 2326 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 2327 2328 if not (where or group or aggregates) and len(expression.expressions) == 1: 2329 return f"TTL {self.expressions(expression, flat=True)}" 2330 2331 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 2332 2333 def transaction_sql(self, expression: exp.Transaction) -> str: 2334 return "BEGIN" 2335 2336 def commit_sql(self, expression: exp.Commit) -> str: 2337 chain = expression.args.get("chain") 2338 if chain is not None: 2339 chain = " AND CHAIN" if chain else " AND NO CHAIN" 2340 2341 return f"COMMIT{chain or ''}" 2342 2343 def rollback_sql(self, expression: exp.Rollback) -> str: 2344 savepoint = expression.args.get("savepoint") 2345 savepoint = f" TO {savepoint}" if savepoint else "" 2346 return f"ROLLBACK{savepoint}" 2347 2348 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 2349 this = self.sql(expression, "this") 2350 2351 dtype = self.sql(expression, "dtype") 2352 if dtype: 2353 collate = self.sql(expression, "collate") 2354 collate = f" COLLATE {collate}" if collate else "" 2355 using = self.sql(expression, "using") 2356 using = f" USING {using}" if using else "" 2357 return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}" 2358 2359 default = self.sql(expression, "default") 2360 if default: 2361 return f"ALTER COLUMN {this} SET DEFAULT {default}" 2362 2363 if not expression.args.get("drop"): 2364 self.unsupported("Unsupported ALTER COLUMN syntax") 2365 2366 return f"ALTER COLUMN {this} DROP DEFAULT" 2367 2368 def renametable_sql(self, expression: exp.RenameTable) -> str: 2369 if not self.RENAME_TABLE_WITH_DB: 2370 # Remove db from tables 2371 expression = expression.transform( 2372 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 2373 ) 2374 this = self.sql(expression, "this") 2375 return f"RENAME TO {this}" 2376 2377 def altertable_sql(self, expression: exp.AlterTable) -> str: 2378 actions = expression.args["actions"] 2379 2380 if isinstance(actions[0], exp.ColumnDef): 2381 if self.ALTER_TABLE_ADD_COLUMN_KEYWORD: 2382 actions = self.expressions( 2383 expression, 2384 key="actions", 2385 prefix="ADD COLUMN ", 2386 ) 2387 else: 2388 actions = f"ADD {self.expressions(expression, key='actions')}" 2389 elif isinstance(actions[0], exp.Schema): 2390 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 2391 elif isinstance(actions[0], exp.Delete): 2392 actions = self.expressions(expression, key="actions", flat=True) 2393 else: 2394 actions = self.expressions(expression, key="actions") 2395 2396 exists = " IF EXISTS" if expression.args.get("exists") else "" 2397 only = " ONLY" if expression.args.get("only") else "" 2398 return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')} {actions}" 2399 2400 def droppartition_sql(self, expression: exp.DropPartition) -> str: 2401 expressions = self.expressions(expression) 2402 exists = " IF EXISTS " if expression.args.get("exists") else " " 2403 return f"DROP{exists}{expressions}" 2404 2405 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 2406 this = self.sql(expression, "this") 2407 expression_ = self.sql(expression, "expression") 2408 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD" 2409 2410 enforced = expression.args.get("enforced") 2411 if enforced is not None: 2412 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}" 2413 2414 return f"{add_constraint} {expression_}" 2415 2416 def distinct_sql(self, expression: exp.Distinct) -> str: 2417 this = self.expressions(expression, flat=True) 2418 this = f" {this}" if this else "" 2419 2420 on = self.sql(expression, "on") 2421 on = f" ON {on}" if on else "" 2422 return f"DISTINCT{this}{on}" 2423 2424 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 2425 return f"{self.sql(expression, 'this')} IGNORE NULLS" 2426 2427 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 2428 return f"{self.sql(expression, 'this')} RESPECT NULLS" 2429 2430 def intdiv_sql(self, expression: exp.IntDiv) -> str: 2431 return self.sql( 2432 exp.Cast( 2433 this=exp.Div(this=expression.this.copy(), expression=expression.expression.copy()), 2434 to=exp.DataType(this=exp.DataType.Type.INT), 2435 ) 2436 ) 2437 2438 def dpipe_sql(self, expression: exp.DPipe) -> str: 2439 return self.binary(expression, "||") 2440 2441 def safedpipe_sql(self, expression: exp.SafeDPipe) -> str: 2442 if self.STRICT_STRING_CONCAT: 2443 return self.func("CONCAT", *(exp.cast(e, "text") for e in expression.flatten())) 2444 return self.dpipe_sql(expression) 2445 2446 def div_sql(self, expression: exp.Div) -> str: 2447 return self.binary(expression, "/") 2448 2449 def overlaps_sql(self, expression: exp.Overlaps) -> str: 2450 return self.binary(expression, "OVERLAPS") 2451 2452 def distance_sql(self, expression: exp.Distance) -> str: 2453 return self.binary(expression, "<->") 2454 2455 def dot_sql(self, expression: exp.Dot) -> str: 2456 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 2457 2458 def eq_sql(self, expression: exp.EQ) -> str: 2459 return self.binary(expression, "=") 2460 2461 def escape_sql(self, expression: exp.Escape) -> str: 2462 return self.binary(expression, "ESCAPE") 2463 2464 def glob_sql(self, expression: exp.Glob) -> str: 2465 return self.binary(expression, "GLOB") 2466 2467 def gt_sql(self, expression: exp.GT) -> str: 2468 return self.binary(expression, ">") 2469 2470 def gte_sql(self, expression: exp.GTE) -> str: 2471 return self.binary(expression, ">=") 2472 2473 def ilike_sql(self, expression: exp.ILike) -> str: 2474 return self.binary(expression, "ILIKE") 2475 2476 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 2477 return self.binary(expression, "ILIKE ANY") 2478 2479 def is_sql(self, expression: exp.Is) -> str: 2480 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 2481 return self.sql( 2482 expression.this if expression.expression.this else exp.not_(expression.this) 2483 ) 2484 return self.binary(expression, "IS") 2485 2486 def like_sql(self, expression: exp.Like) -> str: 2487 return self.binary(expression, "LIKE") 2488 2489 def likeany_sql(self, expression: exp.LikeAny) -> str: 2490 return self.binary(expression, "LIKE ANY") 2491 2492 def similarto_sql(self, expression: exp.SimilarTo) -> str: 2493 return self.binary(expression, "SIMILAR TO") 2494 2495 def lt_sql(self, expression: exp.LT) -> str: 2496 return self.binary(expression, "<") 2497 2498 def lte_sql(self, expression: exp.LTE) -> str: 2499 return self.binary(expression, "<=") 2500 2501 def mod_sql(self, expression: exp.Mod) -> str: 2502 return self.binary(expression, "%") 2503 2504 def mul_sql(self, expression: exp.Mul) -> str: 2505 return self.binary(expression, "*") 2506 2507 def neq_sql(self, expression: exp.NEQ) -> str: 2508 return self.binary(expression, "<>") 2509 2510 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 2511 return self.binary(expression, "IS NOT DISTINCT FROM") 2512 2513 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 2514 return self.binary(expression, "IS DISTINCT FROM") 2515 2516 def or_sql(self, expression: exp.Or) -> str: 2517 return self.connector_sql(expression, "OR") 2518 2519 def slice_sql(self, expression: exp.Slice) -> str: 2520 return self.binary(expression, ":") 2521 2522 def sub_sql(self, expression: exp.Sub) -> str: 2523 return self.binary(expression, "-") 2524 2525 def trycast_sql(self, expression: exp.TryCast) -> str: 2526 return self.cast_sql(expression, safe_prefix="TRY_") 2527 2528 def use_sql(self, expression: exp.Use) -> str: 2529 kind = self.sql(expression, "kind") 2530 kind = f" {kind}" if kind else "" 2531 this = self.sql(expression, "this") 2532 this = f" {this}" if this else "" 2533 return f"USE{kind}{this}" 2534 2535 def binary(self, expression: exp.Binary, op: str) -> str: 2536 op = self.maybe_comment(op, comments=expression.comments) 2537 return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}" 2538 2539 def function_fallback_sql(self, expression: exp.Func) -> str: 2540 args = [] 2541 2542 for key in expression.arg_types: 2543 arg_value = expression.args.get(key) 2544 2545 if isinstance(arg_value, list): 2546 for value in arg_value: 2547 args.append(value) 2548 elif arg_value is not None: 2549 args.append(arg_value) 2550 2551 if self.normalize_functions: 2552 name = expression.sql_name() 2553 else: 2554 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 2555 2556 return self.func(name, *args) 2557 2558 def func( 2559 self, 2560 name: str, 2561 *args: t.Optional[exp.Expression | str], 2562 prefix: str = "(", 2563 suffix: str = ")", 2564 ) -> str: 2565 return f"{self.normalize_func(name)}{prefix}{self.format_args(*args)}{suffix}" 2566 2567 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 2568 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None) 2569 if self.pretty and self.text_width(arg_sqls) > self.max_text_width: 2570 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 2571 return ", ".join(arg_sqls) 2572 2573 def text_width(self, args: t.Iterable) -> int: 2574 return sum(len(arg) for arg in args) 2575 2576 def format_time(self, expression: exp.Expression) -> t.Optional[str]: 2577 return format_time( 2578 self.sql(expression, "format"), self.INVERSE_TIME_MAPPING, self.INVERSE_TIME_TRIE 2579 ) 2580 2581 def expressions( 2582 self, 2583 expression: t.Optional[exp.Expression] = None, 2584 key: t.Optional[str] = None, 2585 sqls: t.Optional[t.List[str]] = None, 2586 flat: bool = False, 2587 indent: bool = True, 2588 skip_first: bool = False, 2589 sep: str = ", ", 2590 prefix: str = "", 2591 ) -> str: 2592 expressions = expression.args.get(key or "expressions") if expression else sqls 2593 2594 if not expressions: 2595 return "" 2596 2597 if flat: 2598 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 2599 2600 num_sqls = len(expressions) 2601 2602 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly 2603 pad = " " * self.pad 2604 stripped_sep = sep.strip() 2605 2606 result_sqls = [] 2607 for i, e in enumerate(expressions): 2608 sql = self.sql(e, comment=False) 2609 if not sql: 2610 continue 2611 2612 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 2613 2614 if self.pretty: 2615 if self.leading_comma: 2616 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}") 2617 else: 2618 result_sqls.append( 2619 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}" 2620 ) 2621 else: 2622 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 2623 2624 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls) 2625 return self.indent(result_sql, skip_first=skip_first) if indent else result_sql 2626 2627 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 2628 flat = flat or isinstance(expression.parent, exp.Properties) 2629 expressions_sql = self.expressions(expression, flat=flat) 2630 if flat: 2631 return f"{op} {expressions_sql}" 2632 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 2633 2634 def naked_property(self, expression: exp.Property) -> str: 2635 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 2636 if not property_name: 2637 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 2638 return f"{property_name} {self.sql(expression, 'this')}" 2639 2640 def set_operation(self, expression: exp.Expression, op: str) -> str: 2641 this = self.sql(expression, "this") 2642 op = self.seg(op) 2643 return self.query_modifiers( 2644 expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}" 2645 ) 2646 2647 def tag_sql(self, expression: exp.Tag) -> str: 2648 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 2649 2650 def token_sql(self, token_type: TokenType) -> str: 2651 return self.TOKEN_MAPPING.get(token_type, token_type.name) 2652 2653 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 2654 this = self.sql(expression, "this") 2655 expressions = self.no_identify(self.expressions, expression) 2656 expressions = ( 2657 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 2658 ) 2659 return f"{this}{expressions}" 2660 2661 def joinhint_sql(self, expression: exp.JoinHint) -> str: 2662 this = self.sql(expression, "this") 2663 expressions = self.expressions(expression, flat=True) 2664 return f"{this}({expressions})" 2665 2666 def kwarg_sql(self, expression: exp.Kwarg) -> str: 2667 return self.binary(expression, "=>") 2668 2669 def when_sql(self, expression: exp.When) -> str: 2670 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 2671 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 2672 condition = self.sql(expression, "condition") 2673 condition = f" AND {condition}" if condition else "" 2674 2675 then_expression = expression.args.get("then") 2676 if isinstance(then_expression, exp.Insert): 2677 then = f"INSERT {self.sql(then_expression, 'this')}" 2678 if "expression" in then_expression.args: 2679 then += f" VALUES {self.sql(then_expression, 'expression')}" 2680 elif isinstance(then_expression, exp.Update): 2681 if isinstance(then_expression.args.get("expressions"), exp.Star): 2682 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 2683 else: 2684 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 2685 else: 2686 then = self.sql(then_expression) 2687 return f"WHEN {matched}{source}{condition} THEN {then}" 2688 2689 def merge_sql(self, expression: exp.Merge) -> str: 2690 table = expression.this 2691 table_alias = "" 2692 2693 hints = table.args.get("hints") 2694 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 2695 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 2696 table = table.copy() 2697 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 2698 2699 this = self.sql(table) 2700 using = f"USING {self.sql(expression, 'using')}" 2701 on = f"ON {self.sql(expression, 'on')}" 2702 expressions = self.expressions(expression, sep=" ") 2703 2704 return f"MERGE INTO {this}{table_alias} {using} {on} {expressions}" 2705 2706 def tochar_sql(self, expression: exp.ToChar) -> str: 2707 if expression.args.get("format"): 2708 self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function") 2709 2710 return self.sql(exp.cast(expression.this, "text")) 2711 2712 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 2713 this = self.sql(expression, "this") 2714 kind = self.sql(expression, "kind") 2715 settings_sql = self.expressions(expression, key="settings", sep=" ") 2716 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 2717 return f"{this}({kind}{args})" 2718 2719 def dictrange_sql(self, expression: exp.DictRange) -> str: 2720 this = self.sql(expression, "this") 2721 max = self.sql(expression, "max") 2722 min = self.sql(expression, "min") 2723 return f"{this}(MIN {min} MAX {max})" 2724 2725 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 2726 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 2727 2728 def oncluster_sql(self, expression: exp.OnCluster) -> str: 2729 return "" 2730 2731 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 2732 expressions = self.expressions(expression, key="expressions", flat=True) 2733 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 2734 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 2735 buckets = self.sql(expression, "buckets") 2736 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 2737 2738 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 2739 this = self.sql(expression, "this") 2740 having = self.sql(expression, "having") 2741 2742 if having: 2743 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 2744 2745 return self.func("ANY_VALUE", this) 2746 2747 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 2748 transform = self.func("TRANSFORM", *expression.expressions) 2749 row_format_before = self.sql(expression, "row_format_before") 2750 row_format_before = f" {row_format_before}" if row_format_before else "" 2751 record_writer = self.sql(expression, "record_writer") 2752 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 2753 using = f" USING {self.sql(expression, 'command_script')}" 2754 schema = self.sql(expression, "schema") 2755 schema = f" AS {schema}" if schema else "" 2756 row_format_after = self.sql(expression, "row_format_after") 2757 row_format_after = f" {row_format_after}" if row_format_after else "" 2758 record_reader = self.sql(expression, "record_reader") 2759 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 2760 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 2761 2762 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 2763 key_block_size = self.sql(expression, "key_block_size") 2764 if key_block_size: 2765 return f"KEY_BLOCK_SIZE = {key_block_size}" 2766 2767 using = self.sql(expression, "using") 2768 if using: 2769 return f"USING {using}" 2770 2771 parser = self.sql(expression, "parser") 2772 if parser: 2773 return f"WITH PARSER {parser}" 2774 2775 comment = self.sql(expression, "comment") 2776 if comment: 2777 return f"COMMENT {comment}" 2778 2779 visible = expression.args.get("visible") 2780 if visible is not None: 2781 return "VISIBLE" if visible else "INVISIBLE" 2782 2783 engine_attr = self.sql(expression, "engine_attr") 2784 if engine_attr: 2785 return f"ENGINE_ATTRIBUTE = {engine_attr}" 2786 2787 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 2788 if secondary_engine_attr: 2789 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 2790 2791 self.unsupported("Unsupported index constraint option.") 2792 return "" 2793 2794 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 2795 kind = self.sql(expression, "kind") 2796 kind = f"{kind} INDEX" if kind else "INDEX" 2797 this = self.sql(expression, "this") 2798 this = f" {this}" if this else "" 2799 index_type = self.sql(expression, "index_type") 2800 index_type = f" USING {index_type}" if index_type else "" 2801 schema = self.sql(expression, "schema") 2802 schema = f" {schema}" if schema else "" 2803 options = self.expressions(expression, key="options", sep=" ") 2804 options = f" {options}" if options else "" 2805 return f"{kind}{this}{index_type}{schema}{options}" 2806 2807 def nvl2_sql(self, expression: exp.Nvl2) -> str: 2808 if self.NVL2_SUPPORTED: 2809 return self.function_fallback_sql(expression) 2810 2811 case = exp.Case().when( 2812 expression.this.is_(exp.null()).not_(copy=False), 2813 expression.args["true"].copy(), 2814 copy=False, 2815 ) 2816 else_cond = expression.args.get("false") 2817 if else_cond: 2818 case.else_(else_cond.copy(), copy=False) 2819 2820 return self.sql(case) 2821 2822 def comprehension_sql(self, expression: exp.Comprehension) -> str: 2823 this = self.sql(expression, "this") 2824 expr = self.sql(expression, "expression") 2825 iterator = self.sql(expression, "iterator") 2826 condition = self.sql(expression, "condition") 2827 condition = f" IF {condition}" if condition else "" 2828 return f"{this} FOR {expr} IN {iterator}{condition}" 2829 2830 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 2831 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether or not to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether or not to normalize identifiers to lowercase. Default: False.
- pad: Determines the pad size in a formatted string. Default: 2.
- indent: Determines the indentation size in a formatted string. Default: 2.
- normalize_functions: Whether or not to normalize all function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Determines whether or not the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether or not to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True)
374 def __init__( 375 self, 376 pretty: t.Optional[bool] = None, 377 identify: str | bool = False, 378 normalize: bool = False, 379 pad: int = 2, 380 indent: int = 2, 381 normalize_functions: t.Optional[str | bool] = None, 382 unsupported_level: ErrorLevel = ErrorLevel.WARN, 383 max_unsupported: int = 3, 384 leading_comma: bool = False, 385 max_text_width: int = 80, 386 comments: bool = True, 387 ): 388 import sqlglot 389 390 self.pretty = pretty if pretty is not None else sqlglot.pretty 391 self.identify = identify 392 self.normalize = normalize 393 self.pad = pad 394 self._indent = indent 395 self.unsupported_level = unsupported_level 396 self.max_unsupported = max_unsupported 397 self.leading_comma = leading_comma 398 self.max_text_width = max_text_width 399 self.comments = comments 400 401 # This is both a Dialect property and a Generator argument, so we prioritize the latter 402 self.normalize_functions = ( 403 self.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 404 ) 405 406 self.unsupported_messages: t.List[str] = [] 407 self._escaped_quote_end: str = self.TOKENIZER_CLASS.STRING_ESCAPES[0] + self.QUOTE_END 408 self._escaped_identifier_end: str = ( 409 self.TOKENIZER_CLASS.IDENTIFIER_ESCAPES[0] + self.IDENTIFIER_END 410 ) 411 self._cache: t.Optional[t.Dict[int, str]] = None
TRANSFORMS =
{<class 'sqlglot.expressions.DateAdd'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TsOrDsAdd'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CheckColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>}
TYPE_MAPPING =
{<Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET'}
TIME_PART_SINGULARS =
{'microseconds': 'microsecond', 'seconds': 'second', 'minutes': 'minute', 'hours': 'hour', 'days': 'day', 'weeks': 'week', 'months': 'month', 'quarters': 'quarter', 'years': 'year'}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
@classmethod
def
can_identify(text: str, identify: str | bool = 'safe') -> bool:
269 @classmethod 270 def can_identify(cls, text: str, identify: str | bool = "safe") -> bool: 271 """Checks if text can be identified given an identify option. 272 273 Args: 274 text: The text to check. 275 identify: 276 "always" or `True`: Always returns true. 277 "safe": True if the identifier is case-insensitive. 278 279 Returns: 280 Whether or not the given text can be identified. 281 """ 282 if identify is True or identify == "always": 283 return True 284 285 if identify == "safe": 286 return not cls.case_sensitive(text) 287 288 return False
Checks if text can be identified given an identify option.
Arguments:
- text: The text to check.
- identify: "always" or
True
: Always returns true. "safe": True if the identifier is case-insensitive.
Returns:
Whether or not the given text can be identified.
TOKENIZER_CLASS =
<class 'sqlglot.tokens.Tokenizer'>
def
generate( self, expression: Optional[sqlglot.expressions.Expression], cache: Optional[Dict[int, str]] = None) -> str:
413 def generate( 414 self, 415 expression: t.Optional[exp.Expression], 416 cache: t.Optional[t.Dict[int, str]] = None, 417 ) -> str: 418 """ 419 Generates the SQL string corresponding to the given syntax tree. 420 421 Args: 422 expression: The syntax tree. 423 cache: An optional sql string cache. This leverages the hash of an Expression 424 which can be slow to compute, so only use it if you set _hash on each node. 425 426 Returns: 427 The SQL string corresponding to `expression`. 428 """ 429 if cache is not None: 430 self._cache = cache 431 432 self.unsupported_messages = [] 433 sql = self.sql(expression).strip() 434 self._cache = None 435 436 if self.unsupported_level == ErrorLevel.IGNORE: 437 return sql 438 439 if self.unsupported_level == ErrorLevel.WARN: 440 for msg in self.unsupported_messages: 441 logger.warning(msg) 442 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 443 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 444 445 if self.pretty: 446 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 447 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- cache: An optional sql string cache. This leverages the hash of an Expression which can be slow to compute, so only use it if you set _hash on each node.
Returns:
The SQL string corresponding to
expression
.
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None) -> str:
465 def maybe_comment( 466 self, 467 sql: str, 468 expression: t.Optional[exp.Expression] = None, 469 comments: t.Optional[t.List[str]] = None, 470 ) -> str: 471 comments = ( 472 ((expression and expression.comments) if comments is None else comments) # type: ignore 473 if self.comments 474 else None 475 ) 476 477 if not comments or isinstance(expression, exp.Binary): 478 return sql 479 480 sep = "\n" if self.pretty else " " 481 comments_sql = sep.join( 482 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 483 ) 484 485 if not comments_sql: 486 return sql 487 488 if isinstance(expression, self.WITH_SEPARATED_COMMENTS): 489 return ( 490 f"{self.sep()}{comments_sql}{sql}" 491 if sql[0].isspace() 492 else f"{comments_sql}{self.sep()}{sql}" 493 ) 494 495 return f"{sql} {comments_sql}"
497 def wrap(self, expression: exp.Expression | str) -> str: 498 this_sql = self.indent( 499 self.sql(expression) 500 if isinstance(expression, (exp.Select, exp.Union)) 501 else self.sql(expression, "this"), 502 level=1, 503 pad=0, 504 ) 505 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:
521 def indent( 522 self, 523 sql: str, 524 level: int = 0, 525 pad: t.Optional[int] = None, 526 skip_first: bool = False, 527 skip_last: bool = False, 528 ) -> str: 529 if not self.pretty: 530 return sql 531 532 pad = self.pad if pad is None else pad 533 lines = sql.split("\n") 534 535 return "\n".join( 536 line 537 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 538 else f"{' ' * (level * self._indent + pad)}{line}" 539 for i, line in enumerate(lines) 540 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
542 def sql( 543 self, 544 expression: t.Optional[str | exp.Expression], 545 key: t.Optional[str] = None, 546 comment: bool = True, 547 ) -> str: 548 if not expression: 549 return "" 550 551 if isinstance(expression, str): 552 return expression 553 554 if key: 555 value = expression.args.get(key) 556 if value: 557 return self.sql(value) 558 return "" 559 560 if self._cache is not None: 561 expression_id = hash(expression) 562 563 if expression_id in self._cache: 564 return self._cache[expression_id] 565 566 transform = self.TRANSFORMS.get(expression.__class__) 567 568 if callable(transform): 569 sql = transform(self, expression) 570 elif transform: 571 sql = transform 572 elif isinstance(expression, exp.Expression): 573 exp_handler_name = f"{expression.key}_sql" 574 575 if hasattr(self, exp_handler_name): 576 sql = getattr(self, exp_handler_name)(expression) 577 elif isinstance(expression, exp.Func): 578 sql = self.function_fallback_sql(expression) 579 elif isinstance(expression, exp.Property): 580 sql = self.property_sql(expression) 581 else: 582 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 583 else: 584 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 585 586 sql = self.maybe_comment(sql, expression) if self.comments and comment else sql 587 588 if self._cache is not None: 589 self._cache[expression_id] = sql 590 return sql
597 def cache_sql(self, expression: exp.Cache) -> str: 598 lazy = " LAZY" if expression.args.get("lazy") else "" 599 table = self.sql(expression, "this") 600 options = expression.args.get("options") 601 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 602 sql = self.sql(expression, "expression") 603 sql = f" AS{self.sep()}{sql}" if sql else "" 604 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 605 return self.prepend_ctes(expression, sql)
607 def characterset_sql(self, expression: exp.CharacterSet) -> str: 608 if isinstance(expression.parent, exp.Cast): 609 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 610 default = "DEFAULT " if expression.args.get("default") else "" 611 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
613 def column_sql(self, expression: exp.Column) -> str: 614 join_mark = " (+)" if expression.args.get("join_mark") else "" 615 616 if join_mark and not self.COLUMN_JOIN_MARKS_SUPPORTED: 617 join_mark = "" 618 self.unsupported("Outer join syntax using the (+) operator is not supported.") 619 620 column = ".".join( 621 self.sql(part) 622 for part in ( 623 expression.args.get("catalog"), 624 expression.args.get("db"), 625 expression.args.get("table"), 626 expression.args.get("this"), 627 ) 628 if part 629 ) 630 631 return f"{column}{join_mark}"
639 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 640 column = self.sql(expression, "this") 641 kind = self.sql(expression, "kind") 642 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 643 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 644 kind = f"{sep}{kind}" if kind else "" 645 constraints = f" {constraints}" if constraints else "" 646 position = self.sql(expression, "position") 647 position = f" {position}" if position else "" 648 649 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
656 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 657 this = self.sql(expression, "this") 658 if expression.args.get("not_null"): 659 persisted = " PERSISTED NOT NULL" 660 elif expression.args.get("persisted"): 661 persisted = " PERSISTED" 662 else: 663 persisted = "" 664 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
677 def generatedasidentitycolumnconstraint_sql( 678 self, expression: exp.GeneratedAsIdentityColumnConstraint 679 ) -> str: 680 this = "" 681 if expression.this is not None: 682 on_null = " ON NULL" if expression.args.get("on_null") else "" 683 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 684 685 start = expression.args.get("start") 686 start = f"START WITH {start}" if start else "" 687 increment = expression.args.get("increment") 688 increment = f" INCREMENT BY {increment}" if increment else "" 689 minvalue = expression.args.get("minvalue") 690 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 691 maxvalue = expression.args.get("maxvalue") 692 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 693 cycle = expression.args.get("cycle") 694 cycle_sql = "" 695 696 if cycle is not None: 697 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 698 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 699 700 sequence_opts = "" 701 if start or increment or cycle_sql: 702 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 703 sequence_opts = f" ({sequence_opts.strip()})" 704 705 expr = self.sql(expression, "expression") 706 expr = f"({expr})" if expr else "IDENTITY" 707 708 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:
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
719 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 720 this = self.sql(expression, "this") 721 this = f" {this}" if this else "" 722 index_type = expression.args.get("index_type") 723 index_type = f" USING {index_type}" if index_type else "" 724 return f"UNIQUE{this}{index_type}"
729 def create_sql(self, expression: exp.Create) -> str: 730 kind = self.sql(expression, "kind").upper() 731 properties = expression.args.get("properties") 732 properties_locs = self.locate_properties(properties) if properties else defaultdict() 733 734 this = self.createable_sql(expression, properties_locs) 735 736 properties_sql = "" 737 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 738 exp.Properties.Location.POST_WITH 739 ): 740 properties_sql = self.sql( 741 exp.Properties( 742 expressions=[ 743 *properties_locs[exp.Properties.Location.POST_SCHEMA], 744 *properties_locs[exp.Properties.Location.POST_WITH], 745 ] 746 ) 747 ) 748 749 begin = " BEGIN" if expression.args.get("begin") else "" 750 expression_sql = self.sql(expression, "expression") 751 if expression_sql: 752 expression_sql = f"{begin}{self.sep()}{expression_sql}" 753 754 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 755 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 756 postalias_props_sql = self.properties( 757 exp.Properties( 758 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 759 ), 760 wrapped=False, 761 ) 762 expression_sql = f" AS {postalias_props_sql}{expression_sql}" 763 else: 764 expression_sql = f" AS{expression_sql}" 765 766 postindex_props_sql = "" 767 if properties_locs.get(exp.Properties.Location.POST_INDEX): 768 postindex_props_sql = self.properties( 769 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 770 wrapped=False, 771 prefix=" ", 772 ) 773 774 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 775 indexes = f" {indexes}" if indexes else "" 776 index_sql = indexes + postindex_props_sql 777 778 replace = " OR REPLACE" if expression.args.get("replace") else "" 779 unique = " UNIQUE" if expression.args.get("unique") else "" 780 781 postcreate_props_sql = "" 782 if properties_locs.get(exp.Properties.Location.POST_CREATE): 783 postcreate_props_sql = self.properties( 784 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 785 sep=" ", 786 prefix=" ", 787 wrapped=False, 788 ) 789 790 modifiers = "".join((replace, unique, postcreate_props_sql)) 791 792 postexpression_props_sql = "" 793 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 794 postexpression_props_sql = self.properties( 795 exp.Properties( 796 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 797 ), 798 sep=" ", 799 prefix=" ", 800 wrapped=False, 801 ) 802 803 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 804 no_schema_binding = ( 805 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 806 ) 807 808 clone = self.sql(expression, "clone") 809 clone = f" {clone}" if clone else "" 810 811 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 812 return self.prepend_ctes(expression, expression_sql)
814 def clone_sql(self, expression: exp.Clone) -> str: 815 this = self.sql(expression, "this") 816 shallow = "SHALLOW " if expression.args.get("shallow") else "" 817 this = f"{shallow}CLONE {this}" 818 when = self.sql(expression, "when") 819 820 if when: 821 kind = self.sql(expression, "kind") 822 expr = self.sql(expression, "expression") 823 return f"{this} {when} ({kind} => {expr})" 824 825 return this
880 def datatype_sql(self, expression: exp.DataType) -> str: 881 type_value = expression.this 882 883 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 884 type_sql = self.sql(expression, "kind") 885 else: 886 type_sql = ( 887 self.TYPE_MAPPING.get(type_value, type_value.value) 888 if isinstance(type_value, exp.DataType.Type) 889 else type_value 890 ) 891 892 nested = "" 893 interior = self.expressions(expression, flat=True) 894 values = "" 895 896 if interior: 897 if expression.args.get("nested"): 898 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 899 if expression.args.get("values") is not None: 900 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 901 values = self.expressions(expression, key="values", flat=True) 902 values = f"{delimiters[0]}{values}{delimiters[1]}" 903 elif type_value == exp.DataType.Type.INTERVAL: 904 nested = f" {interior}" 905 else: 906 nested = f"({interior})" 907 908 type_sql = f"{type_sql}{nested}{values}" 909 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 910 exp.DataType.Type.TIMETZ, 911 exp.DataType.Type.TIMESTAMPTZ, 912 ): 913 type_sql = f"{type_sql} WITH TIME ZONE" 914 915 return type_sql
917 def directory_sql(self, expression: exp.Directory) -> str: 918 local = "LOCAL " if expression.args.get("local") else "" 919 row_format = self.sql(expression, "row_format") 920 row_format = f" {row_format}" if row_format else "" 921 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
923 def delete_sql(self, expression: exp.Delete) -> str: 924 this = self.sql(expression, "this") 925 this = f" FROM {this}" if this else "" 926 using = self.sql(expression, "using") 927 using = f" USING {using}" if using else "" 928 where = self.sql(expression, "where") 929 returning = self.sql(expression, "returning") 930 limit = self.sql(expression, "limit") 931 tables = self.expressions(expression, key="tables") 932 tables = f" {tables}" if tables else "" 933 if self.RETURNING_END: 934 expression_sql = f"{this}{using}{where}{returning}{limit}" 935 else: 936 expression_sql = f"{returning}{this}{using}{where}{limit}" 937 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
939 def drop_sql(self, expression: exp.Drop) -> str: 940 this = self.sql(expression, "this") 941 kind = expression.args["kind"] 942 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 943 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 944 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 945 cascade = " CASCADE" if expression.args.get("cascade") else "" 946 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 947 purge = " PURGE" if expression.args.get("purge") else "" 948 return ( 949 f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}{constraints}{purge}" 950 )
961 def fetch_sql(self, expression: exp.Fetch) -> str: 962 direction = expression.args.get("direction") 963 direction = f" {direction.upper()}" if direction else "" 964 count = expression.args.get("count") 965 count = f" {count}" if count else "" 966 if expression.args.get("percent"): 967 count = f"{count} PERCENT" 968 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 969 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}"
971 def filter_sql(self, expression: exp.Filter) -> str: 972 if self.AGGREGATE_FILTER_SUPPORTED: 973 this = self.sql(expression, "this") 974 where = self.sql(expression, "expression").strip() 975 return f"{this} FILTER({where})" 976 977 agg = expression.this.copy() 978 agg_arg = agg.this 979 cond = expression.expression.this 980 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 981 return self.sql(agg)
990 def index_sql(self, expression: exp.Index) -> str: 991 unique = "UNIQUE " if expression.args.get("unique") else "" 992 primary = "PRIMARY " if expression.args.get("primary") else "" 993 amp = "AMP " if expression.args.get("amp") else "" 994 name = self.sql(expression, "this") 995 name = f"{name} " if name else "" 996 table = self.sql(expression, "table") 997 table = f"{self.INDEX_ON} {table}" if table else "" 998 using = self.sql(expression, "using") 999 using = f" USING {using}" if using else "" 1000 index = "INDEX " if not table else "" 1001 columns = self.expressions(expression, key="columns", flat=True) 1002 columns = f"({columns})" if columns else "" 1003 partition_by = self.expressions(expression, key="partition_by", flat=True) 1004 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1005 where = self.sql(expression, "where") 1006 return f"{unique}{primary}{amp}{index}{name}{table}{using}{columns}{partition_by}{where}"
1008 def identifier_sql(self, expression: exp.Identifier) -> str: 1009 text = expression.name 1010 lower = text.lower() 1011 text = lower if self.normalize and not expression.quoted else text 1012 text = text.replace(self.IDENTIFIER_END, self._escaped_identifier_end) 1013 if ( 1014 expression.quoted 1015 or self.can_identify(text, self.identify) 1016 or lower in self.RESERVED_KEYWORDS 1017 or (not self.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1018 ): 1019 text = f"{self.IDENTIFIER_START}{text}{self.IDENTIFIER_END}" 1020 return text
1022 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1023 input_format = self.sql(expression, "input_format") 1024 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1025 output_format = self.sql(expression, "output_format") 1026 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1027 return self.sep().join((input_format, output_format))
1036 def properties_sql(self, expression: exp.Properties) -> str: 1037 root_properties = [] 1038 with_properties = [] 1039 1040 for p in expression.expressions: 1041 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1042 if p_loc == exp.Properties.Location.POST_WITH: 1043 with_properties.append(p.copy()) 1044 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1045 root_properties.append(p.copy()) 1046 1047 return self.root_properties( 1048 exp.Properties(expressions=root_properties) 1049 ) + 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:
1056 def properties( 1057 self, 1058 properties: exp.Properties, 1059 prefix: str = "", 1060 sep: str = ", ", 1061 suffix: str = "", 1062 wrapped: bool = True, 1063 ) -> str: 1064 if properties.expressions: 1065 expressions = self.expressions(properties, sep=sep, indent=False) 1066 if expressions: 1067 expressions = self.wrap(expressions) if wrapped else expressions 1068 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}" 1069 return ""
1074 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1075 properties_locs = defaultdict(list) 1076 for p in properties.expressions: 1077 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1078 if p_loc != exp.Properties.Location.UNSUPPORTED: 1079 properties_locs[p_loc].append(p.copy()) 1080 else: 1081 self.unsupported(f"Unsupported property {p.key}") 1082 1083 return properties_locs
1085 def property_sql(self, expression: exp.Property) -> str: 1086 property_cls = expression.__class__ 1087 if property_cls == exp.Property: 1088 return f"{expression.name}={self.sql(expression, 'value')}" 1089 1090 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1091 if not property_name: 1092 self.unsupported(f"Unsupported property {expression.key}") 1093 1094 return f"{property_name}={self.sql(expression, 'this')}"
1106 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1107 no = "NO " if expression.args.get("no") else "" 1108 local = expression.args.get("local") 1109 local = f"{local} " if local else "" 1110 dual = "DUAL " if expression.args.get("dual") else "" 1111 before = "BEFORE " if expression.args.get("before") else "" 1112 after = "AFTER " if expression.args.get("after") else "" 1113 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1129 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1130 if expression.args.get("no"): 1131 return "NO MERGEBLOCKRATIO" 1132 if expression.args.get("default"): 1133 return "DEFAULT MERGEBLOCKRATIO" 1134 1135 percent = " PERCENT" if expression.args.get("percent") else "" 1136 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1138 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1139 default = expression.args.get("default") 1140 minimum = expression.args.get("minimum") 1141 maximum = expression.args.get("maximum") 1142 if default or minimum or maximum: 1143 if default: 1144 prop = "DEFAULT" 1145 elif minimum: 1146 prop = "MINIMUM" 1147 else: 1148 prop = "MAXIMUM" 1149 return f"{prop} DATABLOCKSIZE" 1150 units = expression.args.get("units") 1151 units = f" {units}" if units else "" 1152 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1154 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1155 autotemp = expression.args.get("autotemp") 1156 always = expression.args.get("always") 1157 default = expression.args.get("default") 1158 manual = expression.args.get("manual") 1159 never = expression.args.get("never") 1160 1161 if autotemp is not None: 1162 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1163 elif always: 1164 prop = "ALWAYS" 1165 elif default: 1166 prop = "DEFAULT" 1167 elif manual: 1168 prop = "MANUAL" 1169 elif never: 1170 prop = "NEVER" 1171 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1173 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1174 no = expression.args.get("no") 1175 no = " NO" if no else "" 1176 concurrent = expression.args.get("concurrent") 1177 concurrent = " CONCURRENT" if concurrent else "" 1178 1179 for_ = "" 1180 if expression.args.get("for_all"): 1181 for_ = " FOR ALL" 1182 elif expression.args.get("for_insert"): 1183 for_ = " FOR INSERT" 1184 elif expression.args.get("for_none"): 1185 for_ = " FOR NONE" 1186 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"
1188 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1189 kind = expression.args.get("kind") 1190 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1191 for_or_in = expression.args.get("for_or_in") 1192 lock_type = expression.args.get("lock_type") 1193 override = " OVERRIDE" if expression.args.get("override") else "" 1194 return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}"
1196 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1197 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1198 statistics = expression.args.get("statistics") 1199 statistics_sql = "" 1200 if statistics is not None: 1201 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1202 return f"{data_sql}{statistics_sql}"
1204 def insert_sql(self, expression: exp.Insert) -> str: 1205 overwrite = expression.args.get("overwrite") 1206 1207 if isinstance(expression.this, exp.Directory): 1208 this = " OVERWRITE" if overwrite else " INTO" 1209 else: 1210 this = " OVERWRITE TABLE" if overwrite else " INTO" 1211 1212 alternative = expression.args.get("alternative") 1213 alternative = f" OR {alternative}" if alternative else "" 1214 ignore = " IGNORE" if expression.args.get("ignore") else "" 1215 1216 this = f"{this} {self.sql(expression, 'this')}" 1217 1218 exists = " IF EXISTS" if expression.args.get("exists") else "" 1219 partition_sql = ( 1220 f" {self.sql(expression, 'partition')}" if expression.args.get("partition") else "" 1221 ) 1222 where = self.sql(expression, "where") 1223 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1224 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1225 conflict = self.sql(expression, "conflict") 1226 by_name = " BY NAME" if expression.args.get("by_name") else "" 1227 returning = self.sql(expression, "returning") 1228 1229 if self.RETURNING_END: 1230 expression_sql = f"{expression_sql}{conflict}{returning}" 1231 else: 1232 expression_sql = f"{returning}{expression_sql}{conflict}" 1233 1234 sql = f"INSERT{alternative}{ignore}{this}{by_name}{exists}{partition_sql}{where}{expression_sql}" 1235 return self.prepend_ctes(expression, sql)
1262 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1263 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1264 constraint = self.sql(expression, "constraint") 1265 if constraint: 1266 constraint = f"ON CONSTRAINT {constraint}" 1267 key = self.expressions(expression, key="key", flat=True) 1268 do = "" if expression.args.get("duplicate") else " DO " 1269 nothing = "NOTHING" if expression.args.get("nothing") else "" 1270 expressions = self.expressions(expression, flat=True) 1271 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1272 if expressions: 1273 expressions = f"UPDATE {set_keyword}{expressions}" 1274 return f"{self.seg(conflict)} {constraint}{key}{do}{nothing}{expressions}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1279 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1280 fields = expression.args.get("fields") 1281 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1282 escaped = expression.args.get("escaped") 1283 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1284 items = expression.args.get("collection_items") 1285 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1286 keys = expression.args.get("map_keys") 1287 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1288 lines = expression.args.get("lines") 1289 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1290 null = expression.args.get("null") 1291 null = f" NULL DEFINED AS {null}" if null else "" 1292 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1303 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1304 table = ".".join( 1305 part 1306 for part in [ 1307 self.sql(expression, "catalog"), 1308 self.sql(expression, "db"), 1309 self.sql(expression, "this"), 1310 ] 1311 if part 1312 ) 1313 1314 version = self.sql(expression, "version") 1315 version = f" {version}" if version else "" 1316 alias = self.sql(expression, "alias") 1317 alias = f"{sep}{alias}" if alias else "" 1318 hints = self.expressions(expression, key="hints", sep=" ") 1319 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1320 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) 1321 pivots = f" {pivots}" if pivots else "" 1322 joins = self.expressions(expression, key="joins", sep="", skip_first=True) 1323 laterals = self.expressions(expression, key="laterals", sep="") 1324 1325 return f"{table}{version}{alias}{hints}{pivots}{joins}{laterals}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, seed_prefix: str = 'SEED', sep=' AS ') -> str:
1327 def tablesample_sql( 1328 self, expression: exp.TableSample, seed_prefix: str = "SEED", sep=" AS " 1329 ) -> str: 1330 if self.ALIAS_POST_TABLESAMPLE and expression.this.alias: 1331 table = expression.this.copy() 1332 table.set("alias", None) 1333 this = self.sql(table) 1334 alias = f"{sep}{self.sql(expression.this, 'alias')}" 1335 else: 1336 this = self.sql(expression, "this") 1337 alias = "" 1338 method = self.sql(expression, "method") 1339 method = f"{method.upper()} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1340 numerator = self.sql(expression, "bucket_numerator") 1341 denominator = self.sql(expression, "bucket_denominator") 1342 field = self.sql(expression, "bucket_field") 1343 field = f" ON {field}" if field else "" 1344 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1345 percent = self.sql(expression, "percent") 1346 percent = f"{percent} PERCENT" if percent else "" 1347 rows = self.sql(expression, "rows") 1348 rows = f"{rows} ROWS" if rows else "" 1349 size = self.sql(expression, "size") 1350 if size and self.TABLESAMPLE_SIZE_IS_PERCENT: 1351 size = f"{size} PERCENT" 1352 seed = self.sql(expression, "seed") 1353 seed = f" {seed_prefix} ({seed})" if seed else "" 1354 kind = expression.args.get("kind", "TABLESAMPLE") 1355 return f"{this} {kind} {method}({bucket}{percent}{rows}{size}){seed}{alias}"
1357 def pivot_sql(self, expression: exp.Pivot) -> str: 1358 expressions = self.expressions(expression, flat=True) 1359 1360 if expression.this: 1361 this = self.sql(expression, "this") 1362 on = f"{self.seg('ON')} {expressions}" 1363 using = self.expressions(expression, key="using", flat=True) 1364 using = f"{self.seg('USING')} {using}" if using else "" 1365 group = self.sql(expression, "group") 1366 return f"PIVOT {this}{on}{using}{group}" 1367 1368 alias = self.sql(expression, "alias") 1369 alias = f" AS {alias}" if alias else "" 1370 unpivot = expression.args.get("unpivot") 1371 direction = "UNPIVOT" if unpivot else "PIVOT" 1372 field = self.sql(expression, "field") 1373 include_nulls = expression.args.get("include_nulls") 1374 if include_nulls is not None: 1375 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1376 else: 1377 nulls = "" 1378 return f"{direction}{nulls}({expressions} FOR {field}){alias}"
1389 def update_sql(self, expression: exp.Update) -> str: 1390 this = self.sql(expression, "this") 1391 set_sql = self.expressions(expression, flat=True) 1392 from_sql = self.sql(expression, "from") 1393 where_sql = self.sql(expression, "where") 1394 returning = self.sql(expression, "returning") 1395 order = self.sql(expression, "order") 1396 limit = self.sql(expression, "limit") 1397 if self.RETURNING_END: 1398 expression_sql = f"{from_sql}{where_sql}{returning}" 1399 else: 1400 expression_sql = f"{returning}{from_sql}{where_sql}" 1401 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1402 return self.prepend_ctes(expression, sql)
1404 def values_sql(self, expression: exp.Values) -> str: 1405 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 1406 if self.VALUES_AS_TABLE or not expression.find_ancestor(exp.From, exp.Join): 1407 args = self.expressions(expression) 1408 alias = self.sql(expression, "alias") 1409 values = f"VALUES{self.seg('')}{args}" 1410 values = ( 1411 f"({values})" 1412 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1413 else values 1414 ) 1415 return f"{values} AS {alias}" if alias else values 1416 1417 # Converts `VALUES...` expression into a series of select unions. 1418 expression = expression.copy() 1419 column_names = expression.alias and expression.args["alias"].columns 1420 1421 selects = [] 1422 1423 for i, tup in enumerate(expression.expressions): 1424 row = tup.expressions 1425 1426 if i == 0 and column_names: 1427 row = [ 1428 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 1429 ] 1430 1431 selects.append(exp.Select(expressions=row)) 1432 1433 if self.pretty: 1434 # This may result in poor performance for large-cardinality `VALUES` tables, due to 1435 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 1436 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 1437 subquery_expression: exp.Select | exp.Union = selects[0] 1438 if len(selects) > 1: 1439 for select in selects[1:]: 1440 subquery_expression = exp.union( 1441 subquery_expression, select, distinct=False, copy=False 1442 ) 1443 1444 return self.subquery_sql(subquery_expression.subquery(expression.alias, copy=False)) 1445 1446 alias = f" AS {expression.alias}" if expression.alias else "" 1447 unions = " UNION ALL ".join(self.sql(select) for select in selects) 1448 return f"({unions}){alias}"
1461 def group_sql(self, expression: exp.Group) -> str: 1462 group_by = self.op_expressions("GROUP BY", expression) 1463 1464 if expression.args.get("all"): 1465 return f"{group_by} ALL" 1466 1467 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False) 1468 grouping_sets = ( 1469 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else "" 1470 ) 1471 1472 cube = expression.args.get("cube", []) 1473 if seq_get(cube, 0) is True: 1474 return f"{group_by}{self.seg('WITH CUBE')}" 1475 else: 1476 cube_sql = self.expressions(expression, key="cube", indent=False) 1477 cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else "" 1478 1479 rollup = expression.args.get("rollup", []) 1480 if seq_get(rollup, 0) is True: 1481 return f"{group_by}{self.seg('WITH ROLLUP')}" 1482 else: 1483 rollup_sql = self.expressions(expression, key="rollup", indent=False) 1484 rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else "" 1485 1486 groupings = csv( 1487 grouping_sets, 1488 cube_sql, 1489 rollup_sql, 1490 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 1491 sep=self.GROUPINGS_SEP, 1492 ) 1493 1494 if expression.args.get("expressions") and groupings: 1495 group_by = f"{group_by}{self.GROUPINGS_SEP}" 1496 1497 return f"{group_by}{groupings}"
1513 def join_sql(self, expression: exp.Join) -> str: 1514 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 1515 side = None 1516 else: 1517 side = expression.side 1518 1519 op_sql = " ".join( 1520 op 1521 for op in ( 1522 expression.method, 1523 "GLOBAL" if expression.args.get("global") else None, 1524 side, 1525 expression.kind, 1526 expression.hint if self.JOIN_HINTS else None, 1527 ) 1528 if op 1529 ) 1530 on_sql = self.sql(expression, "on") 1531 using = expression.args.get("using") 1532 1533 if not on_sql and using: 1534 on_sql = csv(*(self.sql(column) for column in using)) 1535 1536 this_sql = self.sql(expression, "this") 1537 1538 if on_sql: 1539 on_sql = self.indent(on_sql, skip_first=True) 1540 space = self.seg(" " * self.pad) if self.pretty else " " 1541 if using: 1542 on_sql = f"{space}USING ({on_sql})" 1543 else: 1544 on_sql = f"{space}ON {on_sql}" 1545 elif not op_sql: 1546 return f", {this_sql}" 1547 1548 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 1549 return f"{self.seg(op_sql)} {this_sql}{on_sql}"
1556 def lateral_sql(self, expression: exp.Lateral) -> str: 1557 this = self.sql(expression, "this") 1558 1559 if isinstance(expression.this, exp.Subquery): 1560 return f"LATERAL {this}" 1561 1562 if expression.args.get("view"): 1563 alias = expression.args["alias"] 1564 columns = self.expressions(alias, key="columns", flat=True) 1565 table = f" {alias.name}" if alias.name else "" 1566 columns = f" AS {columns}" if columns else "" 1567 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 1568 return f"{op_sql}{self.sep()}{this}{table}{columns}" 1569 1570 alias = self.sql(expression, "alias") 1571 alias = f" AS {alias}" if alias else "" 1572 return f"LATERAL {this}{alias}"
1574 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 1575 this = self.sql(expression, "this") 1576 args = ", ".join( 1577 sql 1578 for sql in ( 1579 self.sql(expression, "offset"), 1580 self.sql(expression, "expression"), 1581 ) 1582 if sql 1583 ) 1584 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args}"
1590 def setitem_sql(self, expression: exp.SetItem) -> str: 1591 kind = self.sql(expression, "kind") 1592 kind = f"{kind} " if kind else "" 1593 this = self.sql(expression, "this") 1594 expressions = self.expressions(expression) 1595 collate = self.sql(expression, "collate") 1596 collate = f" COLLATE {collate}" if collate else "" 1597 global_ = "GLOBAL " if expression.args.get("global") else "" 1598 return f"{global_}{kind}{this}{expressions}{collate}"
1600 def set_sql(self, expression: exp.Set) -> str: 1601 expressions = ( 1602 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 1603 ) 1604 tag = " TAG" if expression.args.get("tag") else "" 1605 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
1610 def lock_sql(self, expression: exp.Lock) -> str: 1611 if not self.LOCKING_READS_SUPPORTED: 1612 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 1613 return "" 1614 1615 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 1616 expressions = self.expressions(expression, flat=True) 1617 expressions = f" OF {expressions}" if expressions else "" 1618 wait = expression.args.get("wait") 1619 1620 if wait is not None: 1621 if isinstance(wait, exp.Literal): 1622 wait = f" WAIT {self.sql(wait)}" 1623 else: 1624 wait = " NOWAIT" if wait else " SKIP LOCKED" 1625 1626 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str) -> str:
1634 def escape_str(self, text: str) -> str: 1635 text = text.replace(self.QUOTE_END, self._escaped_quote_end) 1636 if self.UNESCAPED_SEQUENCE_TABLE: 1637 text = text.translate(self.UNESCAPED_SEQUENCE_TABLE) 1638 elif self.pretty: 1639 text = text.replace("\n", self.SENTINEL_LINE_BREAK) 1640 return text
1642 def loaddata_sql(self, expression: exp.LoadData) -> str: 1643 local = " LOCAL" if expression.args.get("local") else "" 1644 inpath = f" INPATH {self.sql(expression, 'inpath')}" 1645 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 1646 this = f" INTO TABLE {self.sql(expression, 'this')}" 1647 partition = self.sql(expression, "partition") 1648 partition = f" {partition}" if partition else "" 1649 input_format = self.sql(expression, "input_format") 1650 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 1651 serde = self.sql(expression, "serde") 1652 serde = f" SERDE {serde}" if serde else "" 1653 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
1675 def ordered_sql(self, expression: exp.Ordered) -> str: 1676 desc = expression.args.get("desc") 1677 asc = not desc 1678 1679 nulls_first = expression.args.get("nulls_first") 1680 nulls_last = not nulls_first 1681 nulls_are_large = self.NULL_ORDERING == "nulls_are_large" 1682 nulls_are_small = self.NULL_ORDERING == "nulls_are_small" 1683 nulls_are_last = self.NULL_ORDERING == "nulls_are_last" 1684 1685 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 1686 nulls_sort_change = "" 1687 if nulls_first and ( 1688 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 1689 ): 1690 nulls_sort_change = " NULLS FIRST" 1691 elif ( 1692 nulls_last 1693 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 1694 and not nulls_are_last 1695 ): 1696 nulls_sort_change = " NULLS LAST" 1697 1698 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 1699 self.unsupported( 1700 "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect" 1701 ) 1702 nulls_sort_change = "" 1703 1704 return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}"
1706 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 1707 partition = self.partition_by_sql(expression) 1708 order = self.sql(expression, "order") 1709 measures = self.expressions(expression, key="measures") 1710 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 1711 rows = self.sql(expression, "rows") 1712 rows = self.seg(rows) if rows else "" 1713 after = self.sql(expression, "after") 1714 after = self.seg(after) if after else "" 1715 pattern = self.sql(expression, "pattern") 1716 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 1717 definition_sqls = [ 1718 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 1719 for definition in expression.args.get("define", []) 1720 ] 1721 definitions = self.expressions(sqls=definition_sqls) 1722 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 1723 body = "".join( 1724 ( 1725 partition, 1726 order, 1727 measures, 1728 rows, 1729 after, 1730 pattern, 1731 define, 1732 ) 1733 ) 1734 alias = self.sql(expression, "alias") 1735 alias = f" {alias}" if alias else "" 1736 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
1738 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 1739 limit: t.Optional[exp.Fetch | exp.Limit] = expression.args.get("limit") 1740 1741 # If the limit is generated as TOP, we need to ensure it's not generated twice 1742 with_offset_limit_modifiers = not isinstance(limit, exp.Limit) or not self.LIMIT_IS_TOP 1743 1744 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 1745 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 1746 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 1747 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 1748 1749 fetch = isinstance(limit, exp.Fetch) 1750 1751 offset_limit_modifiers = ( 1752 self.offset_limit_modifiers(expression, fetch, limit) 1753 if with_offset_limit_modifiers 1754 else [] 1755 ) 1756 1757 return csv( 1758 *sqls, 1759 *[self.sql(join) for join in expression.args.get("joins") or []], 1760 self.sql(expression, "connect"), 1761 self.sql(expression, "match"), 1762 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 1763 self.sql(expression, "where"), 1764 self.sql(expression, "group"), 1765 self.sql(expression, "having"), 1766 *self.after_having_modifiers(expression), 1767 self.sql(expression, "order"), 1768 *offset_limit_modifiers, 1769 *self.after_limit_modifiers(expression), 1770 sep="", 1771 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
1773 def offset_limit_modifiers( 1774 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 1775 ) -> t.List[str]: 1776 return [ 1777 self.sql(expression, "offset") if fetch else self.sql(limit), 1778 self.sql(limit) if fetch else self.sql(expression, "offset"), 1779 ]
1781 def after_having_modifiers(self, expression: exp.Expression) -> t.List[str]: 1782 return [ 1783 self.sql(expression, "qualify"), 1784 self.seg("WINDOW ") + self.expressions(expression, key="windows", flat=True) 1785 if expression.args.get("windows") 1786 else "", 1787 self.sql(expression, "distribute"), 1788 self.sql(expression, "sort"), 1789 self.sql(expression, "cluster"), 1790 ]
1797 def select_sql(self, expression: exp.Select) -> str: 1798 hint = self.sql(expression, "hint") 1799 distinct = self.sql(expression, "distinct") 1800 distinct = f" {distinct}" if distinct else "" 1801 kind = self.sql(expression, "kind").upper() 1802 limit = expression.args.get("limit") 1803 top = ( 1804 self.limit_sql(limit, top=True) 1805 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP 1806 else "" 1807 ) 1808 1809 expressions = self.expressions(expression) 1810 1811 if kind: 1812 if kind in self.SELECT_KINDS: 1813 kind = f" AS {kind}" 1814 else: 1815 if kind == "STRUCT": 1816 expressions = self.expressions( 1817 sqls=[ 1818 self.sql( 1819 exp.Struct( 1820 expressions=[ 1821 exp.column(e.output_name).eq( 1822 e.this if isinstance(e, exp.Alias) else e 1823 ) 1824 for e in expression.expressions 1825 ] 1826 ) 1827 ) 1828 ] 1829 ) 1830 kind = "" 1831 1832 expressions = f"{self.sep()}{expressions}" if expressions else expressions 1833 sql = self.query_modifiers( 1834 expression, 1835 f"SELECT{top}{hint}{distinct}{kind}{expressions}", 1836 self.sql(expression, "into", comment=False), 1837 self.sql(expression, "from", comment=False), 1838 ) 1839 return self.prepend_ctes(expression, sql)
1850 def star_sql(self, expression: exp.Star) -> str: 1851 except_ = self.expressions(expression, key="except", flat=True) 1852 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else "" 1853 replace = self.expressions(expression, key="replace", flat=True) 1854 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else "" 1855 return f"*{except_}{replace}"
1871 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 1872 alias = self.sql(expression, "alias") 1873 alias = f"{sep}{alias}" if alias else "" 1874 1875 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True) 1876 pivots = f" {pivots}" if pivots else "" 1877 1878 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 1879 return self.prepend_ctes(expression, sql)
1897 def unnest_sql(self, expression: exp.Unnest) -> str: 1898 args = self.expressions(expression, flat=True) 1899 1900 alias = expression.args.get("alias") 1901 offset = expression.args.get("offset") 1902 1903 if self.UNNEST_WITH_ORDINALITY: 1904 if alias and isinstance(offset, exp.Expression): 1905 alias = alias.copy() 1906 alias.append("columns", offset.copy()) 1907 1908 if alias and self.UNNEST_COLUMN_ONLY: 1909 columns = alias.columns 1910 alias = self.sql(columns[0]) if columns else "" 1911 else: 1912 alias = self.sql(alias) 1913 1914 alias = f" AS {alias}" if alias else alias 1915 if self.UNNEST_WITH_ORDINALITY: 1916 suffix = f" WITH ORDINALITY{alias}" if offset else alias 1917 else: 1918 if isinstance(offset, exp.Expression): 1919 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 1920 elif offset: 1921 suffix = f"{alias} WITH OFFSET" 1922 else: 1923 suffix = alias 1924 1925 return f"UNNEST({args}){suffix}"
1931 def window_sql(self, expression: exp.Window) -> str: 1932 this = self.sql(expression, "this") 1933 partition = self.partition_by_sql(expression) 1934 order = expression.args.get("order") 1935 order = self.order_sql(order, flat=True) if order else "" 1936 spec = self.sql(expression, "spec") 1937 alias = self.sql(expression, "alias") 1938 over = self.sql(expression, "over") or "OVER" 1939 1940 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 1941 1942 first = expression.args.get("first") 1943 if first is None: 1944 first = "" 1945 else: 1946 first = "FIRST" if first else "LAST" 1947 1948 if not partition and not order and not spec and alias: 1949 return f"{this} {alias}" 1950 1951 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 1952 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
1958 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 1959 kind = self.sql(expression, "kind") 1960 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 1961 end = ( 1962 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 1963 or "CURRENT ROW" 1964 ) 1965 return f"{kind} BETWEEN {start} AND {end}"
1999 def case_sql(self, expression: exp.Case) -> str: 2000 this = self.sql(expression, "this") 2001 statements = [f"CASE {this}" if this else "CASE"] 2002 2003 for e in expression.args["ifs"]: 2004 statements.append(f"WHEN {self.sql(e, 'this')}") 2005 statements.append(f"THEN {self.sql(e, 'true')}") 2006 2007 default = self.sql(expression, "default") 2008 2009 if default: 2010 statements.append(f"ELSE {default}") 2011 2012 statements.append("END") 2013 2014 if self.pretty and self.text_width(statements) > self.max_text_width: 2015 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2016 2017 return " ".join(statements)
2034 def trim_sql(self, expression: exp.Trim) -> str: 2035 trim_type = self.sql(expression, "position") 2036 2037 if trim_type == "LEADING": 2038 return self.func("LTRIM", expression.this) 2039 elif trim_type == "TRAILING": 2040 return self.func("RTRIM", expression.this) 2041 else: 2042 return self.func("TRIM", expression.this, expression.expression)
2054 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2055 expressions = self.expressions(expression, flat=True) 2056 reference = self.sql(expression, "reference") 2057 reference = f" {reference}" if reference else "" 2058 delete = self.sql(expression, "delete") 2059 delete = f" ON DELETE {delete}" if delete else "" 2060 update = self.sql(expression, "update") 2061 update = f" ON UPDATE {update}" if update else "" 2062 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
2064 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2065 expressions = self.expressions(expression, flat=True) 2066 options = self.expressions(expression, key="options", flat=True, sep=" ") 2067 options = f" {options}" if options else "" 2068 return f"PRIMARY KEY ({expressions}){options}"
2085 def jsonobject_sql(self, expression: exp.JSONObject) -> str: 2086 null_handling = expression.args.get("null_handling") 2087 null_handling = f" {null_handling}" if null_handling else "" 2088 unique_keys = expression.args.get("unique_keys") 2089 if unique_keys is not None: 2090 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2091 else: 2092 unique_keys = "" 2093 return_type = self.sql(expression, "return_type") 2094 return_type = f" RETURNING {return_type}" if return_type else "" 2095 encoding = self.sql(expression, "encoding") 2096 encoding = f" ENCODING {encoding}" if encoding else "" 2097 return self.func( 2098 "JSON_OBJECT", 2099 *expression.expressions, 2100 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2101 )
2103 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2104 null_handling = expression.args.get("null_handling") 2105 null_handling = f" {null_handling}" if null_handling else "" 2106 return_type = self.sql(expression, "return_type") 2107 return_type = f" RETURNING {return_type}" if return_type else "" 2108 strict = " STRICT" if expression.args.get("strict") else "" 2109 return self.func( 2110 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2111 )
2113 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2114 this = self.sql(expression, "this") 2115 order = self.sql(expression, "order") 2116 null_handling = expression.args.get("null_handling") 2117 null_handling = f" {null_handling}" if null_handling else "" 2118 return_type = self.sql(expression, "return_type") 2119 return_type = f" RETURNING {return_type}" if return_type else "" 2120 strict = " STRICT" if expression.args.get("strict") else "" 2121 return self.func( 2122 "JSON_ARRAYAGG", 2123 this, 2124 suffix=f"{order}{null_handling}{return_type}{strict})", 2125 )
2127 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2128 this = self.sql(expression, "this") 2129 kind = self.sql(expression, "kind") 2130 kind = f" {kind}" if kind else "" 2131 path = self.sql(expression, "path") 2132 path = f" PATH {path}" if path else "" 2133 return f"{this}{kind}{path}"
2135 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2136 this = self.sql(expression, "this") 2137 path = self.sql(expression, "path") 2138 path = f", {path}" if path else "" 2139 error_handling = expression.args.get("error_handling") 2140 error_handling = f" {error_handling}" if error_handling else "" 2141 empty_handling = expression.args.get("empty_handling") 2142 empty_handling = f" {empty_handling}" if empty_handling else "" 2143 columns = f" COLUMNS ({self.expressions(expression, skip_first=True)})" 2144 return self.func( 2145 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling}{columns})" 2146 )
2148 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2149 this = self.sql(expression, "this") 2150 kind = self.sql(expression, "kind") 2151 path = self.sql(expression, "path") 2152 path = f" {path}" if path else "" 2153 as_json = " AS JSON" if expression.args.get("as_json") else "" 2154 return f"{this} {kind}{path}{as_json}"
2156 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2157 this = self.sql(expression, "this") 2158 path = self.sql(expression, "path") 2159 path = f", {path}" if path else "" 2160 expressions = self.expressions(expression) 2161 with_ = ( 2162 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2163 if expressions 2164 else "" 2165 ) 2166 return f"OPENJSON({this}{path}){with_}"
2168 def in_sql(self, expression: exp.In) -> str: 2169 query = expression.args.get("query") 2170 unnest = expression.args.get("unnest") 2171 field = expression.args.get("field") 2172 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2173 2174 if query: 2175 in_sql = self.wrap(query) 2176 elif unnest: 2177 in_sql = self.in_unnest_op(unnest) 2178 elif field: 2179 in_sql = self.sql(field) 2180 else: 2181 in_sql = f"({self.expressions(expression, flat=True)})" 2182 2183 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
2188 def interval_sql(self, expression: exp.Interval) -> str: 2189 unit = self.sql(expression, "unit") 2190 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2191 unit = self.TIME_PART_SINGULARS.get(unit.lower(), unit) 2192 unit = f" {unit}" if unit else "" 2193 2194 if self.SINGLE_STRING_INTERVAL: 2195 this = expression.this.name if expression.this else "" 2196 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2197 2198 this = self.sql(expression, "this") 2199 if this: 2200 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2201 this = f" {this}" if unwrapped else f" ({this})" 2202 2203 return f"INTERVAL{this}{unit}"
2208 def reference_sql(self, expression: exp.Reference) -> str: 2209 this = self.sql(expression, "this") 2210 expressions = self.expressions(expression, flat=True) 2211 expressions = f"({expressions})" if expressions else "" 2212 options = self.expressions(expression, key="options", flat=True, sep=" ") 2213 options = f" {options}" if options else "" 2214 return f"REFERENCES {this}{expressions}{options}"
2219 def paren_sql(self, expression: exp.Paren) -> str: 2220 if isinstance(expression.unnest(), exp.Select): 2221 sql = self.wrap(expression) 2222 else: 2223 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2224 sql = f"({sql}{self.seg(')', sep='')}" 2225 2226 return self.prepend_ctes(expression, sql)
2259 def connector_sql(self, expression: exp.Connector, op: str) -> str: 2260 if not self.pretty: 2261 return self.binary(expression, op) 2262 2263 sqls = tuple( 2264 self.maybe_comment(self.sql(e), e, e.parent.comments or []) if i != 1 else self.sql(e) 2265 for i, e in enumerate(expression.flatten(unnest=False)) 2266 ) 2267 2268 sep = "\n" if self.text_width(sqls) > self.max_text_width else " " 2269 return f"{sep}{op} ".join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
2289 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 2290 format_sql = self.sql(expression, "format") 2291 format_sql = f" FORMAT {format_sql}" if format_sql else "" 2292 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')}{format_sql})"
2304 def comment_sql(self, expression: exp.Comment) -> str: 2305 this = self.sql(expression, "this") 2306 kind = expression.args["kind"] 2307 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 2308 expression_sql = self.sql(expression, "expression") 2309 return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}"
2311 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 2312 this = self.sql(expression, "this") 2313 delete = " DELETE" if expression.args.get("delete") else "" 2314 recompress = self.sql(expression, "recompress") 2315 recompress = f" RECOMPRESS {recompress}" if recompress else "" 2316 to_disk = self.sql(expression, "to_disk") 2317 to_disk = f" TO DISK {to_disk}" if to_disk else "" 2318 to_volume = self.sql(expression, "to_volume") 2319 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 2320 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
2322 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 2323 where = self.sql(expression, "where") 2324 group = self.sql(expression, "group") 2325 aggregates = self.expressions(expression, key="aggregates") 2326 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 2327 2328 if not (where or group or aggregates) and len(expression.expressions) == 1: 2329 return f"TTL {self.expressions(expression, flat=True)}" 2330 2331 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
2348 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 2349 this = self.sql(expression, "this") 2350 2351 dtype = self.sql(expression, "dtype") 2352 if dtype: 2353 collate = self.sql(expression, "collate") 2354 collate = f" COLLATE {collate}" if collate else "" 2355 using = self.sql(expression, "using") 2356 using = f" USING {using}" if using else "" 2357 return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}" 2358 2359 default = self.sql(expression, "default") 2360 if default: 2361 return f"ALTER COLUMN {this} SET DEFAULT {default}" 2362 2363 if not expression.args.get("drop"): 2364 self.unsupported("Unsupported ALTER COLUMN syntax") 2365 2366 return f"ALTER COLUMN {this} DROP DEFAULT"
2368 def renametable_sql(self, expression: exp.RenameTable) -> str: 2369 if not self.RENAME_TABLE_WITH_DB: 2370 # Remove db from tables 2371 expression = expression.transform( 2372 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 2373 ) 2374 this = self.sql(expression, "this") 2375 return f"RENAME TO {this}"
2377 def altertable_sql(self, expression: exp.AlterTable) -> str: 2378 actions = expression.args["actions"] 2379 2380 if isinstance(actions[0], exp.ColumnDef): 2381 if self.ALTER_TABLE_ADD_COLUMN_KEYWORD: 2382 actions = self.expressions( 2383 expression, 2384 key="actions", 2385 prefix="ADD COLUMN ", 2386 ) 2387 else: 2388 actions = f"ADD {self.expressions(expression, key='actions')}" 2389 elif isinstance(actions[0], exp.Schema): 2390 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 2391 elif isinstance(actions[0], exp.Delete): 2392 actions = self.expressions(expression, key="actions", flat=True) 2393 else: 2394 actions = self.expressions(expression, key="actions") 2395 2396 exists = " IF EXISTS" if expression.args.get("exists") else "" 2397 only = " ONLY" if expression.args.get("only") else "" 2398 return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')} {actions}"
2405 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 2406 this = self.sql(expression, "this") 2407 expression_ = self.sql(expression, "expression") 2408 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD" 2409 2410 enforced = expression.args.get("enforced") 2411 if enforced is not None: 2412 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}" 2413 2414 return f"{add_constraint} {expression_}"
2539 def function_fallback_sql(self, expression: exp.Func) -> str: 2540 args = [] 2541 2542 for key in expression.arg_types: 2543 arg_value = expression.args.get(key) 2544 2545 if isinstance(arg_value, list): 2546 for value in arg_value: 2547 args.append(value) 2548 elif arg_value is not None: 2549 args.append(arg_value) 2550 2551 if self.normalize_functions: 2552 name = expression.sql_name() 2553 else: 2554 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 2555 2556 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')') -> str:
2567 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 2568 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None) 2569 if self.pretty and self.text_width(arg_sqls) > self.max_text_width: 2570 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 2571 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, skip_first: bool = False, sep: str = ', ', prefix: str = '') -> str:
2581 def expressions( 2582 self, 2583 expression: t.Optional[exp.Expression] = None, 2584 key: t.Optional[str] = None, 2585 sqls: t.Optional[t.List[str]] = None, 2586 flat: bool = False, 2587 indent: bool = True, 2588 skip_first: bool = False, 2589 sep: str = ", ", 2590 prefix: str = "", 2591 ) -> str: 2592 expressions = expression.args.get(key or "expressions") if expression else sqls 2593 2594 if not expressions: 2595 return "" 2596 2597 if flat: 2598 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 2599 2600 num_sqls = len(expressions) 2601 2602 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly 2603 pad = " " * self.pad 2604 stripped_sep = sep.strip() 2605 2606 result_sqls = [] 2607 for i, e in enumerate(expressions): 2608 sql = self.sql(e, comment=False) 2609 if not sql: 2610 continue 2611 2612 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 2613 2614 if self.pretty: 2615 if self.leading_comma: 2616 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}") 2617 else: 2618 result_sqls.append( 2619 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}" 2620 ) 2621 else: 2622 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 2623 2624 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls) 2625 return self.indent(result_sql, skip_first=skip_first) if indent else result_sql
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
2627 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 2628 flat = flat or isinstance(expression.parent, exp.Properties) 2629 expressions_sql = self.expressions(expression, flat=flat) 2630 if flat: 2631 return f"{op} {expressions_sql}" 2632 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
2634 def naked_property(self, expression: exp.Property) -> str: 2635 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 2636 if not property_name: 2637 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 2638 return f"{property_name} {self.sql(expression, 'this')}"
2653 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 2654 this = self.sql(expression, "this") 2655 expressions = self.no_identify(self.expressions, expression) 2656 expressions = ( 2657 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 2658 ) 2659 return f"{this}{expressions}"
2669 def when_sql(self, expression: exp.When) -> str: 2670 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 2671 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 2672 condition = self.sql(expression, "condition") 2673 condition = f" AND {condition}" if condition else "" 2674 2675 then_expression = expression.args.get("then") 2676 if isinstance(then_expression, exp.Insert): 2677 then = f"INSERT {self.sql(then_expression, 'this')}" 2678 if "expression" in then_expression.args: 2679 then += f" VALUES {self.sql(then_expression, 'expression')}" 2680 elif isinstance(then_expression, exp.Update): 2681 if isinstance(then_expression.args.get("expressions"), exp.Star): 2682 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 2683 else: 2684 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 2685 else: 2686 then = self.sql(then_expression) 2687 return f"WHEN {matched}{source}{condition} THEN {then}"
2689 def merge_sql(self, expression: exp.Merge) -> str: 2690 table = expression.this 2691 table_alias = "" 2692 2693 hints = table.args.get("hints") 2694 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 2695 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 2696 table = table.copy() 2697 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 2698 2699 this = self.sql(table) 2700 using = f"USING {self.sql(expression, 'using')}" 2701 on = f"ON {self.sql(expression, 'on')}" 2702 expressions = self.expressions(expression, sep=" ") 2703 2704 return f"MERGE INTO {this}{table_alias} {using} {on} {expressions}"
2712 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 2713 this = self.sql(expression, "this") 2714 kind = self.sql(expression, "kind") 2715 settings_sql = self.expressions(expression, key="settings", sep=" ") 2716 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 2717 return f"{this}({kind}{args})"
2731 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 2732 expressions = self.expressions(expression, key="expressions", flat=True) 2733 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 2734 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 2735 buckets = self.sql(expression, "buckets") 2736 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
2738 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 2739 this = self.sql(expression, "this") 2740 having = self.sql(expression, "having") 2741 2742 if having: 2743 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 2744 2745 return self.func("ANY_VALUE", this)
2747 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 2748 transform = self.func("TRANSFORM", *expression.expressions) 2749 row_format_before = self.sql(expression, "row_format_before") 2750 row_format_before = f" {row_format_before}" if row_format_before else "" 2751 record_writer = self.sql(expression, "record_writer") 2752 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 2753 using = f" USING {self.sql(expression, 'command_script')}" 2754 schema = self.sql(expression, "schema") 2755 schema = f" AS {schema}" if schema else "" 2756 row_format_after = self.sql(expression, "row_format_after") 2757 row_format_after = f" {row_format_after}" if row_format_after else "" 2758 record_reader = self.sql(expression, "record_reader") 2759 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 2760 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
2762 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 2763 key_block_size = self.sql(expression, "key_block_size") 2764 if key_block_size: 2765 return f"KEY_BLOCK_SIZE = {key_block_size}" 2766 2767 using = self.sql(expression, "using") 2768 if using: 2769 return f"USING {using}" 2770 2771 parser = self.sql(expression, "parser") 2772 if parser: 2773 return f"WITH PARSER {parser}" 2774 2775 comment = self.sql(expression, "comment") 2776 if comment: 2777 return f"COMMENT {comment}" 2778 2779 visible = expression.args.get("visible") 2780 if visible is not None: 2781 return "VISIBLE" if visible else "INVISIBLE" 2782 2783 engine_attr = self.sql(expression, "engine_attr") 2784 if engine_attr: 2785 return f"ENGINE_ATTRIBUTE = {engine_attr}" 2786 2787 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 2788 if secondary_engine_attr: 2789 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 2790 2791 self.unsupported("Unsupported index constraint option.") 2792 return ""
2794 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 2795 kind = self.sql(expression, "kind") 2796 kind = f"{kind} INDEX" if kind else "INDEX" 2797 this = self.sql(expression, "this") 2798 this = f" {this}" if this else "" 2799 index_type = self.sql(expression, "index_type") 2800 index_type = f" USING {index_type}" if index_type else "" 2801 schema = self.sql(expression, "schema") 2802 schema = f" {schema}" if schema else "" 2803 options = self.expressions(expression, key="options", sep=" ") 2804 options = f" {options}" if options else "" 2805 return f"{kind}{this}{index_type}{schema}{options}"
2807 def nvl2_sql(self, expression: exp.Nvl2) -> str: 2808 if self.NVL2_SUPPORTED: 2809 return self.function_fallback_sql(expression) 2810 2811 case = exp.Case().when( 2812 expression.this.is_(exp.null()).not_(copy=False), 2813 expression.args["true"].copy(), 2814 copy=False, 2815 ) 2816 else_cond = expression.args.get("false") 2817 if else_cond: 2818 case.else_(else_cond.copy(), copy=False) 2819 2820 return self.sql(case)
2822 def comprehension_sql(self, expression: exp.Comprehension) -> str: 2823 this = self.sql(expression, "this") 2824 expr = self.sql(expression, "expression") 2825 iterator = self.sql(expression, "iterator") 2826 condition = self.sql(expression, "condition") 2827 condition = f" IF {condition}" if condition else "" 2828 return f"{this} FOR {expr} IN {iterator}{condition}"
def
cached_generator( cache: Optional[Dict[int, str]] = None) -> Callable[[sqlglot.expressions.Expression], str]:
2834def cached_generator( 2835 cache: t.Optional[t.Dict[int, str]] = None 2836) -> t.Callable[[exp.Expression], str]: 2837 """Returns a cached generator.""" 2838 cache = {} if cache is None else cache 2839 generator = Generator(normalize=True, identify="safe") 2840 return lambda e: generator.generate(e, cache)
Returns a cached generator.