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