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