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