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