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