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