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