sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from decimal import Decimal 8from functools import reduce, wraps 9 10from sqlglot import exp 11from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 12from sqlglot.expressions import apply_index_offset 13from sqlglot.expressions.core import maybe_parse 14from sqlglot.helper import csv, name_sequence, seq_get 15from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 16from sqlglot.time import format_time 17from sqlglot.tokens import TokenType 18 19if t.TYPE_CHECKING: 20 from sqlglot._typing import E 21 from sqlglot.dialects.dialect import DialectType 22 23 G = t.TypeVar("G", bound="Generator") 24 GeneratorMethod = t.Callable[[G, E], str] 25 26logger = logging.getLogger("sqlglot") 27 28ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 29UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 30 31 32def unsupported_args( 33 *args: str | tuple[str, str], 34) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 35 """ 36 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 37 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 38 """ 39 diagnostic_by_arg: dict[str, str | None] = {} 40 for arg in args: 41 if isinstance(arg, str): 42 diagnostic_by_arg[arg] = None 43 else: 44 diagnostic_by_arg[arg[0]] = arg[1] 45 46 def decorator(func: GeneratorMethod) -> GeneratorMethod: 47 @wraps(func) 48 def _func(generator: G, expression: E) -> str: 49 expression_name = expression.__class__.__name__ 50 dialect_name = generator.dialect.__class__.__name__ 51 52 for arg_name, diagnostic in diagnostic_by_arg.items(): 53 if expression.args.get(arg_name): 54 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 55 arg_name, expression_name, dialect_name 56 ) 57 generator.unsupported(diagnostic) 58 59 return func(generator, expression) 60 61 return _func 62 63 return decorator 64 65 66AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 67 "windows": lambda self, e: ( 68 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 69 if e.args.get("windows") 70 else "" 71 ), 72 "qualify": lambda self, e: self.sql(e, "qualify"), 73} 74 75 76_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 77 78 79def _build_dispatch( 80 cls: type[Generator], 81) -> dict[type[exp.Expr], t.Callable[..., str]]: 82 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 83 84 for attr_name in dir(cls): 85 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 86 continue 87 88 expr_key = attr_name[:-4] 89 expr_cls = exp.EXPR_CLASSES.get(expr_key) 90 91 if expr_cls and expr_cls not in dispatch: 92 dispatch[expr_cls] = getattr(cls, attr_name) 93 94 return dispatch 95 96 97class Generator: 98 """ 99 Generator converts a given syntax tree to the corresponding SQL string. 100 101 Args: 102 pretty: Whether to format the produced SQL string. 103 Default: False. 104 identify: Determines when an identifier should be quoted. Possible values are: 105 False (default): Never quote, except in cases where it's mandatory by the dialect. 106 True: Always quote except for specials cases. 107 'safe': Only quote identifiers that are case insensitive. 108 normalize: Whether to normalize identifiers to lowercase. 109 Default: False. 110 pad: The pad size in a formatted string. For example, this affects the indentation of 111 a projection in a query, relative to its nesting level. 112 Default: 2. 113 indent: The indentation size in a formatted string. For example, this affects the 114 indentation of subqueries and filters under a `WHERE` clause. 115 Default: 2. 116 normalize_functions: How to normalize function names. Possible values are: 117 "upper" or True (default): Convert names to uppercase. 118 "lower": Convert names to lowercase. 119 False: Disables function name normalization. 120 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 121 Default ErrorLevel.WARN. 122 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 123 This is only relevant if unsupported_level is ErrorLevel.RAISE. 124 Default: 3 125 leading_comma: Whether the comma is leading or trailing in select expressions. 126 This is only relevant when generating in pretty mode. 127 Default: False 128 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 129 The default is on the smaller end because the length only represents a segment and not the true 130 line length. 131 Default: 80 132 comments: Whether to preserve comments in the output SQL code. 133 Default: True 134 """ 135 136 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 137 **JSON_PATH_PART_TRANSFORMS, 138 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 139 exp.AllowedValuesProperty: lambda self, e: ( 140 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 141 ), 142 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 143 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 144 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 145 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 146 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 147 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 148 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 149 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 150 exp.BinaryColumnConstraint: lambda *_: "BINARY", 151 exp.CaseSpecificColumnConstraint: lambda _, e: ( 152 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 153 ), 154 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 155 exp.Ceil: lambda self, e: self.ceil_floor(e), 156 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 157 exp.CharacterSetProperty: lambda self, e: ( 158 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 159 ), 160 exp.ClusteredColumnConstraint: lambda self, e: ( 161 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 162 ), 163 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 164 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 165 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 166 exp.ConvertToCharset: lambda self, e: self.func( 167 "CONVERT", e.this, e.args["dest"], e.args.get("source") 168 ), 169 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 170 exp.CredentialsProperty: lambda self, e: ( 171 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 172 ), 173 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 174 exp.SessionUser: lambda *_: "SESSION_USER", 175 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 176 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 177 exp.ApiProperty: lambda *_: "API", 178 exp.ApplicationProperty: lambda *_: "APPLICATION", 179 exp.CatalogProperty: lambda *_: "CATALOG", 180 exp.ComputeProperty: lambda *_: "COMPUTE", 181 exp.DatabaseProperty: lambda *_: "DATABASE", 182 exp.DynamicProperty: lambda *_: "DYNAMIC", 183 exp.EmptyProperty: lambda *_: "EMPTY", 184 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 185 exp.EndStatement: lambda *_: "END", 186 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 187 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 188 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 189 exp.EphemeralColumnConstraint: lambda self, e: ( 190 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 191 ), 192 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 193 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 194 exp.Except: lambda self, e: self.set_operations(e), 195 exp.ExternalProperty: lambda *_: "EXTERNAL", 196 exp.Floor: lambda self, e: self.ceil_floor(e), 197 exp.Get: lambda self, e: self.get_put_sql(e), 198 exp.GlobalProperty: lambda *_: "GLOBAL", 199 exp.HeapProperty: lambda *_: "HEAP", 200 exp.HybridProperty: lambda *_: "HYBRID", 201 exp.IcebergProperty: lambda *_: "ICEBERG", 202 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 203 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 204 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 205 exp.Intersect: lambda self, e: self.set_operations(e), 206 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 207 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 208 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 209 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 210 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 211 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 212 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 213 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 214 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 215 exp.LanguageProperty: lambda self, e: self.naked_property(e), 216 exp.LocationProperty: lambda self, e: self.naked_property(e), 217 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 218 exp.MaskingProperty: lambda *_: "MASKING", 219 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 220 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 221 exp.NetworkProperty: lambda *_: "NETWORK", 222 exp.NonClusteredColumnConstraint: lambda self, e: ( 223 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 224 ), 225 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 226 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 227 exp.OnCommitProperty: lambda _, e: ( 228 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 229 ), 230 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 231 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 232 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 233 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 234 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 235 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 236 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 237 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 238 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 239 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 240 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 241 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 242 f"PROJECTION POLICY {self.sql(e, 'this')}" 243 ), 244 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 245 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 246 exp.Put: lambda self, e: self.get_put_sql(e), 247 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 248 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 249 ), 250 exp.ReturnsProperty: lambda self, e: ( 251 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 252 ), 253 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 254 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 255 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 256 exp.SecureProperty: lambda *_: "SECURE", 257 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 258 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 259 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 260 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 261 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 262 exp.SqlReadWriteProperty: lambda _, e: e.name, 263 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 264 exp.StabilityProperty: lambda _, e: e.name, 265 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 266 exp.StreamingTableProperty: lambda *_: "STREAMING", 267 exp.StrictProperty: lambda *_: "STRICT", 268 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 269 exp.TableColumn: lambda self, e: self.sql(e.this), 270 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 271 exp.TemporaryProperty: lambda *_: "TEMPORARY", 272 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 273 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 274 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 275 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 276 exp.TransientProperty: lambda *_: "TRANSIENT", 277 exp.VirtualProperty: lambda *_: "VIRTUAL", 278 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 279 exp.Union: lambda self, e: self.set_operations(e), 280 exp.UnloggedProperty: lambda *_: "UNLOGGED", 281 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 282 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 283 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 284 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 285 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 286 exp.UtcTimestamp: lambda self, e: self.sql( 287 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 288 ), 289 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 290 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 291 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 292 exp.VolatileProperty: lambda *_: "VOLATILE", 293 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 294 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 295 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 296 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 297 exp.ForceProperty: lambda *_: "FORCE", 298 } 299 300 # Whether null ordering is supported in order by 301 # True: Full Support, None: No support, False: No support for certain cases 302 # such as window specifications, aggregate functions etc 303 NULL_ORDERING_SUPPORTED: bool | None = True 304 305 # Window functions that support NULLS FIRST/LAST 306 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 307 308 # Whether ignore nulls is inside the agg or outside. 309 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 310 IGNORE_NULLS_IN_FUNC = False 311 312 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 313 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 314 IGNORE_NULLS_BEFORE_ORDER = True 315 316 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 317 LOCKING_READS_SUPPORTED = False 318 319 # Whether the EXCEPT and INTERSECT operations can return duplicates 320 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 321 322 # Wrap derived values in parens, usually standard but spark doesn't support it 323 WRAP_DERIVED_VALUES = True 324 325 # Whether create function uses an AS before the RETURN 326 CREATE_FUNCTION_RETURN_AS = True 327 328 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 329 MATCHED_BY_SOURCE = True 330 331 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 332 SUPPORTS_MERGE_WHERE = False 333 334 # Whether the INTERVAL expression works only with values like '1 day' 335 SINGLE_STRING_INTERVAL = False 336 337 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 338 INTERVAL_ALLOWS_PLURAL_FORM = True 339 340 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 341 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 342 AUTO_REFRESH_BARE_INTERVALS = False 343 344 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 345 LIMIT_FETCH = "ALL" 346 347 # Whether limit and fetch allows expresions or just limits 348 LIMIT_ONLY_LITERALS = False 349 350 # Whether a table is allowed to be renamed with a db 351 RENAME_TABLE_WITH_DB = True 352 353 # The separator for grouping sets and rollups 354 GROUPINGS_SEP = "," 355 356 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 357 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 358 359 # The string used for creating an index on a table 360 INDEX_ON = "ON" 361 362 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 363 INOUT_SEPARATOR = " " 364 365 # Whether join hints should be generated 366 JOIN_HINTS = True 367 368 # Whether directed joins are supported 369 DIRECTED_JOINS = False 370 371 # Whether table hints should be generated 372 TABLE_HINTS = True 373 374 # Whether query hints should be generated 375 QUERY_HINTS = True 376 377 # What kind of separator to use for query hints 378 QUERY_HINT_SEP = ", " 379 380 # Whether comparing against booleans (e.g. x IS TRUE) is supported 381 IS_BOOL_ALLOWED = True 382 383 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 384 DUPLICATE_KEY_UPDATE_WITH_SET = True 385 386 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 387 LIMIT_IS_TOP = False 388 389 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 390 RETURNING_END = True 391 392 # Whether to generate an unquoted value for EXTRACT's date part argument 393 EXTRACT_ALLOWS_QUOTES = True 394 395 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 396 TZ_TO_WITH_TIME_ZONE = False 397 398 # Whether the NVL2 function is supported 399 NVL2_SUPPORTED = True 400 401 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 402 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 403 404 # Whether VALUES statements can be used as derived tables. 405 # MySQL 5 and Redshift do not allow this, so when False, it will convert 406 # SELECT * VALUES into SELECT UNION 407 VALUES_AS_TABLE = True 408 409 # Whether the word COLUMN is included when adding a column with ALTER TABLE 410 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 411 412 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 413 UNNEST_WITH_ORDINALITY = True 414 415 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 416 SEMI_ANTI_JOIN_WITH_SIDE = True 417 418 # Whether to include the type of a computed column in the CREATE DDL 419 COMPUTED_COLUMN_WITH_TYPE = True 420 421 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 422 SUPPORTS_TABLE_COPY = True 423 424 # Whether parentheses are required around the table sample's expression 425 TABLESAMPLE_REQUIRES_PARENS = True 426 427 # Whether a table sample clause's size needs to be followed by the ROWS keyword 428 TABLESAMPLE_SIZE_IS_ROWS = True 429 430 # The keyword(s) to use when generating a sample clause 431 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 432 433 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 434 TABLESAMPLE_WITH_METHOD = True 435 436 # The keyword to use when specifying the seed of a sample clause 437 TABLESAMPLE_SEED_KEYWORD = "SEED" 438 439 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 440 HISTORICAL_DATA_POST_ALIAS = False 441 442 # Whether COLLATE is a function instead of a binary operator 443 COLLATE_IS_FUNC = False 444 445 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 446 DATA_TYPE_SPECIFIERS_ALLOWED = False 447 448 # Whether conditions require booleans WHERE x = 0 vs WHERE x 449 ENSURE_BOOLS = False 450 451 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 452 CTE_RECURSIVE_KEYWORD_REQUIRED = True 453 454 # Whether CONCAT requires >1 arguments 455 SUPPORTS_SINGLE_ARG_CONCAT = True 456 457 # Whether LAST_DAY function supports a date part argument 458 LAST_DAY_SUPPORTS_DATE_PART = True 459 460 # Whether named columns are allowed in table aliases 461 SUPPORTS_TABLE_ALIAS_COLUMNS = True 462 463 # Whether named columns are allowed in CTE definitions 464 SUPPORTS_NAMED_CTE_COLUMNS = True 465 466 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 467 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 468 469 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 470 PIVOT_ALIAS_WITH_AS = True 471 472 # What delimiter to use for separating JSON key/value pairs 473 JSON_KEY_VALUE_PAIR_SEP = ":" 474 475 # INSERT OVERWRITE TABLE x override 476 INSERT_OVERWRITE = " OVERWRITE TABLE" 477 478 # Whether the SELECT .. INTO syntax is used instead of CTAS 479 SUPPORTS_SELECT_INTO = False 480 481 # Whether UNLOGGED tables can be created 482 SUPPORTS_UNLOGGED_TABLES = False 483 484 # Whether the CREATE TABLE LIKE statement is supported 485 SUPPORTS_CREATE_TABLE_LIKE = True 486 487 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 488 SUPPORTS_MODIFY_COLUMN = False 489 490 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 491 SUPPORTS_CHANGE_COLUMN = False 492 493 # Whether ALTER COLUMN can set a column's nullability together with its type 494 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 495 496 # Whether ALTER COLUMN IF EXISTS is supported 497 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 498 499 # Whether the LikeProperty needs to be specified inside of the schema clause 500 LIKE_PROPERTY_INSIDE_SCHEMA = False 501 502 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 503 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 504 MULTI_ARG_DISTINCT = True 505 506 # Whether the JSON extraction operators expect a value of type JSON 507 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 508 509 # Whether bracketed keys like ["foo"] are supported in JSON paths 510 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 511 512 # Whether to escape keys using single quotes in JSON paths 513 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 514 515 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 516 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 517 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 518 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 519 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 520 521 # The JSONPathPart expressions supported by this dialect 522 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 523 524 # Whether any(f(x) for x in array) can be implemented by this dialect 525 CAN_IMPLEMENT_ARRAY_ANY = False 526 527 # Whether the function TO_NUMBER is supported 528 SUPPORTS_TO_NUMBER = True 529 530 # Whether EXCLUDE in window specification is supported 531 SUPPORTS_WINDOW_EXCLUDE = False 532 533 # Whether or not set op modifiers apply to the outer set op or select. 534 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 535 # True means limit 1 happens after the set op, False means it it happens on y. 536 SET_OP_MODIFIERS = True 537 538 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 539 SET_OP_LIMITS = False 540 541 # Whether set operation operands can be parenthesized without a SELECT wrapper. 542 SET_OP_PARENTHESIZED_OPERANDS = True 543 544 # Whether parameters from COPY statement are wrapped in parentheses 545 COPY_PARAMS_ARE_WRAPPED = True 546 547 # Whether values of params are set with "=" token or empty space 548 COPY_PARAMS_EQ_REQUIRED = False 549 550 # Whether COPY statement has INTO keyword 551 COPY_HAS_INTO_KEYWORD = True 552 553 # Whether the conditional TRY(expression) function is supported 554 TRY_SUPPORTED = True 555 556 # Whether the UESCAPE syntax in unicode strings is supported 557 SUPPORTS_UESCAPE = True 558 559 # Function used to replace escaped unicode codes in unicode strings 560 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 561 562 # The keyword to use when generating a star projection with excluded columns 563 STAR_EXCEPT = "EXCEPT" 564 565 # The HEX function name 566 HEX_FUNC = "HEX" 567 568 # The keywords to use when prefixing & separating WITH based properties 569 WITH_PROPERTIES_PREFIX = "WITH" 570 571 # Whether to quote the generated expression of exp.JsonPath 572 QUOTE_JSON_PATH = True 573 574 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 575 PAD_FILL_PATTERN_IS_REQUIRED = False 576 577 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 578 SUPPORTS_EXPLODING_PROJECTIONS = True 579 580 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 581 ARRAY_CONCAT_IS_VAR_LEN = True 582 583 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 584 SUPPORTS_CONVERT_TIMEZONE = False 585 586 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 587 SUPPORTS_MEDIAN = True 588 589 # Whether UNIX_SECONDS(timestamp) is supported 590 SUPPORTS_UNIX_SECONDS = False 591 592 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 593 ALTER_SET_WRAPPED = False 594 595 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 596 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 597 # TODO: The normalization should be done by default once we've tested it across all dialects. 598 NORMALIZE_EXTRACT_DATE_PARTS = False 599 600 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 601 PARSE_JSON_NAME: str | None = "PARSE_JSON" 602 603 # The function name of the exp.ArraySize expression 604 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 605 606 # The syntax to use when altering the type of a column 607 ALTER_SET_TYPE = "SET DATA TYPE" 608 609 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 610 # None -> Doesn't support it at all 611 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 612 # True (Postgres) -> Explicitly requires it 613 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 614 615 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 616 SUPPORTS_DECODE_CASE = True 617 618 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 619 SUPPORTS_BETWEEN_FLAGS = False 620 621 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 622 SUPPORTS_LIKE_QUANTIFIERS = True 623 624 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 625 MATCH_AGAINST_TABLE_PREFIX: str | None = None 626 627 # Whether to include the VARIABLE keyword for SET assignments 628 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 629 630 # The keyword to use for default value assignment in DECLARE statements 631 DECLARE_DEFAULT_ASSIGNMENT = "=" 632 633 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 634 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 635 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 636 UPDATE_STATEMENT_SUPPORTS_FROM = True 637 638 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 639 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 640 641 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 642 # - Snowflake: DROP ICEBERG TABLE a.b; 643 # - DuckDB: DROP TABLE a.b; 644 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 645 646 TYPE_MAPPING: t.ClassVar = { 647 exp.DType.DATETIME2: "TIMESTAMP", 648 exp.DType.NCHAR: "CHAR", 649 exp.DType.NVARCHAR: "VARCHAR", 650 exp.DType.MEDIUMTEXT: "TEXT", 651 exp.DType.LONGTEXT: "TEXT", 652 exp.DType.TINYTEXT: "TEXT", 653 exp.DType.BLOB: "VARBINARY", 654 exp.DType.MEDIUMBLOB: "BLOB", 655 exp.DType.LONGBLOB: "BLOB", 656 exp.DType.TINYBLOB: "BLOB", 657 exp.DType.INET: "INET", 658 exp.DType.ROWVERSION: "VARBINARY", 659 exp.DType.SMALLDATETIME: "TIMESTAMP", 660 } 661 662 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 663 664 # mapping of DType to its default parameters, bounds 665 TYPE_PARAM_SETTINGS: t.ClassVar[ 666 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 667 ] = {} 668 669 TIME_PART_SINGULARS: t.ClassVar = { 670 "MICROSECONDS": "MICROSECOND", 671 "SECONDS": "SECOND", 672 "MINUTES": "MINUTE", 673 "HOURS": "HOUR", 674 "DAYS": "DAY", 675 "WEEKS": "WEEK", 676 "MONTHS": "MONTH", 677 "QUARTERS": "QUARTER", 678 "YEARS": "YEAR", 679 } 680 681 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 682 "cluster": lambda self, e: self.sql(e, "cluster"), 683 "distribute": lambda self, e: self.sql(e, "distribute"), 684 "sort": lambda self, e: self.sql(e, "sort"), 685 **AFTER_HAVING_MODIFIER_TRANSFORMS, 686 } 687 688 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 689 690 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 691 692 PARAMETER_TOKEN = "@" 693 NAMED_PLACEHOLDER_TOKEN = ":" 694 695 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 696 697 PROPERTIES_LOCATION: t.ClassVar = { 698 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 700 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 701 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 702 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 704 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 705 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 706 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 708 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 710 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 711 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 712 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 714 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 719 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 720 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 722 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 723 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 725 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 729 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 735 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 736 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 737 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 738 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 739 exp.HeapProperty: exp.Properties.Location.POST_WITH, 740 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 741 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 743 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 746 exp.JournalProperty: exp.Properties.Location.POST_NAME, 747 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 748 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 752 exp.LogProperty: exp.Properties.Location.POST_NAME, 753 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 754 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 755 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 756 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 758 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 759 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 761 exp.Order: exp.Properties.Location.POST_SCHEMA, 762 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 764 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 766 exp.Property: exp.Properties.Location.POST_WITH, 767 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 771 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 772 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 778 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 779 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 780 exp.Set: exp.Properties.Location.POST_SCHEMA, 781 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 782 exp.SetProperty: exp.Properties.Location.POST_CREATE, 783 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 784 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 785 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 786 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 787 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 790 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 791 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 792 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 793 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.Tags: exp.Properties.Location.POST_WITH, 795 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 796 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 797 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 798 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 800 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 801 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 802 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 805 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 806 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 807 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 808 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 809 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 810 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 811 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 812 } 813 814 # Keywords that can't be used as unquoted identifier names 815 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 816 817 # Exprs whose comments are separated from them for better formatting 818 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 819 exp.Command, 820 exp.Create, 821 exp.Describe, 822 exp.Delete, 823 exp.Drop, 824 exp.From, 825 exp.Insert, 826 exp.Join, 827 exp.MultitableInserts, 828 exp.Order, 829 exp.Group, 830 exp.Having, 831 exp.Select, 832 exp.SetOperation, 833 exp.Update, 834 exp.Where, 835 exp.With, 836 ) 837 838 # Exprs that should not have their comments generated in maybe_comment 839 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 840 exp.Binary, 841 exp.SetOperation, 842 ) 843 844 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 845 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 846 exp.Column, 847 exp.Literal, 848 exp.Neg, 849 exp.Paren, 850 ) 851 852 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 853 exp.DType.NVARCHAR, 854 exp.DType.VARCHAR, 855 exp.DType.CHAR, 856 exp.DType.NCHAR, 857 } 858 859 # Exprs that need to have all CTEs under them bubbled up to them 860 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 861 862 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 863 864 MOD_OPERATOR = "%" 865 866 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 867 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 868 exp.Mul, 869 exp.Div, 870 exp.IntDiv, 871 exp.Mod, 872 ) 873 874 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 875 876 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 877 878 __slots__ = ( 879 "pretty", 880 "identify", 881 "normalize", 882 "pad", 883 "_indent", 884 "normalize_functions", 885 "unsupported_level", 886 "max_unsupported", 887 "leading_comma", 888 "max_text_width", 889 "comments", 890 "dialect", 891 "unsupported_messages", 892 "_escaped_quote_end", 893 "_escaped_byte_quote_end", 894 "_escaped_identifier_end", 895 "_next_name", 896 "_identifier_start", 897 "_identifier_end", 898 "_quote_json_path_key_using_brackets", 899 "_dispatch", 900 ) 901 902 def __init__( 903 self, 904 pretty: bool | int | None = None, 905 identify: str | bool = False, 906 normalize: bool = False, 907 pad: int = 2, 908 indent: int = 2, 909 normalize_functions: str | bool | None = None, 910 unsupported_level: ErrorLevel = ErrorLevel.WARN, 911 max_unsupported: int = 3, 912 leading_comma: bool = False, 913 max_text_width: int = 80, 914 comments: bool = True, 915 dialect: DialectType = None, 916 ): 917 import sqlglot 918 import sqlglot.dialects.dialect 919 920 self.pretty = pretty if pretty is not None else sqlglot.pretty 921 self.identify = identify 922 self.normalize = normalize 923 self.pad = pad 924 self._indent = indent 925 self.unsupported_level = unsupported_level 926 self.max_unsupported = max_unsupported 927 self.leading_comma = leading_comma 928 self.max_text_width = max_text_width 929 self.comments = comments 930 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 931 932 # This is both a Dialect property and a Generator argument, so we prioritize the latter 933 self.normalize_functions = ( 934 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 935 ) 936 937 self.unsupported_messages: list[str] = [] 938 self._escaped_quote_end: str = ( 939 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 940 ) 941 self._escaped_byte_quote_end: str = ( 942 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 943 if self.dialect.BYTE_END 944 else "" 945 ) 946 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 947 948 self._next_name = name_sequence("_t") 949 950 self._identifier_start = self.dialect.IDENTIFIER_START 951 self._identifier_end = self.dialect.IDENTIFIER_END 952 953 self._quote_json_path_key_using_brackets = True 954 955 cls = type(self) 956 dispatch = _DISPATCH_CACHE.get(cls) 957 if dispatch is None: 958 dispatch = _build_dispatch(cls) 959 _DISPATCH_CACHE[cls] = dispatch 960 self._dispatch = dispatch 961 962 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 963 """ 964 Generates the SQL string corresponding to the given syntax tree. 965 966 Args: 967 expression: The syntax tree. 968 copy: Whether to copy the expression. The generator performs mutations so 969 it is safer to copy. 970 971 Returns: 972 The SQL string corresponding to `expression`. 973 """ 974 if copy: 975 expression = expression.copy() 976 977 expression = self.preprocess(expression) 978 979 self.unsupported_messages = [] 980 sql = self.sql(expression).strip() 981 982 if self.pretty: 983 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 984 985 if self.unsupported_level == ErrorLevel.IGNORE: 986 return sql 987 988 if self.unsupported_level == ErrorLevel.WARN: 989 for msg in self.unsupported_messages: 990 logger.warning(msg) 991 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 992 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 993 994 return sql 995 996 def preprocess(self, expression: exp.Expr) -> exp.Expr: 997 """Apply generic preprocessing transformations to a given expression.""" 998 expression = self._move_ctes_to_top_level(expression) 999 1000 if self.ENSURE_BOOLS: 1001 import sqlglot.transforms 1002 1003 expression = sqlglot.transforms.ensure_bools(expression) 1004 1005 return expression 1006 1007 def _move_ctes_to_top_level(self, expression: E) -> E: 1008 if ( 1009 not expression.parent 1010 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1011 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1012 ): 1013 import sqlglot.transforms 1014 1015 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1016 return expression 1017 1018 def unsupported(self, message: str) -> None: 1019 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1020 raise UnsupportedError(message) 1021 self.unsupported_messages.append(message) 1022 1023 def sep(self, sep: str = " ") -> str: 1024 return f"{sep.strip()}\n" if self.pretty else sep 1025 1026 def seg(self, sql: str, sep: str = " ") -> str: 1027 return f"{self.sep(sep)}{sql}" 1028 1029 def sanitize_comment(self, comment: str) -> str: 1030 comment = " " + comment if comment[0].strip() else comment 1031 comment = comment + " " if comment[-1].strip() else comment 1032 1033 # Escape block comment markers to prevent premature closure or unintended nesting. 1034 # This is necessary because single-line comments (--) are converted to block comments 1035 # (/* */) on output, and any */ in the original text would close the comment early. 1036 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1037 1038 return comment 1039 1040 def maybe_comment( 1041 self, 1042 sql: str, 1043 expression: exp.Expr | None = None, 1044 comments: list[str] | None = None, 1045 separated: bool = False, 1046 ) -> str: 1047 comments = ( 1048 ((expression and expression.comments) if comments is None else comments) # type: ignore 1049 if self.comments 1050 else None 1051 ) 1052 1053 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1054 return sql 1055 1056 comments_list = [ 1057 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1058 for comment in comments 1059 if comment 1060 ] 1061 1062 if not comments_list: 1063 return sql 1064 1065 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1066 comments_sql = self.sep().join(comments_list) 1067 return ( 1068 f"{self.sep()}{comments_sql}{sql}" 1069 if not sql or sql[0].isspace() 1070 else f"{comments_sql}{self.sep()}{sql}" 1071 ) 1072 1073 return f"{sql} {' '.join(comments_list)}" 1074 1075 def wrap(self, expression: exp.Expr | str) -> str: 1076 this_sql = ( 1077 self.sql(expression) 1078 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1079 else self.sql(expression, "this") 1080 ) 1081 if not this_sql: 1082 return "()" 1083 1084 this_sql = self.indent(this_sql, level=1, pad=0) 1085 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1086 1087 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1088 original = self.identify 1089 self.identify = False 1090 result = func(*args, **kwargs) 1091 self.identify = original 1092 return result 1093 1094 def normalize_func(self, name: str) -> str: 1095 if self.normalize_functions == "upper" or self.normalize_functions is True: 1096 return name.upper() 1097 if self.normalize_functions == "lower": 1098 return name.lower() 1099 return name 1100 1101 def indent( 1102 self, 1103 sql: str, 1104 level: int = 0, 1105 pad: int | None = None, 1106 skip_first: bool = False, 1107 skip_last: bool = False, 1108 ) -> str: 1109 if not self.pretty or not sql: 1110 return sql 1111 1112 pad = self.pad if pad is None else pad 1113 lines = sql.split("\n") 1114 1115 return "\n".join( 1116 ( 1117 line 1118 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1119 else f"{' ' * (level * self._indent + pad)}{line}" 1120 ) 1121 for i, line in enumerate(lines) 1122 ) 1123 1124 def sql( 1125 self, 1126 expression: str | exp.Expr | None, 1127 key: str | None = None, 1128 comment: bool = True, 1129 ) -> str: 1130 if not expression: 1131 return "" 1132 1133 if isinstance(expression, str): 1134 return expression 1135 1136 if key: 1137 value = expression.args.get(key) 1138 if value: 1139 return self.sql(value) 1140 return "" 1141 1142 handler = self._dispatch.get(expression.__class__) 1143 1144 if handler: 1145 sql = handler(self, expression) 1146 elif isinstance(expression, exp.Func): 1147 sql = self.function_fallback_sql(expression) 1148 elif isinstance(expression, exp.Property): 1149 sql = self.property_sql(expression) 1150 else: 1151 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1152 1153 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1154 1155 def uncache_sql(self, expression: exp.Uncache) -> str: 1156 table = self.sql(expression, "this") 1157 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1158 return f"UNCACHE TABLE{exists_sql} {table}" 1159 1160 def cache_sql(self, expression: exp.Cache) -> str: 1161 lazy = " LAZY" if expression.args.get("lazy") else "" 1162 table = self.sql(expression, "this") 1163 options = expression.args.get("options") 1164 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1165 sql = self.sql(expression, "expression") 1166 sql = f" AS{self.sep()}{sql}" if sql else "" 1167 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1168 return self.prepend_ctes(expression, sql) 1169 1170 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1171 default = "DEFAULT " if expression.args.get("default") else "" 1172 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1173 1174 def column_parts(self, expression: exp.Column) -> str: 1175 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1176 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1177 return self.sql(expression, "this") 1178 1179 return ".".join( 1180 self.sql(part) 1181 for part in ( 1182 expression.args.get("catalog"), 1183 expression.args.get("db"), 1184 expression.args.get("table"), 1185 expression.args.get("this"), 1186 ) 1187 if part 1188 ) 1189 1190 def column_sql(self, expression: exp.Column) -> str: 1191 join_mark = " (+)" if expression.args.get("join_mark") else "" 1192 1193 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1194 join_mark = "" 1195 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1196 1197 return f"{self.column_parts(expression)}{join_mark}" 1198 1199 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1200 return self.column_sql(expression) 1201 1202 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1203 this = self.sql(expression, "this") 1204 this = f" {this}" if this else "" 1205 position = self.sql(expression, "position") 1206 return f"{position}{this}" 1207 1208 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1209 column = self.sql(expression, "this") 1210 kind = self.sql(expression, "kind") 1211 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1212 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1213 kind = f"{sep}{kind}" if kind else "" 1214 constraints = f" {constraints}" if constraints else "" 1215 position = self.sql(expression, "position") 1216 position = f" {position}" if position else "" 1217 1218 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1219 kind = "" 1220 1221 return f"{exists}{column}{kind}{constraints}{position}" 1222 1223 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1224 this = self.sql(expression, "this") 1225 kind_sql = self.sql(expression, "kind").strip() 1226 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1227 1228 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1229 this = self.sql(expression, "this") 1230 if expression.args.get("not_null"): 1231 persisted = " PERSISTED NOT NULL" 1232 elif expression.args.get("persisted"): 1233 persisted = " PERSISTED" 1234 else: 1235 persisted = "" 1236 1237 return f"AS {this}{persisted}" 1238 1239 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1240 return self.token_sql(TokenType.AUTO_INCREMENT) 1241 1242 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1243 if isinstance(expression.this, list): 1244 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1245 else: 1246 this = self.sql(expression, "this") 1247 1248 return f"COMPRESS {this}" 1249 1250 def generatedasidentitycolumnconstraint_sql( 1251 self, expression: exp.GeneratedAsIdentityColumnConstraint 1252 ) -> str: 1253 this = "" 1254 if expression.this is not None: 1255 on_null = " ON NULL" if expression.args.get("on_null") else "" 1256 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1257 1258 start = expression.args.get("start") 1259 start = f"START WITH {start}" if start else "" 1260 increment = expression.args.get("increment") 1261 increment = f" INCREMENT BY {increment}" if increment else "" 1262 minvalue = expression.args.get("minvalue") 1263 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1264 maxvalue = expression.args.get("maxvalue") 1265 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1266 cycle = expression.args.get("cycle") 1267 cycle_sql = "" 1268 1269 if cycle is not None: 1270 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1271 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1272 1273 sequence_opts = "" 1274 if start or increment or cycle_sql: 1275 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1276 sequence_opts = f" ({sequence_opts.strip()})" 1277 1278 expr = self.sql(expression, "expression") 1279 expr = f"({expr})" if expr else "IDENTITY" 1280 1281 return f"GENERATED{this} AS {expr}{sequence_opts}" 1282 1283 def generatedasrowcolumnconstraint_sql( 1284 self, expression: exp.GeneratedAsRowColumnConstraint 1285 ) -> str: 1286 start = "START" if expression.args.get("start") else "END" 1287 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1288 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1289 1290 def periodforsystemtimeconstraint_sql( 1291 self, expression: exp.PeriodForSystemTimeConstraint 1292 ) -> str: 1293 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1294 1295 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1296 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1297 1298 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1299 desc = expression.args.get("desc") 1300 if desc is not None: 1301 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1302 options = self.expressions(expression, key="options", flat=True, sep=" ") 1303 options = f" {options}" if options else "" 1304 return f"PRIMARY KEY{options}" 1305 1306 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1307 this = self.sql(expression, "this") 1308 this = f" {this}" if this else "" 1309 index_type = expression.args.get("index_type") 1310 index_type = f" USING {index_type}" if index_type else "" 1311 on_conflict = self.sql(expression, "on_conflict") 1312 on_conflict = f" {on_conflict}" if on_conflict else "" 1313 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1314 options = self.expressions(expression, key="options", flat=True, sep=" ") 1315 options = f" {options}" if options else "" 1316 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1317 1318 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1319 input_ = expression.args.get("input_") 1320 output = expression.args.get("output") 1321 variadic = expression.args.get("variadic") 1322 1323 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1324 if variadic: 1325 return "VARIADIC" 1326 1327 if input_ and output: 1328 return f"IN{self.INOUT_SEPARATOR}OUT" 1329 if input_: 1330 return "IN" 1331 if output: 1332 return "OUT" 1333 1334 return "" 1335 1336 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1337 return self.sql(expression, "this") 1338 1339 def create_sql(self, expression: exp.Create) -> str: 1340 kind = self.sql(expression, "kind") 1341 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1342 1343 properties = expression.args.get("properties") 1344 1345 if ( 1346 kind == "TRIGGER" 1347 and properties 1348 and properties.expressions 1349 and isinstance(properties.expressions[0], exp.TriggerProperties) 1350 and properties.expressions[0].args.get("constraint") 1351 ): 1352 kind = f"CONSTRAINT {kind}" 1353 1354 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1355 1356 this = self.createable_sql(expression, properties_locs) 1357 1358 properties_sql = "" 1359 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1360 exp.Properties.Location.POST_WITH 1361 ): 1362 props_ast = exp.Properties( 1363 expressions=[ 1364 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1365 *properties_locs[exp.Properties.Location.POST_WITH], 1366 ] 1367 ) 1368 props_ast.parent = expression 1369 properties_sql = self.sql(props_ast) 1370 1371 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1372 properties_sql = self.sep() + properties_sql 1373 elif not self.pretty: 1374 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1375 properties_sql = f" {properties_sql}" 1376 1377 begin = " BEGIN" if expression.args.get("begin") else "" 1378 1379 expression_sql = self.sql(expression, "expression") 1380 if expression_sql: 1381 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1382 1383 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1384 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1385 ): 1386 postalias_props_sql = "" 1387 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1388 postalias_props_sql = self.properties( 1389 exp.Properties( 1390 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1391 ), 1392 wrapped=False, 1393 ) 1394 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1395 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1396 1397 postindex_props_sql = "" 1398 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1399 postindex_props_sql = self.properties( 1400 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1401 wrapped=False, 1402 prefix=" ", 1403 ) 1404 1405 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1406 indexes = f" {indexes}" if indexes else "" 1407 index_sql = indexes + postindex_props_sql 1408 1409 replace = " OR REPLACE" if expression.args.get("replace") else "" 1410 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1411 unique = " UNIQUE" if expression.args.get("unique") else "" 1412 1413 clustered = expression.args.get("clustered") 1414 if clustered is None: 1415 clustered_sql = "" 1416 elif clustered: 1417 clustered_sql = " CLUSTERED COLUMNSTORE" 1418 else: 1419 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1420 1421 postcreate_props_sql = "" 1422 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1423 postcreate_props_sql = self.properties( 1424 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1425 sep=" ", 1426 prefix=" ", 1427 wrapped=False, 1428 ) 1429 1430 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1431 1432 postexpression_props_sql = "" 1433 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1434 postexpression_props_sql = self.properties( 1435 exp.Properties( 1436 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1437 ), 1438 sep=" ", 1439 prefix=" ", 1440 wrapped=False, 1441 ) 1442 1443 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1444 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1445 no_schema_binding = ( 1446 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1447 ) 1448 1449 clone = self.sql(expression, "clone") 1450 clone = f" {clone}" if clone else "" 1451 1452 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1453 properties_expression = f"{expression_sql}{properties_sql}" 1454 else: 1455 properties_expression = f"{properties_sql}{expression_sql}" 1456 1457 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1458 return self.prepend_ctes(expression, expression_sql) 1459 1460 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1461 start = self.sql(expression, "start") 1462 start = f"START WITH {start}" if start else "" 1463 increment = self.sql(expression, "increment") 1464 increment = f" INCREMENT BY {increment}" if increment else "" 1465 minvalue = self.sql(expression, "minvalue") 1466 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1467 maxvalue = self.sql(expression, "maxvalue") 1468 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1469 owned = self.sql(expression, "owned") 1470 owned = f" OWNED BY {owned}" if owned else "" 1471 1472 cache = expression.args.get("cache") 1473 if cache is None: 1474 cache_str = "" 1475 elif cache is True: 1476 cache_str = " CACHE" 1477 else: 1478 cache_str = f" CACHE {cache}" 1479 1480 options = self.expressions(expression, key="options", flat=True, sep=" ") 1481 options = f" {options}" if options else "" 1482 1483 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1484 1485 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1486 timing = expression.args.get("timing", "") 1487 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1488 timing_events = f"{timing} {events}".strip() if timing or events else "" 1489 1490 parts = [timing_events, "ON", self.sql(expression, "table")] 1491 1492 if referenced_table := expression.args.get("referenced_table"): 1493 parts.extend(["FROM", self.sql(referenced_table)]) 1494 1495 if deferrable := expression.args.get("deferrable"): 1496 parts.append(deferrable) 1497 1498 if initially := expression.args.get("initially"): 1499 parts.append(f"INITIALLY {initially}") 1500 1501 if referencing := expression.args.get("referencing"): 1502 parts.append(self.sql(referencing)) 1503 1504 if for_each := expression.args.get("for_each"): 1505 parts.append(f"FOR EACH {for_each}") 1506 1507 if when := expression.args.get("when"): 1508 parts.append(f"WHEN ({self.sql(when)})") 1509 1510 parts.append(self.sql(expression, "execute")) 1511 1512 return self.sep().join(parts) 1513 1514 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1515 parts = [] 1516 1517 if old_alias := expression.args.get("old"): 1518 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1519 1520 if new_alias := expression.args.get("new"): 1521 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1522 1523 return f"REFERENCING {' '.join(parts)}" 1524 1525 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1526 columns = expression.args.get("columns") 1527 if columns: 1528 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1529 1530 return self.sql(expression, "this") 1531 1532 def clone_sql(self, expression: exp.Clone) -> str: 1533 this = self.sql(expression, "this") 1534 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1535 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1536 return f"{shallow}{keyword} {this}" 1537 1538 def describe_sql(self, expression: exp.Describe) -> str: 1539 style = expression.args.get("style") 1540 style = f" {style}" if style else "" 1541 partition = self.sql(expression, "partition") 1542 partition = f" {partition}" if partition else "" 1543 format = self.sql(expression, "format") 1544 format = f" {format}" if format else "" 1545 as_json = " AS JSON" if expression.args.get("as_json") else "" 1546 1547 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1548 1549 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1550 tag = self.sql(expression, "tag") 1551 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1552 1553 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1554 with_ = self.sql(expression, "with_") 1555 if with_: 1556 sql = f"{with_}{self.sep()}{sql}" 1557 return sql 1558 1559 def with_sql(self, expression: exp.With) -> str: 1560 udfs = self.expressions(expression, key="udfs", flat=True) 1561 udfs = f"WITH {udfs}" if udfs else "" 1562 1563 sql = self.expressions(expression, flat=True) 1564 1565 recursive = ( 1566 "RECURSIVE " 1567 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1568 else "" 1569 ) 1570 search = self.sql(expression, "search") 1571 search = f" {search}" if search else "" 1572 1573 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1574 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1575 1576 def cte_sql(self, expression: exp.CTE) -> str: 1577 alias = expression.args.get("alias") 1578 if alias: 1579 alias.add_comments(expression.pop_comments()) 1580 1581 alias_sql = self.sql(expression, "alias") 1582 1583 materialized = expression.args.get("materialized") 1584 if materialized is False: 1585 materialized = "NOT MATERIALIZED " 1586 elif materialized: 1587 materialized = "MATERIALIZED " 1588 1589 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1590 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1591 1592 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1593 1594 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1595 alias = self.sql(expression, "this") 1596 columns = self.expressions(expression, key="columns", flat=True) 1597 columns = f"({columns})" if columns else "" 1598 1599 if ( 1600 columns 1601 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1602 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1603 ): 1604 columns = "" 1605 self.unsupported("Named columns are not supported in table alias.") 1606 1607 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1608 alias = self._next_name() 1609 1610 return f"{alias}{columns}" 1611 1612 def bitstring_sql(self, expression: exp.BitString) -> str: 1613 this = self.sql(expression, "this") 1614 if self.dialect.BIT_START: 1615 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1616 return f"{int(this, 2)}" 1617 1618 def hexstring_sql( 1619 self, expression: exp.HexString, binary_function_repr: str | None = None 1620 ) -> str: 1621 this = self.sql(expression, "this") 1622 is_integer_type = expression.args.get("is_integer") 1623 1624 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1625 not self.dialect.HEX_START and not binary_function_repr 1626 ): 1627 # Integer representation will be returned if: 1628 # - The read dialect treats the hex value as integer literal but not the write 1629 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1630 return f"{int(this, 16)}" 1631 1632 if not is_integer_type: 1633 # Read dialect treats the hex value as BINARY/BLOB 1634 if binary_function_repr: 1635 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1636 return self.func(binary_function_repr, exp.Literal.string(this)) 1637 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1638 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1639 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1640 1641 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1642 1643 def bytestring_sql(self, expression: exp.ByteString) -> str: 1644 this = self.sql(expression, "this") 1645 if self.dialect.BYTE_START: 1646 escaped_byte_string = self.escape_str( 1647 this, 1648 escape_backslash=False, 1649 delimiter=self.dialect.BYTE_END, 1650 escaped_delimiter=self._escaped_byte_quote_end, 1651 is_byte_string=True, 1652 ) 1653 is_bytes = expression.args.get("is_bytes", False) 1654 delimited_byte_string = ( 1655 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1656 ) 1657 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1658 return self.sql( 1659 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1660 ) 1661 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1662 return self.sql( 1663 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1664 ) 1665 1666 return delimited_byte_string 1667 1668 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1669 return self.sql(exp.Literal.string(this)) 1670 1671 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1672 return "" 1673 1674 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1675 this = self.sql(expression, "this") 1676 escape = expression.args.get("escape") 1677 unicode_start = self.dialect.UNICODE_START 1678 1679 if unicode_start: 1680 escape_substitute = r"\\\1" 1681 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1682 else: 1683 escape_substitute = r"\\u\1" 1684 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1685 1686 if escape: 1687 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1688 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1689 else: 1690 escape_pattern = ESCAPED_UNICODE_RE 1691 escape_sql = "" 1692 1693 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1694 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1695 1696 if unicode_start: 1697 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1698 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1699 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1700 else: 1701 this = self.escape_str(this, escape_backslash=False) 1702 1703 return f"{left_quote}{this}{right_quote}{escape_sql}" 1704 1705 def rawstring_sql(self, expression: exp.RawString) -> str: 1706 string = expression.this 1707 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1708 string = string.replace("\\", "\\\\") 1709 1710 string = self.escape_str(string, escape_backslash=False) 1711 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1712 1713 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1714 this = self.sql(expression, "this") 1715 specifier = self.sql(expression, "expression") 1716 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1717 return f"{this}{specifier}" 1718 1719 def datatype_param_bound_limiter( 1720 self, 1721 expression: exp.DataType, 1722 type_value: exp.DType, 1723 defaults: tuple[int, ...], 1724 bounds: tuple[int | None, ...], 1725 ) -> exp.DataType: 1726 params = expression.expressions 1727 1728 if not params: 1729 if defaults: 1730 expression.set( 1731 "expressions", 1732 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1733 ) 1734 return expression 1735 1736 if not bounds: 1737 return expression 1738 1739 for i, param in enumerate(params): 1740 bound = bounds[i] if i < len(bounds) else None 1741 if bound is None: 1742 continue 1743 1744 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1745 value = ( 1746 param_value.to_py() 1747 if isinstance(param_value, exp.Literal) and param_value.is_number 1748 else None 1749 ) 1750 if isinstance(value, (int, Decimal)) and value > bound: 1751 self.unsupported( 1752 f"{type_value.value} parameter {param_value.name} exceeds " 1753 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1754 ) 1755 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1756 1757 return expression 1758 1759 def datatype_sql(self, expression: exp.DataType) -> str: 1760 nested = "" 1761 values = "" 1762 1763 expr_nested = expression.args.get("nested") 1764 type_value = expression.this 1765 1766 if ( 1767 not expr_nested 1768 and isinstance(type_value, exp.DType) 1769 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1770 ): 1771 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1772 1773 interior = ( 1774 self.expressions( 1775 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1776 ) 1777 if expr_nested and self.pretty 1778 else self.expressions(expression, flat=True) 1779 ) 1780 1781 if type_value in self.UNSUPPORTED_TYPES: 1782 self.unsupported( 1783 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1784 ) 1785 1786 type_sql: t.Any = "" 1787 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1788 type_sql = self.sql(expression, "kind") 1789 elif type_value == exp.DType.CHARACTER_SET: 1790 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1791 else: 1792 type_sql = ( 1793 self.TYPE_MAPPING.get(type_value, type_value.value) 1794 if isinstance(type_value, exp.DType) 1795 else type_value 1796 ) 1797 1798 if interior: 1799 if expr_nested: 1800 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1801 if expression.args.get("values") is not None: 1802 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1803 values = self.expressions(expression, key="values", flat=True) 1804 values = f"{delimiters[0]}{values}{delimiters[1]}" 1805 elif type_value == exp.DType.INTERVAL: 1806 nested = f" {interior}" 1807 else: 1808 nested = f"({interior})" 1809 1810 type_sql = f"{type_sql}{nested}{values}" 1811 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1812 exp.DType.TIMETZ, 1813 exp.DType.TIMESTAMPTZ, 1814 ): 1815 type_sql = f"{type_sql} WITH TIME ZONE" 1816 1817 collate = self.sql(expression, "collate") 1818 if collate: 1819 type_sql = f"{type_sql} COLLATE {collate}" 1820 1821 return type_sql 1822 1823 def directory_sql(self, expression: exp.Directory) -> str: 1824 local = "LOCAL " if expression.args.get("local") else "" 1825 row_format = self.sql(expression, "row_format") 1826 row_format = f" {row_format}" if row_format else "" 1827 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1828 1829 def delete_sql(self, expression: exp.Delete) -> str: 1830 hint = self.sql(expression, "hint") 1831 this = self.sql(expression, "this") 1832 this = f" FROM {this}" if this else "" 1833 using = self.expressions(expression, key="using") 1834 using = f" USING {using}" if using else "" 1835 cluster = self.sql(expression, "cluster") 1836 cluster = f" {cluster}" if cluster else "" 1837 where = self.sql(expression, "where") 1838 returning = self.sql(expression, "returning") 1839 order = self.sql(expression, "order") 1840 limit = self.sql(expression, "limit") 1841 tables = self.expressions(expression, key="tables") 1842 tables = f" {tables}" if tables else "" 1843 if self.RETURNING_END: 1844 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1845 else: 1846 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1847 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1848 1849 def drop_sql(self, expression: exp.Drop) -> str: 1850 tables = self.expressions(expression, key="tables", flat=True) 1851 expressions = self.expressions(expression, flat=True) 1852 expressions = f" ({expressions})" if expressions else "" 1853 kind = expression.args["kind"] 1854 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1855 iceberg = ( 1856 " ICEBERG" 1857 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1858 else "" 1859 ) 1860 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1861 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1862 on_cluster = self.sql(expression, "cluster") 1863 on_cluster = f" {on_cluster}" if on_cluster else "" 1864 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1865 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1866 cascade = " CASCADE" if expression.args.get("cascade") else "" 1867 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1868 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1869 purge = " PURGE" if expression.args.get("purge") else "" 1870 sync = " SYNC" if expression.args.get("sync") else "" 1871 force = " FORCE" if expression.args.get("force") else "" 1872 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1873 1874 def set_operation(self, expression: exp.SetOperation) -> str: 1875 op_type = type(expression) 1876 op_name = op_type.key.upper() 1877 1878 distinct = expression.args.get("distinct") 1879 if ( 1880 distinct is False 1881 and op_type in (exp.Except, exp.Intersect) 1882 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1883 ): 1884 self.unsupported(f"{op_name} ALL is not supported") 1885 1886 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1887 1888 if distinct is None: 1889 distinct = default_distinct 1890 if distinct is None: 1891 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1892 1893 if distinct is default_distinct: 1894 distinct_or_all = "" 1895 else: 1896 distinct_or_all = " DISTINCT" if distinct else " ALL" 1897 1898 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1899 side_kind = f"{side_kind} " if side_kind else "" 1900 1901 by_name = " BY NAME" if expression.args.get("by_name") else "" 1902 on = self.expressions(expression, key="on", flat=True) 1903 on = f" ON ({on})" if on else "" 1904 1905 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1906 1907 def set_operations(self, expression: exp.SetOperation) -> str: 1908 if not self.SET_OP_MODIFIERS: 1909 limit = expression.args.get("limit") 1910 order = expression.args.get("order") 1911 offset = expression.args.get("offset") 1912 1913 if limit or order or offset: 1914 select = self._move_ctes_to_top_level( 1915 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1916 ) 1917 1918 for arg in ("limit", "order", "offset"): 1919 if value := expression.args.get(arg): 1920 select.set(arg, value.pop()) 1921 return self.sql(select) 1922 1923 sqls: list[str] = [] 1924 stack: list[str | exp.Expr] = [expression] 1925 1926 while stack: 1927 node = stack.pop() 1928 1929 if isinstance(node, exp.SetOperation): 1930 stack.append(node.expression) 1931 stack.append( 1932 self.maybe_comment( 1933 self.set_operation(node), comments=node.comments, separated=True 1934 ) 1935 ) 1936 stack.append(node.this) 1937 else: 1938 if ( 1939 not self.SET_OP_LIMITS 1940 and isinstance(node, exp.Select) 1941 and node.args.get("limit") 1942 ): 1943 node = node.subquery(copy=False) 1944 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1945 node = exp.select("*").from_(node, copy=False) 1946 sqls.append(self.sql(node)) 1947 1948 this = self.sep().join(sqls) 1949 this = self.query_modifiers(expression, this) 1950 return self.prepend_ctes(expression, this) 1951 1952 def fetch_sql(self, expression: exp.Fetch) -> str: 1953 direction = expression.args.get("direction") 1954 direction = f" {direction}" if direction else "" 1955 count = self.sql(expression, "count") 1956 count = f" {count}" if count else "" 1957 limit_options = self.sql(expression, "limit_options") 1958 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1959 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1960 1961 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1962 percent = " PERCENT" if expression.args.get("percent") else "" 1963 rows = " ROWS" if expression.args.get("rows") else "" 1964 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1965 if not with_ties and rows: 1966 with_ties = " ONLY" 1967 return f"{percent}{rows}{with_ties}" 1968 1969 def filter_sql(self, expression: exp.Filter) -> str: 1970 this = self.sql(expression, "this") 1971 where = self.sql(expression, "expression").strip() 1972 return f"{this} FILTER({where})" 1973 1974 def hint_sql(self, expression: exp.Hint) -> str: 1975 if not self.QUERY_HINTS: 1976 self.unsupported("Hints are not supported") 1977 return "" 1978 1979 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1980 1981 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1982 using = self.sql(expression, "using") 1983 using = f" USING {using}" if using else "" 1984 columns = self.expressions(expression, key="columns", flat=True) 1985 columns = f"({columns})" if columns else "" 1986 partition_by = self.expressions(expression, key="partition_by", flat=True) 1987 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1988 where = self.sql(expression, "where") 1989 include = self.expressions(expression, key="include", flat=True) 1990 if include: 1991 include = f" INCLUDE ({include})" 1992 with_storage = self.expressions(expression, key="with_storage", flat=True) 1993 with_storage = f" WITH ({with_storage})" if with_storage else "" 1994 tablespace = self.sql(expression, "tablespace") 1995 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1996 on = self.sql(expression, "on") 1997 on = f" ON {on}" if on else "" 1998 1999 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2000 2001 def index_sql(self, expression: exp.Index) -> str: 2002 unique = "UNIQUE " if expression.args.get("unique") else "" 2003 primary = "PRIMARY " if expression.args.get("primary") else "" 2004 amp = "AMP " if expression.args.get("amp") else "" 2005 name = self.sql(expression, "this") 2006 name = f"{name} " if name else "" 2007 table = self.sql(expression, "table") 2008 table = f"{self.INDEX_ON} {table}" if table else "" 2009 2010 index = "INDEX " if not table else "" 2011 2012 params = self.sql(expression, "params") 2013 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2014 2015 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2016 this = expression.this 2017 if this and this.is_string: 2018 resolved = maybe_parse(this.name).sql(self.dialect) 2019 if "expressions" in expression.args: 2020 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2021 # We can't safely emit the call to other dialects since name/arg semantics may differ 2022 self.unsupported( 2023 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2024 ) 2025 return resolved 2026 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2027 return self.func("IDENTIFIER", this) 2028 2029 def identifier_sql(self, expression: exp.Identifier) -> str: 2030 text = expression.name 2031 lower = text.lower() 2032 quoted = expression.quoted 2033 text = lower if self.normalize and not quoted else text 2034 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2035 if ( 2036 quoted 2037 or self.dialect.can_quote(expression, self.identify) 2038 or lower in self.RESERVED_KEYWORDS 2039 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2040 ): 2041 text = ( 2042 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2043 ) 2044 return text 2045 2046 def hex_sql(self, expression: exp.Hex) -> str: 2047 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2048 if self.dialect.HEX_LOWERCASE: 2049 text = self.func("LOWER", text) 2050 2051 return text 2052 2053 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2054 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2055 if not self.dialect.HEX_LOWERCASE: 2056 text = self.func("LOWER", text) 2057 return text 2058 2059 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2060 input_format = self.sql(expression, "input_format") 2061 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2062 output_format = self.sql(expression, "output_format") 2063 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2064 return self.sep().join((input_format, output_format)) 2065 2066 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2067 string = self.sql(exp.Literal.string(expression.name)) 2068 return f"{prefix}{string}" 2069 2070 def partition_sql(self, expression: exp.Partition) -> str: 2071 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2072 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2073 2074 def properties_sql(self, expression: exp.Properties) -> str: 2075 root_properties = [] 2076 with_properties = [] 2077 2078 for p in expression.expressions: 2079 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2080 if p_loc == exp.Properties.Location.POST_WITH: 2081 with_properties.append(p) 2082 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2083 root_properties.append(p) 2084 2085 root_props_ast = exp.Properties(expressions=root_properties) 2086 root_props_ast.parent = expression.parent 2087 2088 with_props_ast = exp.Properties(expressions=with_properties) 2089 with_props_ast.parent = expression.parent 2090 2091 root_props = self.root_properties(root_props_ast) 2092 with_props = self.with_properties(with_props_ast) 2093 2094 if root_props and with_props and not self.pretty: 2095 with_props = " " + with_props 2096 2097 return root_props + with_props 2098 2099 def root_properties(self, properties: exp.Properties) -> str: 2100 if properties.expressions: 2101 return self.expressions(properties, indent=False, sep=" ") 2102 return "" 2103 2104 def properties( 2105 self, 2106 properties: exp.Properties, 2107 prefix: str = "", 2108 sep: str = ", ", 2109 suffix: str = "", 2110 wrapped: bool = True, 2111 ) -> str: 2112 if properties.expressions: 2113 expressions = self.expressions(properties, sep=sep, indent=False) 2114 if expressions: 2115 expressions = self.wrap(expressions) if wrapped else expressions 2116 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2117 return "" 2118 2119 def with_properties(self, properties: exp.Properties) -> str: 2120 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2121 2122 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2123 properties_locs = defaultdict(list) 2124 for p in properties.expressions: 2125 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2126 if p_loc != exp.Properties.Location.UNSUPPORTED: 2127 properties_locs[p_loc].append(p) 2128 else: 2129 self.unsupported(f"Unsupported property {p.key}") 2130 2131 return properties_locs 2132 2133 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2134 if isinstance(expression.this, exp.Dot): 2135 return self.sql(expression, "this") 2136 return f"'{expression.name}'" if string_key else expression.name 2137 2138 def property_sql(self, expression: exp.Property) -> str: 2139 property_cls = expression.__class__ 2140 if property_cls == exp.Property: 2141 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2142 2143 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2144 if not property_name: 2145 self.unsupported(f"Unsupported property {expression.key}") 2146 2147 return f"{property_name}={self.sql(expression, 'this')}" 2148 2149 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2150 return f"UUID {self.sql(expression, 'this')}" 2151 2152 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2153 if self.SUPPORTS_CREATE_TABLE_LIKE: 2154 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2155 options = f" {options}" if options else "" 2156 2157 like = f"LIKE {self.sql(expression, 'this')}{options}" 2158 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2159 like = f"({like})" 2160 2161 return like 2162 2163 if expression.expressions: 2164 self.unsupported("Transpilation of LIKE property options is unsupported") 2165 2166 select = exp.select("*").from_(expression.this).limit(0) 2167 return f"AS {self.sql(select)}" 2168 2169 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2170 no = "NO " if expression.args.get("no") else "" 2171 protection = " PROTECTION" if expression.args.get("protection") else "" 2172 return f"{no}FALLBACK{protection}" 2173 2174 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2175 no = "NO " if expression.args.get("no") else "" 2176 local = expression.args.get("local") 2177 local = f"{local} " if local else "" 2178 dual = "DUAL " if expression.args.get("dual") else "" 2179 before = "BEFORE " if expression.args.get("before") else "" 2180 after = "AFTER " if expression.args.get("after") else "" 2181 return f"{no}{local}{dual}{before}{after}JOURNAL" 2182 2183 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2184 freespace = self.sql(expression, "this") 2185 percent = " PERCENT" if expression.args.get("percent") else "" 2186 return f"FREESPACE={freespace}{percent}" 2187 2188 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2189 if expression.args.get("default"): 2190 property = "DEFAULT" 2191 elif expression.args.get("on"): 2192 property = "ON" 2193 else: 2194 property = "OFF" 2195 return f"CHECKSUM={property}" 2196 2197 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2198 if expression.args.get("no"): 2199 return "NO MERGEBLOCKRATIO" 2200 if expression.args.get("default"): 2201 return "DEFAULT MERGEBLOCKRATIO" 2202 2203 percent = " PERCENT" if expression.args.get("percent") else "" 2204 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2205 2206 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2207 expressions = self.expressions(expression, flat=True) 2208 expressions = f"({expressions})" if expressions else "" 2209 return f"USING {self.sql(expression, 'this')}{expressions}" 2210 2211 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2212 default = expression.args.get("default") 2213 minimum = expression.args.get("minimum") 2214 maximum = expression.args.get("maximum") 2215 if default or minimum or maximum: 2216 if default: 2217 prop = "DEFAULT" 2218 elif minimum: 2219 prop = "MINIMUM" 2220 else: 2221 prop = "MAXIMUM" 2222 return f"{prop} DATABLOCKSIZE" 2223 units = expression.args.get("units") 2224 units = f" {units}" if units else "" 2225 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2226 2227 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2228 autotemp = expression.args.get("autotemp") 2229 always = expression.args.get("always") 2230 default = expression.args.get("default") 2231 manual = expression.args.get("manual") 2232 never = expression.args.get("never") 2233 2234 if autotemp is not None: 2235 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2236 elif always: 2237 prop = "ALWAYS" 2238 elif default: 2239 prop = "DEFAULT" 2240 elif manual: 2241 prop = "MANUAL" 2242 elif never: 2243 prop = "NEVER" 2244 return f"BLOCKCOMPRESSION={prop}" 2245 2246 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2247 no = expression.args.get("no") 2248 no = " NO" if no else "" 2249 concurrent = expression.args.get("concurrent") 2250 concurrent = " CONCURRENT" if concurrent else "" 2251 target = self.sql(expression, "target") 2252 target = f" {target}" if target else "" 2253 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2254 2255 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2256 if isinstance(expression.this, list): 2257 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2258 if expression.this: 2259 modulus = self.sql(expression, "this") 2260 remainder = self.sql(expression, "expression") 2261 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2262 2263 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2264 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2265 return f"FROM ({from_expressions}) TO ({to_expressions})" 2266 2267 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2268 this = self.sql(expression, "this") 2269 2270 for_values_or_default = expression.expression 2271 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2272 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2273 else: 2274 for_values_or_default = " DEFAULT" 2275 2276 return f"PARTITION OF {this}{for_values_or_default}" 2277 2278 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2279 kind = expression.args.get("kind") 2280 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2281 for_or_in = expression.args.get("for_or_in") 2282 for_or_in = f" {for_or_in}" if for_or_in else "" 2283 lock_type = expression.args.get("lock_type") 2284 override = " OVERRIDE" if expression.args.get("override") else "" 2285 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2286 2287 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2288 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2289 statistics = expression.args.get("statistics") 2290 statistics_sql = "" 2291 if statistics is not None: 2292 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2293 return f"{data_sql}{statistics_sql}" 2294 2295 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2296 this = self.sql(expression, "this") 2297 this = f"HISTORY_TABLE={this}" if this else "" 2298 data_consistency: str | None = self.sql(expression, "data_consistency") 2299 data_consistency = ( 2300 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2301 ) 2302 retention_period: str | None = self.sql(expression, "retention_period") 2303 retention_period = ( 2304 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2305 ) 2306 2307 if this: 2308 on_sql = self.func("ON", this, data_consistency, retention_period) 2309 else: 2310 on_sql = "ON" if expression.args.get("on") else "OFF" 2311 2312 sql = f"SYSTEM_VERSIONING={on_sql}" 2313 2314 return f"WITH({sql})" if expression.args.get("with_") else sql 2315 2316 def insert_sql(self, expression: exp.Insert) -> str: 2317 hint = self.sql(expression, "hint") 2318 overwrite = expression.args.get("overwrite") 2319 2320 if isinstance(expression.this, exp.Directory): 2321 this = " OVERWRITE" if overwrite else " INTO" 2322 else: 2323 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2324 2325 stored = self.sql(expression, "stored") 2326 stored = f" {stored}" if stored else "" 2327 alternative = expression.args.get("alternative") 2328 alternative = f" OR {alternative}" if alternative else "" 2329 ignore = " IGNORE" if expression.args.get("ignore") else "" 2330 is_function = expression.args.get("is_function") 2331 if is_function: 2332 this = f"{this} FUNCTION" 2333 this = f"{this} {self.sql(expression, 'this')}" 2334 2335 exists = " IF EXISTS" if expression.args.get("exists") else "" 2336 where = self.sql(expression, "where") 2337 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2338 using = self.expressions(expression, key="using", flat=True) 2339 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2340 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2341 on_conflict = self.sql(expression, "conflict") 2342 on_conflict = f" {on_conflict}" if on_conflict else "" 2343 by_name = " BY NAME" if expression.args.get("by_name") else "" 2344 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2345 returning = self.sql(expression, "returning") 2346 2347 if self.RETURNING_END: 2348 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2349 else: 2350 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2351 2352 partition_by = self.sql(expression, "partition") 2353 partition_by = f" {partition_by}" if partition_by else "" 2354 settings = self.sql(expression, "settings") 2355 settings = f" {settings}" if settings else "" 2356 2357 source = self.sql(expression, "source") 2358 source = f"TABLE {source}" if source else "" 2359 2360 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2361 return self.prepend_ctes(expression, sql) 2362 2363 def introducer_sql(self, expression: exp.Introducer) -> str: 2364 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2365 2366 def kill_sql(self, expression: exp.Kill) -> str: 2367 kind = self.sql(expression, "kind") 2368 kind = f" {kind}" if kind else "" 2369 this = self.sql(expression, "this") 2370 this = f" {this}" if this else "" 2371 return f"KILL{kind}{this}" 2372 2373 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2374 return expression.name 2375 2376 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2377 return expression.name 2378 2379 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2380 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2381 2382 constraint = self.sql(expression, "constraint") 2383 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2384 2385 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2386 if conflict_keys: 2387 conflict_keys = f"({conflict_keys})" 2388 2389 index_predicate = self.sql(expression, "index_predicate") 2390 conflict_keys = f"{conflict_keys}{index_predicate} " 2391 2392 action = self.sql(expression, "action") 2393 2394 expressions = self.expressions(expression, flat=True) 2395 if expressions: 2396 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2397 expressions = f" {set_keyword}{expressions}" 2398 2399 where = self.sql(expression, "where") 2400 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2401 2402 def returning_sql(self, expression: exp.Returning) -> str: 2403 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2404 2405 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2406 fields = self.sql(expression, "fields") 2407 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2408 escaped = self.sql(expression, "escaped") 2409 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2410 items = self.sql(expression, "collection_items") 2411 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2412 keys = self.sql(expression, "map_keys") 2413 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2414 lines = self.sql(expression, "lines") 2415 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2416 null = self.sql(expression, "null") 2417 null = f" NULL DEFINED AS {null}" if null else "" 2418 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2419 2420 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2421 return f"WITH ({self.expressions(expression, flat=True)})" 2422 2423 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2424 this = f"{self.sql(expression, 'this')} INDEX" 2425 target = self.sql(expression, "target") 2426 target = f" FOR {target}" if target else "" 2427 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2428 2429 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2430 this = self.sql(expression, "this") 2431 kind = self.sql(expression, "kind") 2432 expr = self.sql(expression, "expression") 2433 return f"{this} ({kind} => {expr})" 2434 2435 def table_parts(self, expression: exp.Table) -> str: 2436 return ".".join( 2437 self.sql(part) 2438 for part in ( 2439 expression.args.get("catalog"), 2440 expression.args.get("db"), 2441 expression.args.get("this"), 2442 ) 2443 if part is not None 2444 ) 2445 2446 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2447 table = self.table_parts(expression) 2448 only = "ONLY " if expression.args.get("only") else "" 2449 partition = self.sql(expression, "partition") 2450 partition = f" {partition}" if partition else "" 2451 version = self.sql(expression, "version") 2452 version = f" {version}" if version else "" 2453 alias = self.sql(expression, "alias") 2454 alias = f"{sep}{alias}" if alias else "" 2455 2456 sample = self.sql(expression, "sample") 2457 post_alias = "" 2458 pre_alias = "" 2459 2460 if self.dialect.ALIAS_POST_TABLESAMPLE: 2461 pre_alias = sample 2462 else: 2463 post_alias = sample 2464 2465 if self.dialect.ALIAS_POST_VERSION: 2466 pre_alias = f"{pre_alias}{version}" 2467 else: 2468 post_alias = f"{post_alias}{version}" 2469 2470 hints = self.expressions(expression, key="hints", sep=" ") 2471 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2472 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2473 joins = self.indent( 2474 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2475 ) 2476 laterals = self.expressions(expression, key="laterals", sep="") 2477 2478 file_format = self.sql(expression, "format") 2479 pattern = self.sql(expression, "pattern") 2480 if file_format: 2481 pattern = f", PATTERN => {pattern}" if pattern else "" 2482 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2483 elif pattern: 2484 file_format = f" (PATTERN => {pattern})" 2485 2486 ordinality = expression.args.get("ordinality") or "" 2487 if ordinality: 2488 ordinality = f" WITH ORDINALITY{alias}" 2489 alias = "" 2490 2491 when = self.sql(expression, "when") 2492 if when: 2493 if self.HISTORICAL_DATA_POST_ALIAS: 2494 alias = f"{alias} {when}" 2495 else: 2496 table = f"{table} {when}" 2497 2498 changes = self.sql(expression, "changes") 2499 changes = f" {changes}" if changes else "" 2500 2501 rows_from = self.expressions(expression, key="rows_from") 2502 if rows_from: 2503 table = f"ROWS FROM {self.wrap(rows_from)}" 2504 2505 indexed = expression.args.get("indexed") 2506 if indexed is not None: 2507 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2508 else: 2509 indexed = "" 2510 2511 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2512 2513 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2514 table = self.func("TABLE", expression.this) 2515 alias = self.sql(expression, "alias") 2516 alias = f" AS {alias}" if alias else "" 2517 sample = self.sql(expression, "sample") 2518 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2519 joins = self.indent( 2520 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2521 ) 2522 return f"{table}{alias}{pivots}{sample}{joins}" 2523 2524 def tablesample_sql( 2525 self, 2526 expression: exp.TableSample, 2527 tablesample_keyword: str | None = None, 2528 ) -> str: 2529 method = self.sql(expression, "method") 2530 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2531 numerator = self.sql(expression, "bucket_numerator") 2532 denominator = self.sql(expression, "bucket_denominator") 2533 field = self.sql(expression, "bucket_field") 2534 field = f" ON {field}" if field else "" 2535 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2536 seed = self.sql(expression, "seed") 2537 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2538 2539 size = self.sql(expression, "size") 2540 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2541 size = f"{size} ROWS" 2542 2543 percent = self.sql(expression, "percent") 2544 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2545 percent = f"{percent} PERCENT" 2546 2547 expr = f"{bucket}{percent}{size}" 2548 if self.TABLESAMPLE_REQUIRES_PARENS: 2549 expr = f"({expr})" 2550 2551 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2552 2553 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2554 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2555 # the stored column name differs from the target dialect's natural output. 2556 columns = expression.args.get("columns") 2557 if not columns or len(expression.fields) != 1: 2558 return None 2559 2560 args = expression.args 2561 parser_cls = self.dialect.parser_class 2562 2563 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2564 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2565 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2566 2567 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2568 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2569 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2570 2571 if ( 2572 src_identify_pivot_strings == tgt_identify_pivot_strings 2573 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2574 and src_pivot_column_naming == tgt_pivot_column_naming 2575 ): 2576 return None 2577 2578 in_exprs = expression.fields[0].expressions 2579 step = len(columns) // len(in_exprs) 2580 2581 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2582 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2583 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2584 first_stored = columns[0].name 2585 2586 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2587 if not first_stored.startswith(first_base): 2588 return None 2589 2590 suffix = first_stored[len(first_base) :] 2591 2592 # Whether the target dialect would append an agg-name suffix for this pivot. 2593 # Spark single-agg uniquely drops the agg alias entirely. 2594 target_has_suffix = ( 2595 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2596 ) and any(a.alias for a in expression.expressions) 2597 source_has_suffix = suffix != "" 2598 2599 new_exprs: list[exp.Expression] = [] 2600 modified = False 2601 for val_idx, e in enumerate(in_exprs): 2602 if isinstance(e, exp.PivotAlias): 2603 new_exprs.append(e) 2604 continue 2605 2606 i = val_idx * step 2607 stored_full = columns[i].name 2608 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2609 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2610 2611 # Source had a suffix, but target won't apply one 2612 if source_has_suffix and not target_has_suffix: 2613 new_exprs.append( 2614 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2615 ) 2616 modified = True 2617 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2618 elif stored_value != target_value: 2619 new_exprs.append( 2620 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2621 ) 2622 modified = True 2623 else: 2624 new_exprs.append(e) 2625 2626 return new_exprs if modified else None 2627 2628 def pivot_sql(self, expression: exp.Pivot) -> str: 2629 expressions = self.expressions(expression, flat=True) 2630 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2631 2632 group = self.sql(expression, "group") 2633 2634 if expression.this: 2635 this = self.sql(expression, "this") 2636 if not expressions: 2637 sql = f"UNPIVOT {this}" 2638 else: 2639 on = f"{self.seg('ON')} {expressions}" 2640 into = self.sql(expression, "into") 2641 into = f"{self.seg('INTO')} {into}" if into else "" 2642 using = self.expressions(expression, key="using", flat=True) 2643 using = f"{self.seg('USING')} {using}" if using else "" 2644 sql = f"{direction} {this}{on}{into}{using}{group}" 2645 return self.prepend_ctes(expression, sql) 2646 2647 if not expression.unpivot: 2648 # Wrap IN-list values with explicit aliases where the target dialect would differ 2649 new_field_exprs = self._pivot_in_value_aliases(expression) 2650 if new_field_exprs is not None: 2651 expression.fields[0].set("expressions", new_field_exprs) 2652 2653 alias = self.sql(expression, "alias") 2654 if alias: 2655 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2656 2657 fields = self.expressions( 2658 expression, 2659 "fields", 2660 sep=" ", 2661 dynamic=True, 2662 new_line=True, 2663 skip_first=True, 2664 skip_last=True, 2665 ) 2666 2667 include_nulls = expression.args.get("include_nulls") 2668 if include_nulls is not None: 2669 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2670 else: 2671 nulls = "" 2672 2673 default_on_null = self.sql(expression, "default_on_null") 2674 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2675 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2676 return self.prepend_ctes(expression, sql) 2677 2678 def version_sql(self, expression: exp.Version) -> str: 2679 this = f"FOR {expression.name}" 2680 kind = expression.text("kind") 2681 expr = self.sql(expression, "expression") 2682 return f"{this} {kind} {expr}" 2683 2684 def tuple_sql(self, expression: exp.Tuple) -> str: 2685 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2686 2687 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2688 """ 2689 Returns (join_sql, from_sql) for UPDATE statements. 2690 - join_sql: placed after UPDATE table, before SET 2691 - from_sql: placed after SET clause (standard position) 2692 Dialects like MySQL need to convert FROM to JOIN syntax. 2693 """ 2694 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2695 return ("", self.sql(expression, "from_")) 2696 2697 # Qualify unqualified columns in SET clause with the target table 2698 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2699 target_table = expression.this 2700 if isinstance(target_table, exp.Table): 2701 target_name = exp.to_identifier(target_table.alias_or_name) 2702 for eq in expression.expressions: 2703 col = eq.this 2704 if isinstance(col, exp.Column) and not col.table: 2705 col.set("table", target_name) 2706 2707 table = from_expr.this 2708 if nested_joins := table.args.get("joins", []): 2709 table.set("joins", None) 2710 2711 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2712 for nested in nested_joins: 2713 if not nested.args.get("on") and not nested.args.get("using"): 2714 nested.set("on", exp.true()) 2715 join_sql += self.sql(nested) 2716 2717 return (join_sql, "") 2718 2719 def update_sql(self, expression: exp.Update) -> str: 2720 hint = self.sql(expression, "hint") 2721 this = self.sql(expression, "this") 2722 join_sql, from_sql = self._update_from_joins_sql(expression) 2723 set_sql = self.expressions(expression, flat=True) 2724 where_sql = self.sql(expression, "where") 2725 returning = self.sql(expression, "returning") 2726 order = self.sql(expression, "order") 2727 limit = self.sql(expression, "limit") 2728 if self.RETURNING_END: 2729 expression_sql = f"{from_sql}{where_sql}{returning}" 2730 else: 2731 expression_sql = f"{returning}{from_sql}{where_sql}" 2732 options = self.expressions(expression, key="options") 2733 options = f" OPTION({options})" if options else "" 2734 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2735 return self.prepend_ctes(expression, sql) 2736 2737 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2738 values_as_table = values_as_table and self.VALUES_AS_TABLE 2739 2740 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2741 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2742 args = self.expressions(expression) 2743 alias = self.sql(expression, "alias") 2744 values = f"VALUES{self.seg('')}{args}" 2745 values = ( 2746 f"({values})" 2747 if self.WRAP_DERIVED_VALUES 2748 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2749 else values 2750 ) 2751 values = self.query_modifiers(expression, values) 2752 return f"{values} AS {alias}" if alias else values 2753 2754 # Converts `VALUES...` expression into a series of select unions. 2755 alias_node = expression.args.get("alias") 2756 column_names = alias_node and alias_node.columns 2757 2758 selects: list[exp.Query] = [] 2759 2760 for i, tup in enumerate(expression.expressions): 2761 row = tup.expressions 2762 2763 if i == 0 and column_names: 2764 row = [ 2765 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2766 ] 2767 2768 selects.append(exp.Select(expressions=row)) 2769 2770 if self.pretty: 2771 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2772 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2773 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2774 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2775 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2776 2777 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2778 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2779 return f"({unions}){alias}" 2780 2781 def var_sql(self, expression: exp.Var) -> str: 2782 return self.sql(expression, "this") 2783 2784 @unsupported_args("expressions") 2785 def into_sql(self, expression: exp.Into) -> str: 2786 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2787 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2788 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2789 2790 def from_sql(self, expression: exp.From) -> str: 2791 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2792 2793 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2794 grouping_sets = self.expressions(expression, indent=False) 2795 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2796 2797 def rollup_sql(self, expression: exp.Rollup) -> str: 2798 expressions = self.expressions(expression, indent=False) 2799 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2800 2801 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2802 this = self.sql(expression, "this") 2803 2804 columns = self.expressions(expression, flat=True) 2805 2806 from_sql = self.sql(expression, "from_index") 2807 from_sql = f" FROM {from_sql}" if from_sql else "" 2808 2809 properties = expression.args.get("properties") 2810 properties_sql = ( 2811 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2812 ) 2813 2814 return f"{this}({columns}){from_sql}{properties_sql}" 2815 2816 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2817 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2818 2819 def cube_sql(self, expression: exp.Cube) -> str: 2820 expressions = self.expressions(expression, indent=False) 2821 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2822 2823 def group_sql(self, expression: exp.Group) -> str: 2824 group_by_all = expression.args.get("all") 2825 if group_by_all is True: 2826 modifier = " ALL" 2827 elif group_by_all is False: 2828 modifier = " DISTINCT" 2829 else: 2830 modifier = "" 2831 2832 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2833 2834 grouping_sets = self.expressions(expression, key="grouping_sets") 2835 cube = self.expressions(expression, key="cube") 2836 rollup = self.expressions(expression, key="rollup") 2837 2838 groupings = csv( 2839 self.seg(grouping_sets) if grouping_sets else "", 2840 self.seg(cube) if cube else "", 2841 self.seg(rollup) if rollup else "", 2842 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2843 sep=self.GROUPINGS_SEP, 2844 ) 2845 2846 if ( 2847 expression.expressions 2848 and groupings 2849 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2850 ): 2851 add_separator = True 2852 2853 if grouping_sets: 2854 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2855 add_separator = False 2856 else: 2857 self.unsupported( 2858 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2859 ) 2860 2861 if add_separator: 2862 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2863 2864 return f"{group_by}{groupings}" 2865 2866 def having_sql(self, expression: exp.Having) -> str: 2867 this = self.indent(self.sql(expression, "this")) 2868 return f"{self.seg('HAVING')}{self.sep()}{this}" 2869 2870 def connect_sql(self, expression: exp.Connect) -> str: 2871 start = self.sql(expression, "start") 2872 start = self.seg(f"START WITH {start}") if start else "" 2873 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2874 connect = self.sql(expression, "connect") 2875 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2876 return start + connect 2877 2878 def prior_sql(self, expression: exp.Prior) -> str: 2879 return f"PRIOR {self.sql(expression, 'this')}" 2880 2881 def join_sql(self, expression: exp.Join) -> str: 2882 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2883 side = None 2884 else: 2885 side = expression.side 2886 2887 op_sql = " ".join( 2888 op 2889 for op in ( 2890 expression.method, 2891 "GLOBAL" if expression.args.get("global_") else None, 2892 side, 2893 expression.kind, 2894 expression.hint if self.JOIN_HINTS else None, 2895 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2896 ) 2897 if op 2898 ) 2899 match_cond = self.sql(expression, "match_condition") 2900 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2901 on_sql = self.sql(expression, "on") 2902 using = expression.args.get("using") 2903 2904 if not on_sql and using: 2905 on_sql = csv(*(self.sql(column) for column in using)) 2906 2907 this = expression.this 2908 this_sql = self.sql(this) 2909 2910 exprs = self.expressions(expression) 2911 if exprs: 2912 this_sql = f"{this_sql},{self.seg(exprs)}" 2913 2914 if on_sql: 2915 on_sql = self.indent(on_sql, skip_first=True) 2916 space = self.seg(" " * self.pad) if self.pretty else " " 2917 if using: 2918 on_sql = f"{space}USING ({on_sql})" 2919 else: 2920 on_sql = f"{space}ON {on_sql}" 2921 elif not op_sql: 2922 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2923 return f" {this_sql}" 2924 2925 return f", {this_sql}" 2926 2927 if op_sql != "STRAIGHT_JOIN": 2928 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2929 2930 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2931 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2932 2933 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2934 args = self.expressions(expression, flat=True) 2935 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2936 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2937 2938 def lateral_op(self, expression: exp.Lateral) -> str: 2939 cross_apply = expression.args.get("cross_apply") 2940 2941 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2942 if cross_apply is True: 2943 op = "INNER JOIN " 2944 elif cross_apply is False: 2945 op = "LEFT JOIN " 2946 else: 2947 op = "" 2948 2949 return f"{op}LATERAL" 2950 2951 def lateral_sql(self, expression: exp.Lateral) -> str: 2952 this = self.sql(expression, "this") 2953 2954 if expression.args.get("view"): 2955 alias = expression.args["alias"] 2956 columns = self.expressions(alias, key="columns", flat=True) 2957 table = f" {alias.name}" if alias.name else "" 2958 columns = f" AS {columns}" if columns else "" 2959 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2960 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2961 2962 table_alias = expression.args.get("alias") 2963 offset = expression.this.args.get("offset") 2964 2965 if ( 2966 self.UNNEST_WITH_ORDINALITY 2967 and table_alias 2968 and isinstance(expression.this, exp.Unnest) 2969 and isinstance(offset, exp.Identifier) 2970 ): 2971 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 2972 table_alias = table_alias.copy() 2973 table_alias.append("columns", offset.copy()) 2974 2975 alias = self.sql(table_alias) 2976 alias = f" AS {alias}" if alias else "" 2977 2978 ordinality = expression.args.get("ordinality") or "" 2979 if ordinality: 2980 ordinality = f" WITH ORDINALITY{alias}" 2981 alias = "" 2982 2983 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2984 2985 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2986 this = self.sql(expression, "this") 2987 2988 args = [ 2989 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2990 for e in (expression.args.get(k) for k in ("offset", "expression")) 2991 if e 2992 ] 2993 2994 args_sql = ", ".join(self.sql(e) for e in args) 2995 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2996 expressions = self.expressions(expression, flat=True) 2997 limit_options = self.sql(expression, "limit_options") 2998 expressions = f" BY {expressions}" if expressions else "" 2999 3000 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3001 3002 def offset_sql(self, expression: exp.Offset) -> str: 3003 this = self.sql(expression, "this") 3004 value = expression.expression 3005 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3006 expressions = self.expressions(expression, flat=True) 3007 expressions = f" BY {expressions}" if expressions else "" 3008 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3009 3010 def setitem_sql(self, expression: exp.SetItem) -> str: 3011 kind = self.sql(expression, "kind") 3012 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3013 kind = "" 3014 else: 3015 kind = f"{kind} " if kind else "" 3016 this = self.sql(expression, "this") 3017 expressions = self.expressions(expression) 3018 collate = self.sql(expression, "collate") 3019 collate = f" COLLATE {collate}" if collate else "" 3020 global_ = "GLOBAL " if expression.args.get("global_") else "" 3021 return f"{global_}{kind}{this}{expressions}{collate}" 3022 3023 def set_sql(self, expression: exp.Set) -> str: 3024 expressions = f" {self.expressions(expression, flat=True)}" 3025 tag = " TAG" if expression.args.get("tag") else "" 3026 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3027 3028 def queryband_sql(self, expression: exp.QueryBand) -> str: 3029 this = self.sql(expression, "this") 3030 update = " UPDATE" if expression.args.get("update") else "" 3031 scope = self.sql(expression, "scope") 3032 scope = f" FOR {scope}" if scope else "" 3033 3034 return f"QUERY_BAND = {this}{update}{scope}" 3035 3036 def pragma_sql(self, expression: exp.Pragma) -> str: 3037 return f"PRAGMA {self.sql(expression, 'this')}" 3038 3039 def lock_sql(self, expression: exp.Lock) -> str: 3040 if not self.LOCKING_READS_SUPPORTED: 3041 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3042 return "" 3043 3044 update = expression.args["update"] 3045 key = expression.args.get("key") 3046 if update: 3047 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3048 else: 3049 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3050 expressions = self.expressions(expression, flat=True) 3051 expressions = f" OF {expressions}" if expressions else "" 3052 wait = expression.args.get("wait") 3053 3054 if wait is not None: 3055 if isinstance(wait, exp.Literal): 3056 wait = f" WAIT {self.sql(wait)}" 3057 else: 3058 wait = " NOWAIT" if wait else " SKIP LOCKED" 3059 3060 return f"{lock_type}{expressions}{wait or ''}" 3061 3062 def literal_sql(self, expression: exp.Literal) -> str: 3063 text = expression.this or "" 3064 if expression.is_string: 3065 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3066 return text 3067 3068 def escape_str( 3069 self, 3070 text: str, 3071 escape_backslash: bool = True, 3072 delimiter: str | None = None, 3073 escaped_delimiter: str | None = None, 3074 is_byte_string: bool = False, 3075 ) -> str: 3076 if is_byte_string: 3077 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3078 else: 3079 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3080 3081 if supports_escape_sequences: 3082 text = "".join( 3083 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3084 for ch in text 3085 ) 3086 3087 delimiter = delimiter or self.dialect.QUOTE_END 3088 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3089 3090 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3091 3092 def loaddata_sql(self, expression: exp.LoadData) -> str: 3093 is_overwrite = expression.args.get("overwrite") 3094 overwrite = " OVERWRITE" if is_overwrite else "" 3095 this = self.sql(expression, "this") 3096 3097 files = expression.args.get("files") 3098 if files: 3099 files_sql = self.expressions(files, flat=True) 3100 files_sql = f"FILES{self.wrap(files_sql)}" 3101 if is_overwrite: 3102 this = f" {this}" 3103 elif expression.args.get("temp"): 3104 this = f" INTO TEMP TABLE {this}" 3105 else: 3106 this = f" INTO TABLE {this}" 3107 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3108 3109 local = " LOCAL" if expression.args.get("local") else "" 3110 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3111 this = f" INTO TABLE {this}" 3112 partition = self.sql(expression, "partition") 3113 partition = f" {partition}" if partition else "" 3114 input_format = self.sql(expression, "input_format") 3115 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3116 serde = self.sql(expression, "serde") 3117 serde = f" SERDE {serde}" if serde else "" 3118 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3119 3120 def null_sql(self, *_) -> str: 3121 return "NULL" 3122 3123 def boolean_sql(self, expression: exp.Boolean) -> str: 3124 return "TRUE" if expression.this else "FALSE" 3125 3126 def booland_sql(self, expression: exp.Booland) -> str: 3127 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3128 3129 def boolor_sql(self, expression: exp.Boolor) -> str: 3130 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3131 3132 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3133 this = self.sql(expression, "this") 3134 this = f"{this} " if this else this 3135 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3136 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3137 3138 def withfill_sql(self, expression: exp.WithFill) -> str: 3139 from_sql = self.sql(expression, "from_") 3140 from_sql = f" FROM {from_sql}" if from_sql else "" 3141 to_sql = self.sql(expression, "to") 3142 to_sql = f" TO {to_sql}" if to_sql else "" 3143 step_sql = self.sql(expression, "step") 3144 step_sql = f" STEP {step_sql}" if step_sql else "" 3145 interpolated_values = [ 3146 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3147 if isinstance(e, exp.Alias) 3148 else self.sql(e, "this") 3149 for e in expression.args.get("interpolate") or [] 3150 ] 3151 interpolate = ( 3152 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3153 ) 3154 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3155 3156 def cluster_sql(self, expression: exp.Cluster) -> str: 3157 return self.op_expressions("CLUSTER BY", expression) 3158 3159 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3160 if expression.this: 3161 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3162 return "" 3163 expressions = self.expressions(expression, flat=True) 3164 return f"CLUSTER BY ({expressions})" 3165 3166 def distribute_sql(self, expression: exp.Distribute) -> str: 3167 return self.op_expressions("DISTRIBUTE BY", expression) 3168 3169 def sort_sql(self, expression: exp.Sort) -> str: 3170 return self.op_expressions("SORT BY", expression) 3171 3172 def _resolve_ordered_for_null_ordering_simulation( 3173 self, expression: exp.Ordered 3174 ) -> exp.Expr | None: 3175 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3176 3177 Returns the underlying expression of the uniquely-matching projection 3178 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3179 simulation, since the CASE is evaluated in FROM-clause scope rather 3180 than alias scope (MySQL error 1052). Returns None if no safe 3181 substitution applies, leaving the original behaviour unchanged. 3182 """ 3183 this = expression.this 3184 if not (isinstance(this, exp.Column) and not this.table): 3185 return None 3186 3187 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3188 if not isinstance(ancestor, exp.Select): 3189 return None 3190 3191 column_name = this.name 3192 matched: list[exp.Expr] = [ 3193 p.this if isinstance(p, exp.Alias) else p 3194 for p in ancestor.selects 3195 if p.output_name == column_name 3196 ] 3197 match = matched[0] if len(matched) == 1 else None 3198 3199 # Skip the substitution when it would be identical to the existing 3200 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3201 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3202 return None 3203 3204 return match 3205 3206 def ordered_sql(self, expression: exp.Ordered) -> str: 3207 desc = expression.args.get("desc") 3208 asc = not desc 3209 3210 nulls_first = expression.args.get("nulls_first") 3211 nulls_last = not nulls_first 3212 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3213 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3214 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3215 3216 this = self.sql(expression, "this") 3217 3218 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3219 nulls_sort_change = "" 3220 if nulls_first and ( 3221 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3222 ): 3223 nulls_sort_change = " NULLS FIRST" 3224 elif ( 3225 nulls_last 3226 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3227 and not nulls_are_last 3228 ): 3229 nulls_sort_change = " NULLS LAST" 3230 3231 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3232 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3233 window = expression.find_ancestor(exp.Window, exp.Select) 3234 3235 if isinstance(window, exp.Window): 3236 window_this = window.this 3237 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3238 window_this = window_this.this 3239 spec = window.args.get("spec") 3240 else: 3241 window_this = None 3242 spec = None 3243 3244 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3245 # without a spec or with a ROWS spec, but not with RANGE 3246 if not ( 3247 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3248 and (not spec or spec.text("kind").upper() == "ROWS") 3249 ): 3250 if window_this and spec: 3251 self.unsupported( 3252 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3253 ) 3254 nulls_sort_change = "" 3255 elif self.NULL_ORDERING_SUPPORTED is False and ( 3256 (asc and nulls_sort_change == " NULLS LAST") 3257 or (desc and nulls_sort_change == " NULLS FIRST") 3258 ): 3259 # BigQuery does not allow these ordering/nulls combinations when used under 3260 # an aggregation func or under a window containing one 3261 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3262 3263 if isinstance(ancestor, exp.Window): 3264 ancestor = ancestor.this 3265 if isinstance(ancestor, exp.AggFunc): 3266 self.unsupported( 3267 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3268 ) 3269 nulls_sort_change = "" 3270 elif self.NULL_ORDERING_SUPPORTED is None: 3271 if expression.this.is_int: 3272 self.unsupported( 3273 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3274 ) 3275 elif not isinstance(expression.this, exp.Rand): 3276 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3277 target = self.sql(resolved) if resolved is not None else this 3278 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3279 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3280 nulls_sort_change = "" 3281 3282 with_fill = self.sql(expression, "with_fill") 3283 with_fill = f" {with_fill}" if with_fill else "" 3284 3285 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3286 3287 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3288 window_frame = self.sql(expression, "window_frame") 3289 window_frame = f"{window_frame} " if window_frame else "" 3290 3291 this = self.sql(expression, "this") 3292 3293 return f"{window_frame}{this}" 3294 3295 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3296 partition = self.partition_by_sql(expression) 3297 order = self.sql(expression, "order") 3298 measures = self.expressions(expression, key="measures") 3299 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3300 rows = self.sql(expression, "rows") 3301 rows = self.seg(rows) if rows else "" 3302 after = self.sql(expression, "after") 3303 after = self.seg(after) if after else "" 3304 pattern = self.sql(expression, "pattern") 3305 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3306 definition_sqls = [ 3307 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3308 for definition in expression.args.get("define", []) 3309 ] 3310 definitions = self.expressions(sqls=definition_sqls) 3311 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3312 body = "".join( 3313 ( 3314 partition, 3315 order, 3316 measures, 3317 rows, 3318 after, 3319 pattern, 3320 define, 3321 ) 3322 ) 3323 alias = self.sql(expression, "alias") 3324 alias = f" {alias}" if alias else "" 3325 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3326 3327 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3328 limit = expression.args.get("limit") 3329 3330 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3331 count = limit.args.get("count") 3332 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3333 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3334 limit = exp.Limit( 3335 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3336 ) 3337 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3338 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3339 3340 return csv( 3341 *sqls, 3342 *[self.sql(join) for join in expression.args.get("joins") or []], 3343 self.sql(expression, "match"), 3344 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3345 self.sql(expression, "prewhere"), 3346 self.sql(expression, "where"), 3347 self.sql(expression, "connect"), 3348 self.sql(expression, "group"), 3349 self.sql(expression, "having"), 3350 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3351 self.sql(expression, "order"), 3352 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3353 *self.after_limit_modifiers(expression), 3354 self.sql(expression, "for_"), 3355 self.options_modifier(expression), 3356 sep="", 3357 ) 3358 3359 def options_modifier(self, expression: exp.Expr) -> str: 3360 options = self.expressions(expression, key="options") 3361 return f" {options}" if options else "" 3362 3363 def forclause_sql(self, expression: exp.ForClause) -> str: 3364 kind = expression.args["kind"] 3365 if kind == "BROWSE": 3366 return f"{self.sep()}FOR BROWSE" 3367 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3368 # the target dialect doesn't support QueryOption, so we drop the clause. 3369 options = self.expressions(expression, key="expressions") 3370 if not options: 3371 return "" 3372 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3373 3374 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3375 self.unsupported("Unsupported query option.") 3376 return "" 3377 3378 def offset_limit_modifiers( 3379 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3380 ) -> list[str]: 3381 return [ 3382 self.sql(expression, "offset") if fetch else self.sql(limit), 3383 self.sql(limit) if fetch else self.sql(expression, "offset"), 3384 ] 3385 3386 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3387 locks = self.expressions(expression, key="locks", sep=" ") 3388 locks = f" {locks}" if locks else "" 3389 return [locks, self.sql(expression, "sample")] 3390 3391 def select_sql(self, expression: exp.Select) -> str: 3392 into = expression.args.get("into") 3393 if not self.SUPPORTS_SELECT_INTO and into: 3394 into.pop() 3395 3396 hint = self.sql(expression, "hint") 3397 distinct = self.sql(expression, "distinct") 3398 distinct = f" {distinct}" if distinct else "" 3399 kind = self.sql(expression, "kind") 3400 3401 limit = expression.args.get("limit") 3402 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3403 top = self.limit_sql(limit, top=True) 3404 limit.pop() 3405 else: 3406 top = "" 3407 3408 expressions = self.expressions(expression) 3409 3410 if kind: 3411 if kind in self.SELECT_KINDS: 3412 kind = f" AS {kind}" 3413 else: 3414 if kind == "STRUCT": 3415 expressions = self.expressions( 3416 sqls=[ 3417 self.sql( 3418 exp.Struct( 3419 expressions=[ 3420 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3421 if isinstance(e, exp.Alias) 3422 else e 3423 for e in expression.expressions 3424 ] 3425 ) 3426 ) 3427 ] 3428 ) 3429 kind = "" 3430 3431 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3432 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3433 3434 exclude = expression.args.get("exclude") 3435 3436 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3437 exclude_sql = self.expressions(sqls=exclude, flat=True) 3438 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3439 3440 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3441 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3442 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3443 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3444 sql = self.query_modifiers( 3445 expression, 3446 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3447 self.sql(expression, "into", comment=False), 3448 self.sql(expression, "from_", comment=False), 3449 ) 3450 3451 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3452 if expression.args.get("with_"): 3453 sql = self.maybe_comment(sql, expression) 3454 expression.pop_comments() 3455 3456 sql = self.prepend_ctes(expression, sql) 3457 3458 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3459 expression.set("exclude", None) 3460 subquery = expression.subquery(copy=False) 3461 star = exp.Star(except_=exclude) 3462 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3463 3464 if not self.SUPPORTS_SELECT_INTO and into: 3465 if into.args.get("temporary"): 3466 table_kind = " TEMPORARY" 3467 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3468 table_kind = " UNLOGGED" 3469 else: 3470 table_kind = "" 3471 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3472 3473 return sql 3474 3475 def schema_sql(self, expression: exp.Schema) -> str: 3476 this = self.sql(expression, "this") 3477 sql = self.schema_columns_sql(expression) 3478 return f"{this} {sql}" if this and sql else this or sql 3479 3480 def schema_columns_sql(self, expression: exp.Expr) -> str: 3481 if expression.expressions: 3482 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3483 return "" 3484 3485 def star_sql(self, expression: exp.Star) -> str: 3486 except_ = self.expressions(expression, key="except_", flat=True) 3487 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3488 replace = self.expressions(expression, key="replace", flat=True) 3489 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3490 rename = self.expressions(expression, key="rename", flat=True) 3491 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3492 ilike = self.sql(expression, "ilike") 3493 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3494 return f"*{ilike}{except_}{replace}{rename}" 3495 3496 def parameter_sql(self, expression: exp.Parameter) -> str: 3497 this = self.sql(expression, "this") 3498 return f"{self.PARAMETER_TOKEN}{this}" 3499 3500 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3501 this = self.sql(expression, "this") 3502 kind = expression.text("kind") 3503 if kind: 3504 kind = f"{kind}." 3505 return f"@@{kind}{this}" 3506 3507 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3508 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3509 3510 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3511 alias = self.sql(expression, "alias") 3512 alias = f"{sep}{alias}" if alias else "" 3513 sample = self.sql(expression, "sample") 3514 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3515 alias = f"{sample}{alias}" 3516 3517 # Set to None so it's not generated again by self.query_modifiers() 3518 expression.set("sample", None) 3519 3520 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3521 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3522 return self.prepend_ctes(expression, sql) 3523 3524 def qualify_sql(self, expression: exp.Qualify) -> str: 3525 this = self.indent(self.sql(expression, "this")) 3526 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3527 3528 def unnest_sql(self, expression: exp.Unnest) -> str: 3529 args = self.expressions(expression, flat=True) 3530 3531 alias = expression.args.get("alias") 3532 offset = expression.args.get("offset") 3533 3534 if self.UNNEST_WITH_ORDINALITY: 3535 if alias and isinstance(offset, exp.Expr): 3536 alias.append("columns", offset) 3537 expression.set("offset", None) 3538 3539 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3540 columns = alias.columns 3541 alias = self.sql(columns[0]) if columns else "" 3542 else: 3543 alias = self.sql(alias) 3544 3545 alias = f" AS {alias}" if alias else alias 3546 if self.UNNEST_WITH_ORDINALITY: 3547 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3548 else: 3549 if isinstance(offset, exp.Expr): 3550 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3551 elif offset: 3552 suffix = f"{alias} WITH OFFSET" 3553 else: 3554 suffix = alias 3555 3556 return f"UNNEST({args}){suffix}" 3557 3558 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3559 return "" 3560 3561 def where_sql(self, expression: exp.Where) -> str: 3562 this = self.indent(self.sql(expression, "this")) 3563 return f"{self.seg('WHERE')}{self.sep()}{this}" 3564 3565 def window_sql(self, expression: exp.Window) -> str: 3566 this = self.sql(expression, "this") 3567 partition = self.partition_by_sql(expression) 3568 order = expression.args.get("order") 3569 order = self.order_sql(order, flat=True) if order else "" 3570 spec = self.sql(expression, "spec") 3571 alias = self.sql(expression, "alias") 3572 over = self.sql(expression, "over") or "OVER" 3573 3574 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3575 3576 first = expression.args.get("first") 3577 if first is None: 3578 first = "" 3579 else: 3580 first = "FIRST" if first else "LAST" 3581 3582 if not partition and not order and not spec and alias: 3583 return f"{this} {alias}" 3584 3585 args = self.format_args( 3586 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3587 ) 3588 return f"{this} ({args})" 3589 3590 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3591 partition = self.expressions(expression, key="partition_by", flat=True) 3592 return f"PARTITION BY {partition}" if partition else "" 3593 3594 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3595 kind = self.sql(expression, "kind") 3596 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3597 end = ( 3598 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3599 or "CURRENT ROW" 3600 ) 3601 3602 window_spec = f"{kind} BETWEEN {start} AND {end}" 3603 3604 exclude = self.sql(expression, "exclude") 3605 if exclude: 3606 if self.SUPPORTS_WINDOW_EXCLUDE: 3607 window_spec += f" EXCLUDE {exclude}" 3608 else: 3609 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3610 3611 return window_spec 3612 3613 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3614 this = self.sql(expression, "this") 3615 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3616 return f"{this} WITHIN GROUP ({expression_sql})" 3617 3618 def between_sql(self, expression: exp.Between) -> str: 3619 this = self.sql(expression, "this") 3620 low = self.sql(expression, "low") 3621 high = self.sql(expression, "high") 3622 symmetric = expression.args.get("symmetric") 3623 3624 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3625 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3626 3627 flag = ( 3628 " SYMMETRIC" 3629 if symmetric 3630 else " ASYMMETRIC" 3631 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3632 else "" # silently drop ASYMMETRIC – semantics identical 3633 ) 3634 return f"{this} BETWEEN{flag} {low} AND {high}" 3635 3636 def bracket_offset_expressions( 3637 self, expression: exp.Bracket, index_offset: int | None = None 3638 ) -> list[exp.Expr]: 3639 if expression.args.get("json_access"): 3640 return expression.expressions 3641 3642 return apply_index_offset( 3643 expression.this, 3644 expression.expressions, 3645 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3646 dialect=self.dialect, 3647 ) 3648 3649 def bracket_sql(self, expression: exp.Bracket) -> str: 3650 expressions = self.bracket_offset_expressions(expression) 3651 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3652 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3653 3654 def all_sql(self, expression: exp.All) -> str: 3655 this = self.sql(expression, "this") 3656 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3657 this = self.wrap(this) 3658 return f"ALL {this}" 3659 3660 def any_sql(self, expression: exp.Any) -> str: 3661 this = self.sql(expression, "this") 3662 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3663 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3664 this = self.wrap(this) 3665 return f"ANY{this}" 3666 return f"ANY {this}" 3667 3668 def exists_sql(self, expression: exp.Exists) -> str: 3669 return f"EXISTS{self.wrap(expression)}" 3670 3671 def case_sql(self, expression: exp.Case) -> str: 3672 this = self.sql(expression, "this") 3673 statements = [f"CASE {this}" if this else "CASE"] 3674 3675 for e in expression.args["ifs"]: 3676 statements.append(f"WHEN {self.sql(e, 'this')}") 3677 statements.append(f"THEN {self.sql(e, 'true')}") 3678 3679 default = self.sql(expression, "default") 3680 3681 if default: 3682 statements.append(f"ELSE {default}") 3683 3684 statements.append("END") 3685 3686 if self.pretty and self.too_wide(statements): 3687 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3688 3689 return " ".join(statements) 3690 3691 def constraint_sql(self, expression: exp.Constraint) -> str: 3692 this = self.sql(expression, "this") 3693 expressions = self.expressions(expression, flat=True) 3694 return f"CONSTRAINT {this} {expressions}" 3695 3696 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3697 order = expression.args.get("order") 3698 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3699 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3700 3701 def extract_sql(self, expression: exp.Extract) -> str: 3702 import sqlglot.dialects.dialect 3703 3704 this = ( 3705 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3706 if self.NORMALIZE_EXTRACT_DATE_PARTS 3707 else expression.this 3708 ) 3709 if self.EXTRACT_ALLOWS_QUOTES: 3710 this_sql = self.sql(this) 3711 elif isinstance(this, exp.WeekStart): 3712 this_sql = self.weekstart_name(this) 3713 else: 3714 this_sql = this.name 3715 expression_sql = self.sql(expression, "expression") 3716 3717 return f"EXTRACT({this_sql} FROM {expression_sql})" 3718 3719 def trim_sql(self, expression: exp.Trim) -> str: 3720 trim_type = self.sql(expression, "position") 3721 3722 if trim_type == "LEADING": 3723 func_name = "LTRIM" 3724 elif trim_type == "TRAILING": 3725 func_name = "RTRIM" 3726 else: 3727 func_name = "TRIM" 3728 3729 return self.func(func_name, expression.this, expression.expression) 3730 3731 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3732 args = expression.expressions 3733 if isinstance(expression, exp.ConcatWs): 3734 args = args[1:] # Skip the delimiter 3735 3736 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3737 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3738 3739 concat_coalesce = ( 3740 self.dialect.CONCAT_WS_COALESCE 3741 if isinstance(expression, exp.ConcatWs) 3742 else self.dialect.CONCAT_COALESCE 3743 ) 3744 3745 if not concat_coalesce and expression.args.get("coalesce"): 3746 3747 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3748 if not e.type: 3749 import sqlglot.optimizer.annotate_types 3750 3751 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3752 3753 if e.is_string or e.is_type(exp.DType.ARRAY): 3754 return e 3755 3756 return exp.func("coalesce", e, exp.Literal.string("")) 3757 3758 args = [_wrap_with_coalesce(e) for e in args] 3759 3760 return args 3761 3762 def concat_sql(self, expression: exp.Concat) -> str: 3763 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3764 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3765 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3766 # instead of coalescing them to empty string. 3767 import sqlglot.dialects.dialect 3768 3769 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3770 3771 expressions = self.convert_concat_args(expression) 3772 3773 # Some dialects don't allow a single-argument CONCAT call 3774 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3775 return self.sql(expressions[0]) 3776 3777 return self.func("CONCAT", *expressions) 3778 3779 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3780 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3781 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3782 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3783 all_args = expression.expressions 3784 expression.set("coalesce", True) 3785 return self.sql( 3786 exp.case() 3787 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3788 .else_(expression) 3789 ) 3790 3791 return self.func( 3792 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3793 ) 3794 3795 def check_sql(self, expression: exp.Check) -> str: 3796 this = self.sql(expression, key="this") 3797 return f"CHECK ({this})" 3798 3799 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3800 expressions = self.expressions(expression, flat=True) 3801 expressions = f" ({expressions})" if expressions else "" 3802 reference = self.sql(expression, "reference") 3803 reference = f" {reference}" if reference else "" 3804 delete = self.sql(expression, "delete") 3805 delete = f" ON DELETE {delete}" if delete else "" 3806 update = self.sql(expression, "update") 3807 update = f" ON UPDATE {update}" if update else "" 3808 options = self.expressions(expression, key="options", flat=True, sep=" ") 3809 options = f" {options}" if options else "" 3810 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3811 3812 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3813 this = self.sql(expression, "this") 3814 this = f" {this}" if this else "" 3815 expressions = self.expressions(expression, flat=True) 3816 include = self.sql(expression, "include") 3817 options = self.expressions(expression, key="options", flat=True, sep=" ") 3818 options = f" {options}" if options else "" 3819 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3820 3821 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3822 self.unsupported("TIMESERIES primary key columns are not supported") 3823 return self.sql(expression, "this") 3824 3825 def if_sql(self, expression: exp.If) -> str: 3826 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3827 3828 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3829 if self.MATCH_AGAINST_TABLE_PREFIX: 3830 expressions = [] 3831 for expr in expression.expressions: 3832 if isinstance(expr, exp.Table): 3833 expressions.append(f"TABLE {self.sql(expr)}") 3834 else: 3835 expressions.append(expr) 3836 else: 3837 expressions = expression.expressions 3838 3839 modifier = expression.args.get("modifier") 3840 modifier = f" {modifier}" if modifier else "" 3841 return ( 3842 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3843 ) 3844 3845 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3846 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3847 3848 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3849 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3850 3851 if self.QUOTE_JSON_PATH: 3852 path = self.escape_str(path) 3853 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3854 3855 return path 3856 3857 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3858 if isinstance(expression, exp.JSONPathPart): 3859 transform = self.TRANSFORMS.get(expression.__class__) 3860 if not callable(transform): 3861 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3862 return "" 3863 3864 return transform(self, expression) 3865 3866 if isinstance(expression, int): 3867 return str(expression) 3868 3869 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3870 escaped = expression.replace("'", "\\'") 3871 escaped = f"'{escaped}'" 3872 else: 3873 escaped = expression.replace('"', '\\"') 3874 escaped = f'"{escaped}"' 3875 3876 return escaped 3877 3878 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3879 return f"{self.sql(expression, 'this')} FORMAT JSON" 3880 3881 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3882 # Output the Teradata column FORMAT override. 3883 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3884 this = self.sql(expression, "this") 3885 fmt = self.sql(expression, "format") 3886 return f"{this} (FORMAT {fmt})" 3887 3888 def _jsonobject_sql( 3889 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3890 ) -> str: 3891 null_handling = expression.args.get("null_handling") 3892 null_handling = f" {null_handling}" if null_handling else "" 3893 3894 unique_keys = expression.args.get("unique_keys") 3895 if unique_keys is not None: 3896 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3897 else: 3898 unique_keys = "" 3899 3900 return_type = self.sql(expression, "return_type") 3901 return_type = f" RETURNING {return_type}" if return_type else "" 3902 encoding = self.sql(expression, "encoding") 3903 encoding = f" ENCODING {encoding}" if encoding else "" 3904 3905 if not name: 3906 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3907 3908 return self.func( 3909 name, 3910 *expression.expressions, 3911 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3912 ) 3913 3914 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3915 null_handling = expression.args.get("null_handling") 3916 null_handling = f" {null_handling}" if null_handling else "" 3917 return_type = self.sql(expression, "return_type") 3918 return_type = f" RETURNING {return_type}" if return_type else "" 3919 strict = " STRICT" if expression.args.get("strict") else "" 3920 return self.func( 3921 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3922 ) 3923 3924 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3925 this = self.sql(expression, "this") 3926 order = self.sql(expression, "order") 3927 null_handling = expression.args.get("null_handling") 3928 null_handling = f" {null_handling}" if null_handling else "" 3929 return_type = self.sql(expression, "return_type") 3930 return_type = f" RETURNING {return_type}" if return_type else "" 3931 strict = " STRICT" if expression.args.get("strict") else "" 3932 return self.func( 3933 "JSON_ARRAYAGG", 3934 this, 3935 suffix=f"{order}{null_handling}{return_type}{strict})", 3936 ) 3937 3938 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3939 path = self.sql(expression, "path") 3940 path = f" PATH {path}" if path else "" 3941 nested_schema = self.sql(expression, "nested_schema") 3942 3943 if nested_schema: 3944 return f"NESTED{path} {nested_schema}" 3945 3946 this = self.sql(expression, "this") 3947 kind = self.sql(expression, "kind") 3948 kind = f" {kind}" if kind else "" 3949 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3950 3951 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3952 return f"{this}{kind}{format_json}{path}{ordinality}" 3953 3954 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3955 return self.func("COLUMNS", *expression.expressions) 3956 3957 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3958 this = self.sql(expression, "this") 3959 path = self.sql(expression, "path") 3960 path = f", {path}" if path else "" 3961 error_handling = expression.args.get("error_handling") 3962 error_handling = f" {error_handling}" if error_handling else "" 3963 empty_handling = expression.args.get("empty_handling") 3964 empty_handling = f" {empty_handling}" if empty_handling else "" 3965 schema = self.sql(expression, "schema") 3966 return self.func( 3967 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3968 ) 3969 3970 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3971 this = self.sql(expression, "this") 3972 kind = self.sql(expression, "kind") 3973 path = self.sql(expression, "path") 3974 path = f" {path}" if path else "" 3975 as_json = " AS JSON" if expression.args.get("as_json") else "" 3976 return f"{this} {kind}{path}{as_json}" 3977 3978 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3979 this = self.sql(expression, "this") 3980 path = self.sql(expression, "path") 3981 path = f", {path}" if path else "" 3982 expressions = self.expressions(expression) 3983 with_ = ( 3984 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3985 if expressions 3986 else "" 3987 ) 3988 return f"OPENJSON({this}{path}){with_}" 3989 3990 def in_sql(self, expression: exp.In) -> str: 3991 query = expression.args.get("query") 3992 unnest = expression.args.get("unnest") 3993 field = expression.args.get("field") 3994 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3995 3996 if query: 3997 in_sql = self.sql(query) 3998 elif unnest: 3999 in_sql = self.in_unnest_op(unnest) 4000 elif field: 4001 in_sql = self.sql(field) 4002 else: 4003 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4004 4005 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4006 4007 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4008 return f"(SELECT {self.sql(unnest)})" 4009 4010 def interval_sql(self, expression: exp.Interval) -> str: 4011 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4012 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4013 exp.AutoRefreshProperty, 4014 ) 4015 interval_keyword = "INTERVAL" if include_keyword else "" 4016 unit_expression = expression.args.get("unit") 4017 unit = self.sql(unit_expression) if unit_expression else "" 4018 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4019 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4020 unit = f" {unit}" if unit else "" 4021 4022 if self.SINGLE_STRING_INTERVAL: 4023 this = expression.this.name if expression.this else "" 4024 if this: 4025 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4026 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4027 return f"{interval_keyword}'{this}'{unit}" 4028 return f"{interval_keyword}'{this}{unit}'" 4029 return f"{interval_keyword}{unit}" 4030 4031 this = self.sql(expression, "this") 4032 if this: 4033 if not include_keyword and expression.this.is_string: 4034 this = expression.this.name 4035 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4036 this = f"({this})" 4037 if include_keyword: 4038 this = f" {this}" 4039 4040 return f"{interval_keyword}{this}{unit}" 4041 4042 def return_sql(self, expression: exp.Return) -> str: 4043 return f"RETURN {self.sql(expression, 'this')}" 4044 4045 def reference_sql(self, expression: exp.Reference) -> str: 4046 this = self.sql(expression, "this") 4047 expressions = self.expressions(expression, flat=True) 4048 expressions = f"({expressions})" if expressions else "" 4049 options = self.expressions(expression, key="options", flat=True, sep=" ") 4050 options = f" {options}" if options else "" 4051 return f"REFERENCES {this}{expressions}{options}" 4052 4053 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4054 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4055 parent = expression.parent 4056 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4057 4058 return self.func( 4059 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4060 ) 4061 4062 def paren_sql(self, expression: exp.Paren) -> str: 4063 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4064 return f"({sql}{self.seg(')', sep='')}" 4065 4066 def neg_sql(self, expression: exp.Neg) -> str: 4067 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4068 this_sql = self.sql(expression, "this") 4069 sep = " " if this_sql[0] == "-" else "" 4070 return f"-{sep}{this_sql}" 4071 4072 def not_sql(self, expression: exp.Not) -> str: 4073 return f"NOT {self.sql(expression, 'this')}" 4074 4075 def alias_sql(self, expression: exp.Alias) -> str: 4076 alias = self.sql(expression, "alias") 4077 alias = f" AS {alias}" if alias else "" 4078 return f"{self.sql(expression, 'this')}{alias}" 4079 4080 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4081 alias = expression.args["alias"] 4082 4083 parent = expression.parent 4084 pivot = parent and parent.parent 4085 4086 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4087 identifier_alias = isinstance(alias, exp.Identifier) 4088 literal_alias = isinstance(alias, exp.Literal) 4089 4090 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4091 alias.replace(exp.Literal.string(alias.output_name)) 4092 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4093 alias.replace(exp.to_identifier(alias.output_name)) 4094 4095 return self.alias_sql(expression) 4096 4097 def aliases_sql(self, expression: exp.Aliases) -> str: 4098 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4099 4100 def atindex_sql(self, expression: exp.AtIndex) -> str: 4101 this = self.sql(expression, "this") 4102 index = self.sql(expression, "expression") 4103 return f"{this} AT {index}" 4104 4105 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4106 this = self.sql(expression, "this") 4107 zone = self.sql(expression, "zone") 4108 return f"{this} AT TIME ZONE {zone}" 4109 4110 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4111 this = self.sql(expression, "this") 4112 zone = self.sql(expression, "zone") 4113 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4114 4115 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4116 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4117 4118 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4119 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4120 4121 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4122 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4123 4124 def add_sql(self, expression: exp.Add) -> str: 4125 return self.binary(expression, "+") 4126 4127 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4128 return self.connector_sql(expression, "AND", stack) 4129 4130 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4131 return self.connector_sql(expression, "OR", stack) 4132 4133 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4134 return self.connector_sql(expression, "XOR", stack) 4135 4136 def connector_sql( 4137 self, 4138 expression: exp.Connector, 4139 op: str, 4140 stack: list[str | exp.Expr] | None = None, 4141 ) -> str: 4142 if stack is not None: 4143 stack.append(expression.right) 4144 if expression.comments and self.comments: 4145 op = self.maybe_comment(op, comments=expression.comments) 4146 4147 stack.extend((op, expression.left)) 4148 return op 4149 4150 stack = [expression] 4151 sqls: list[str] = [] 4152 ops = set() 4153 4154 while stack: 4155 node = stack.pop() 4156 if isinstance(node, exp.Connector): 4157 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4158 else: 4159 sql = self.sql(node) 4160 if sqls and sqls[-1] in ops: 4161 sqls[-1] += f" {sql}" 4162 else: 4163 sqls.append(sql) 4164 4165 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4166 return sep.join(sqls) 4167 4168 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4169 return self.binary(expression, "&") 4170 4171 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4172 return self.binary(expression, "<<") 4173 4174 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4175 return f"~{self.sql(expression, 'this')}" 4176 4177 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4178 return self.binary(expression, "|") 4179 4180 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4181 return self.binary(expression, ">>") 4182 4183 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4184 return self.binary(expression, "^") 4185 4186 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4187 format_sql = self.sql(expression, "format") 4188 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4189 to_sql = self.sql(expression, "to") 4190 to_sql = f" {to_sql}" if to_sql else "" 4191 action = self.sql(expression, "action") 4192 action = f" {action}" if action else "" 4193 default = self.sql(expression, "default") 4194 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4195 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4196 4197 # Base implementation that excludes safe, zone, and target_type metadata args 4198 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4199 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4200 4201 # Base implementation that excludes the safe and default_year metadata args 4202 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4203 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4204 4205 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4206 return self.func( 4207 "PARSE_DATETIME", 4208 expression.this, 4209 expression.args.get("format"), 4210 expression.args.get("zone"), 4211 ) 4212 4213 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4214 zone = self.sql(expression, "this") 4215 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4216 4217 def collate_sql(self, expression: exp.Collate) -> str: 4218 if self.COLLATE_IS_FUNC: 4219 return self.function_fallback_sql(expression) 4220 return self.binary(expression, "COLLATE") 4221 4222 def command_sql(self, expression: exp.Command) -> str: 4223 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4224 4225 def comment_sql(self, expression: exp.Comment) -> str: 4226 this = self.sql(expression, "this") 4227 kind = expression.args["kind"] 4228 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4229 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4230 expression_sql = self.sql(expression, "expression") 4231 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4232 4233 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4234 this = self.sql(expression, "this") 4235 delete = " DELETE" if expression.args.get("delete") else "" 4236 recompress = self.sql(expression, "recompress") 4237 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4238 to_disk = self.sql(expression, "to_disk") 4239 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4240 to_volume = self.sql(expression, "to_volume") 4241 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4242 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4243 4244 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4245 where = self.sql(expression, "where") 4246 group = self.sql(expression, "group") 4247 aggregates = self.expressions(expression, key="aggregates") 4248 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4249 4250 if not (where or group or aggregates) and len(expression.expressions) == 1: 4251 return f"TTL {self.expressions(expression, flat=True)}" 4252 4253 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4254 4255 def transaction_sql(self, expression: exp.Transaction) -> str: 4256 modes = self.expressions(expression, key="modes") 4257 modes = f" {modes}" if modes else "" 4258 return f"BEGIN{modes}" 4259 4260 def commit_sql(self, expression: exp.Commit) -> str: 4261 chain = expression.args.get("chain") 4262 if chain is not None: 4263 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4264 4265 return f"COMMIT{chain or ''}" 4266 4267 def rollback_sql(self, expression: exp.Rollback) -> str: 4268 savepoint = expression.args.get("savepoint") 4269 savepoint = f" TO {savepoint}" if savepoint else "" 4270 return f"ROLLBACK{savepoint}" 4271 4272 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4273 this = self.sql(expression, "this") 4274 4275 exists = "" 4276 if expression.args.get("exists"): 4277 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4278 exists = " IF EXISTS" 4279 else: 4280 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4281 4282 dtype = self.sql(expression, "dtype") 4283 if dtype: 4284 collate = self.sql(expression, "collate") 4285 collate = f" COLLATE {collate}" if collate else "" 4286 using = self.sql(expression, "using") 4287 using = f" USING {using}" if using else "" 4288 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4289 null_constraint = self._alter_column_null_constraint_sql(expression) 4290 4291 return ( 4292 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4293 f"{collate}{using}{null_constraint}" 4294 ) 4295 4296 default = self.sql(expression, "default") 4297 if default: 4298 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4299 4300 comment = self.sql(expression, "comment") 4301 if comment: 4302 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4303 4304 visible = expression.args.get("visible") 4305 if visible: 4306 return f"ALTER COLUMN{exists} {this} SET {visible}" 4307 4308 allow_null = expression.args.get("allow_null") 4309 drop = expression.args.get("drop") 4310 4311 if not drop and not allow_null: 4312 self.unsupported("Unsupported ALTER COLUMN syntax") 4313 4314 if allow_null is not None: 4315 keyword = "DROP" if drop else "SET" 4316 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4317 4318 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4319 4320 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4321 allow_null = expression.args.get("allow_null") 4322 if allow_null is None: 4323 return "" 4324 4325 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4326 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4327 return "" 4328 4329 return " NULL" if allow_null else " NOT NULL" 4330 4331 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4332 this = self.sql(expression, "this") 4333 rename_from = self.sql(expression, "rename_from") 4334 if rename_from: 4335 if not self.SUPPORTS_CHANGE_COLUMN: 4336 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4337 return f"CHANGE COLUMN {rename_from} {this}" 4338 if not self.SUPPORTS_MODIFY_COLUMN: 4339 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4340 return f"MODIFY COLUMN {this}" 4341 4342 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4343 this = self.sql(expression, "this") 4344 4345 visible = expression.args.get("visible") 4346 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4347 4348 return f"ALTER INDEX {this} {visible_sql}" 4349 4350 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4351 this = self.sql(expression, "this") 4352 if not isinstance(expression.this, exp.Var): 4353 this = f"KEY DISTKEY {this}" 4354 return f"ALTER DISTSTYLE {this}" 4355 4356 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4357 compound = " COMPOUND" if expression.args.get("compound") else "" 4358 this = self.sql(expression, "this") 4359 expressions = self.expressions(expression, flat=True) 4360 expressions = f"({expressions})" if expressions else "" 4361 return f"ALTER{compound} SORTKEY {this or expressions}" 4362 4363 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4364 if not self.RENAME_TABLE_WITH_DB: 4365 # Remove db from tables 4366 expression = expression.transform( 4367 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4368 ).assert_is(exp.AlterRename) 4369 this = self.sql(expression, "this") 4370 to_kw = " TO" if include_to else "" 4371 return f"RENAME{to_kw} {this}" 4372 4373 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4374 exists = " IF EXISTS" if expression.args.get("exists") else "" 4375 old_column = self.sql(expression, "this") 4376 new_column = self.sql(expression, "to") 4377 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4378 4379 def alterset_sql(self, expression: exp.AlterSet) -> str: 4380 exprs = self.expressions(expression, flat=True) 4381 if self.ALTER_SET_WRAPPED: 4382 exprs = f"({exprs})" 4383 4384 return f"SET {exprs}" 4385 4386 def alter_sql(self, expression: exp.Alter) -> str: 4387 actions = expression.args["actions"] 4388 4389 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4390 actions[0], exp.ColumnDef 4391 ): 4392 actions_sql = self.expressions(expression, key="actions", flat=True) 4393 actions_sql = f"ADD {actions_sql}" 4394 else: 4395 actions_list = [] 4396 for action in actions: 4397 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4398 action_sql = self.add_column_sql(action) 4399 else: 4400 action_sql = self.sql(action) 4401 if isinstance(action, exp.Query): 4402 action_sql = f"AS {action_sql}" 4403 4404 actions_list.append(action_sql) 4405 4406 actions_sql = self.format_args(*actions_list).lstrip("\n") 4407 4408 iceberg = ( 4409 "ICEBERG " 4410 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4411 else "" 4412 ) 4413 exists = " IF EXISTS" if expression.args.get("exists") else "" 4414 on_cluster = self.sql(expression, "cluster") 4415 on_cluster = f" {on_cluster}" if on_cluster else "" 4416 only = " ONLY" if expression.args.get("only") else "" 4417 options = self.expressions(expression, key="options") 4418 options = f", {options}" if options else "" 4419 kind = self.sql(expression, "kind") 4420 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4421 check = " WITH CHECK" if expression.args.get("check") else "" 4422 cascade = ( 4423 " CASCADE" 4424 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4425 else "" 4426 ) 4427 this = self.sql(expression, "this") 4428 this = f" {this}" if this else "" 4429 4430 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4431 4432 def altersession_sql(self, expression: exp.AlterSession) -> str: 4433 items_sql = self.expressions(expression, flat=True) 4434 keyword = "UNSET" if expression.args.get("unset") else "SET" 4435 return f"{keyword} {items_sql}" 4436 4437 def add_column_sql(self, expression: exp.Expr) -> str: 4438 sql = self.sql(expression) 4439 if isinstance(expression, exp.Schema): 4440 column_text = " COLUMNS" 4441 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4442 column_text = " COLUMN" 4443 else: 4444 column_text = "" 4445 4446 return f"ADD{column_text} {sql}" 4447 4448 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4449 expressions = self.expressions(expression) 4450 exists = " IF EXISTS " if expression.args.get("exists") else " " 4451 return f"DROP{exists}{expressions}" 4452 4453 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4454 return "DROP PRIMARY KEY" 4455 4456 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4457 return f"ADD {self.expressions(expression, indent=False)}" 4458 4459 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4460 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4461 location = self.sql(expression, "location") 4462 location = f" {location}" if location else "" 4463 return f"ADD {exists}{self.sql(expression.this)}{location}" 4464 4465 def distinct_sql(self, expression: exp.Distinct) -> str: 4466 this = self.expressions(expression, flat=True) 4467 4468 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4469 case = exp.case() 4470 for arg in expression.expressions: 4471 case = case.when(arg.is_(exp.null()), exp.null()) 4472 this = self.sql(case.else_(f"({this})")) 4473 4474 this = f" {this}" if this else "" 4475 4476 on = self.sql(expression, "on") 4477 on = f" ON {on}" if on else "" 4478 return f"DISTINCT{this}{on}" 4479 4480 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4481 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4482 4483 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4484 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4485 4486 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4487 this_sql = self.sql(expression, "this") 4488 expression_sql = self.sql(expression, "expression") 4489 kind = "MAX" if expression.args.get("max") else "MIN" 4490 return f"{this_sql} HAVING {kind} {expression_sql}" 4491 4492 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4493 return self.sql( 4494 exp.Cast( 4495 this=exp.Div(this=expression.this, expression=expression.expression), 4496 to=exp.DataType(this=exp.DType.INT), 4497 ) 4498 ) 4499 4500 def dpipe_sql(self, expression: exp.DPipe) -> str: 4501 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4502 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4503 return self.binary(expression, "||") 4504 4505 def div_sql(self, expression: exp.Div) -> str: 4506 l, r = expression.left, expression.right 4507 4508 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4509 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4510 4511 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4512 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4513 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4514 4515 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4516 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4517 return self.sql( 4518 exp.cast( 4519 l / r, 4520 to=exp.DType.BIGINT, 4521 ) 4522 ) 4523 4524 return self.binary(expression, "/") 4525 4526 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4527 n = exp._wrap(expression.this, exp.Binary) 4528 d = exp._wrap(expression.expression, exp.Binary) 4529 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4530 4531 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4532 return self.binary(expression, "OVERLAPS") 4533 4534 def distance_sql(self, expression: exp.Distance) -> str: 4535 return self.binary(expression, "<->") 4536 4537 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4538 return self.binary(expression, "<<->>") 4539 4540 def dot_sql(self, expression: exp.Dot) -> str: 4541 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4542 4543 def eq_sql(self, expression: exp.EQ) -> str: 4544 return self.binary(expression, "=") 4545 4546 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4547 return self.binary(expression, ":=") 4548 4549 def escape_sql(self, expression: exp.Escape) -> str: 4550 this = expression.this 4551 if ( 4552 isinstance(this, (exp.Like, exp.ILike)) 4553 and isinstance(this.expression, (exp.All, exp.Any)) 4554 and not self.SUPPORTS_LIKE_QUANTIFIERS 4555 ): 4556 return self._like_sql(this, escape=expression) 4557 return self.binary(expression, "ESCAPE") 4558 4559 def glob_sql(self, expression: exp.Glob) -> str: 4560 return self.binary(expression, "GLOB") 4561 4562 def gt_sql(self, expression: exp.GT) -> str: 4563 return self.binary(expression, ">") 4564 4565 def gte_sql(self, expression: exp.GTE) -> str: 4566 return self.binary(expression, ">=") 4567 4568 def is_sql(self, expression: exp.Is) -> str: 4569 negate = expression.args.get("negate") 4570 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4571 positive = bool(expression.expression.this) != bool(negate) 4572 return self.sql(expression.this if positive else exp.not_(expression.this)) 4573 return self.binary(expression, "IS NOT" if negate else "IS") 4574 4575 def _like_sql( 4576 self, 4577 expression: exp.Like | exp.ILike, 4578 escape: exp.Escape | None = None, 4579 ) -> str: 4580 this = expression.this 4581 rhs = expression.expression 4582 4583 if isinstance(expression, exp.Like): 4584 exp_class: type[exp.Like | exp.ILike] = exp.Like 4585 op = "LIKE" 4586 else: 4587 exp_class = exp.ILike 4588 op = "ILIKE" 4589 4590 if expression.args.get("negate"): 4591 op = f"NOT {op}" 4592 4593 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4594 exprs = rhs.this.unnest() 4595 4596 if isinstance(exprs, exp.Tuple): 4597 exprs = exprs.expressions 4598 else: 4599 exprs = [exprs] 4600 4601 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4602 4603 def _make_like(expr: exp.Expression) -> exp.Expression: 4604 like: exp.Expression = exp_class( 4605 this=this, expression=expr, negate=expression.args.get("negate") 4606 ) 4607 if escape: 4608 like = exp.Escape(this=like, expression=escape.expression.copy()) 4609 return like 4610 4611 like_expr: exp.Expr = _make_like(exprs[0]) 4612 for expr in exprs[1:]: 4613 like_expr = connective(like_expr, _make_like(expr), copy=False) 4614 4615 parent = escape.parent if escape else expression.parent 4616 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4617 parent, exp.Condition 4618 ): 4619 like_expr = exp.paren(like_expr, copy=False) 4620 4621 return self.sql(like_expr) 4622 4623 return self.binary(expression, op) 4624 4625 def like_sql(self, expression: exp.Like) -> str: 4626 return self._like_sql(expression) 4627 4628 def ilike_sql(self, expression: exp.ILike) -> str: 4629 return self._like_sql(expression) 4630 4631 def match_sql(self, expression: exp.Match) -> str: 4632 return self.binary(expression, "MATCH") 4633 4634 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4635 return self.binary(expression, "SIMILAR TO") 4636 4637 def lt_sql(self, expression: exp.LT) -> str: 4638 return self.binary(expression, "<") 4639 4640 def lte_sql(self, expression: exp.LTE) -> str: 4641 return self.binary(expression, "<=") 4642 4643 def mod_sql(self, expression: exp.Mod) -> str: 4644 this = self.sql(expression, "this") 4645 expr = self.sql(expression, "expression") 4646 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4647 4648 parent = expression.parent 4649 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4650 return f"({sql})" 4651 4652 return sql 4653 4654 def mul_sql(self, expression: exp.Mul) -> str: 4655 return self.binary(expression, "*") 4656 4657 def neq_sql(self, expression: exp.NEQ) -> str: 4658 return self.binary(expression, "<>") 4659 4660 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4661 return self.binary(expression, "IS NOT DISTINCT FROM") 4662 4663 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4664 return self.binary(expression, "IS DISTINCT FROM") 4665 4666 def sub_sql(self, expression: exp.Sub) -> str: 4667 return self.binary(expression, "-") 4668 4669 def trycast_sql(self, expression: exp.TryCast) -> str: 4670 return self.cast_sql(expression, safe_prefix="TRY_") 4671 4672 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4673 return self.cast_sql(expression) 4674 4675 def try_sql(self, expression: exp.Try) -> str: 4676 if not self.TRY_SUPPORTED: 4677 self.unsupported("Unsupported TRY function") 4678 return self.sql(expression, "this") 4679 4680 return self.func("TRY", expression.this) 4681 4682 def log_sql(self, expression: exp.Log) -> str: 4683 this = expression.this 4684 expr = expression.expression 4685 4686 if self.dialect.LOG_BASE_FIRST is False: 4687 this, expr = expr, this 4688 elif self.dialect.LOG_BASE_FIRST is None and expr: 4689 if this.name in ("2", "10"): 4690 return self.func(f"LOG{this.name}", expr) 4691 4692 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4693 4694 return self.func("LOG", this, expr) 4695 4696 def use_sql(self, expression: exp.Use) -> str: 4697 kind = self.sql(expression, "kind") 4698 kind = f" {kind}" if kind else "" 4699 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4700 this = f" {this}" if this else "" 4701 return f"USE{kind}{this}" 4702 4703 def binary(self, expression: exp.Binary, op: str) -> str: 4704 sqls: list[str] = [] 4705 stack: list[None | str | exp.Expr] = [expression] 4706 binary_type = type(expression) 4707 4708 while stack: 4709 node = stack.pop() 4710 4711 if type(node) is binary_type: 4712 op_func = node.args.get("operator") 4713 if op_func: 4714 op = f"OPERATOR({self.sql(op_func)})" 4715 4716 stack.append(node.args.get("expression")) 4717 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4718 stack.append(node.args.get("this")) 4719 else: 4720 sqls.append(self.sql(node)) 4721 4722 return "".join(sqls) 4723 4724 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4725 to_clause = self.sql(expression, "to") 4726 if to_clause: 4727 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4728 4729 return self.function_fallback_sql(expression) 4730 4731 def function_fallback_sql(self, expression: exp.Func) -> str: 4732 args = [] 4733 4734 for key in expression.arg_types: 4735 arg_value = expression.args.get(key) 4736 4737 if isinstance(arg_value, list): 4738 for value in arg_value: 4739 args.append(value) 4740 elif arg_value is not None: 4741 args.append(arg_value) 4742 4743 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4744 name = expression.meta_get("name") or expression.sql_name() 4745 else: 4746 name = expression.sql_name() 4747 4748 return self.func(name, *args) 4749 4750 def func( 4751 self, 4752 name: str, 4753 *args: t.Any, 4754 prefix: str = "(", 4755 suffix: str = ")", 4756 normalize: bool = True, 4757 ) -> str: 4758 name = self.normalize_func(name) if normalize else name 4759 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4760 4761 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4762 arg_sqls = tuple( 4763 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4764 ) 4765 if self.pretty and self.too_wide(arg_sqls): 4766 return self.indent( 4767 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4768 ) 4769 return sep.join(arg_sqls) 4770 4771 def too_wide(self, args: t.Iterable) -> bool: 4772 return sum(len(arg) for arg in args) > self.max_text_width 4773 4774 def format_time( 4775 self, 4776 expression: exp.Expr, 4777 inverse_time_mapping: dict[str, str] | None = None, 4778 inverse_time_trie: dict | None = None, 4779 ) -> str | None: 4780 return format_time( 4781 self.sql(expression, "format"), 4782 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4783 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4784 ) 4785 4786 def expressions( 4787 self, 4788 expression: exp.Expr | None = None, 4789 key: str | None = None, 4790 sqls: t.Collection[str | exp.Expr] | None = None, 4791 flat: bool = False, 4792 indent: bool = True, 4793 skip_first: bool = False, 4794 skip_last: bool = False, 4795 sep: str = ", ", 4796 prefix: str = "", 4797 dynamic: bool = False, 4798 new_line: bool = False, 4799 ) -> str: 4800 expressions = expression.args.get(key or "expressions") if expression else sqls 4801 4802 if not expressions: 4803 return "" 4804 4805 if flat: 4806 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4807 4808 num_sqls = len(expressions) 4809 result_sqls = [] 4810 4811 for i, e in enumerate(expressions): 4812 sql = self.sql(e, comment=False) 4813 if not sql: 4814 continue 4815 4816 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4817 4818 if self.pretty: 4819 if self.leading_comma: 4820 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4821 else: 4822 result_sqls.append( 4823 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4824 ) 4825 else: 4826 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4827 4828 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4829 if new_line: 4830 result_sqls.insert(0, "") 4831 result_sqls.append("") 4832 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4833 else: 4834 result_sql = "".join(result_sqls) 4835 4836 return ( 4837 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4838 if indent 4839 else result_sql 4840 ) 4841 4842 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4843 flat = flat or isinstance(expression.parent, exp.Properties) 4844 expressions_sql = self.expressions(expression, flat=flat) 4845 if flat: 4846 return f"{op} {expressions_sql}" 4847 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4848 4849 def naked_property(self, expression: exp.Property) -> str: 4850 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4851 if not property_name: 4852 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4853 return f"{property_name} {self.sql(expression, 'this')}" 4854 4855 def tag_sql(self, expression: exp.Tag) -> str: 4856 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4857 4858 def token_sql(self, token_type: TokenType) -> str: 4859 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4860 4861 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4862 this = self.sql(expression, "this") 4863 expressions = self.no_identify(self.expressions, expression) 4864 expressions = ( 4865 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4866 ) 4867 return f"{this}{expressions}" if expressions.strip() != "" else this 4868 4869 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4870 return self.expressions(expression, flat=True) 4871 4872 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4873 params = self.no_identify(self.expressions, expression, flat=True) 4874 body = self.sql(expression, "this") 4875 prefix = "TABLE " if expression.args.get("is_table") else "" 4876 return f"({params}) AS {prefix}{body}" 4877 4878 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4879 this = self.sql(expression, "this") 4880 expressions = self.expressions(expression, flat=True) 4881 return f"{this}({expressions})" 4882 4883 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4884 return self.binary(expression, "=>") 4885 4886 def when_sql(self, expression: exp.When) -> str: 4887 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4888 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4889 condition = self.sql(expression, "condition") 4890 condition = f" AND {condition}" if condition else "" 4891 4892 then_expression = expression.args.get("then") 4893 if isinstance(then_expression, exp.Insert): 4894 this = self.sql(then_expression, "this") 4895 this = f"INSERT {this}" if this else "INSERT" 4896 then = self.sql(then_expression, "expression") 4897 then = f"{this} VALUES {then}" if then else this 4898 elif isinstance(then_expression, exp.Update): 4899 if isinstance(then_expression.args.get("expressions"), exp.Star): 4900 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4901 else: 4902 expressions_sql = self.expressions(then_expression) 4903 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4904 else: 4905 then = self.sql(then_expression) 4906 4907 if isinstance(then_expression, (exp.Insert, exp.Update)): 4908 where = self.sql(then_expression, "where") 4909 if where and not self.SUPPORTS_MERGE_WHERE: 4910 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4911 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4912 where = "" 4913 then = f"{then}{where}" 4914 return f"WHEN {matched}{source}{condition} THEN {then}" 4915 4916 def whens_sql(self, expression: exp.Whens) -> str: 4917 return self.expressions(expression, sep=" ", indent=False) 4918 4919 def merge_sql(self, expression: exp.Merge) -> str: 4920 table = expression.this 4921 table_alias = "" 4922 4923 hints = table.args.get("hints") 4924 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4925 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4926 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4927 4928 this = self.sql(table) 4929 using = f"USING {self.sql(expression, 'using')}" 4930 whens = self.sql(expression, "whens") 4931 4932 on = self.sql(expression, "on") 4933 on = f"ON {on}" if on else "" 4934 4935 if not on: 4936 on = self.expressions(expression, key="using_cond") 4937 on = f"USING ({on})" if on else "" 4938 4939 returning = self.sql(expression, "returning") 4940 if returning: 4941 whens = f"{whens}{returning}" 4942 4943 sep = self.sep() 4944 4945 return self.prepend_ctes( 4946 expression, 4947 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4948 ) 4949 4950 @unsupported_args("format") 4951 def tochar_sql(self, expression: exp.ToChar) -> str: 4952 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4953 4954 @unsupported_args("default") 4955 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4956 if not self.SUPPORTS_TO_NUMBER: 4957 self.unsupported("Unsupported TO_NUMBER function") 4958 return self._tonumber_cast_sql(expression) 4959 4960 fmt = expression.args.get("format") 4961 if not fmt: 4962 self.unsupported("Conversion format is required for TO_NUMBER") 4963 return self._tonumber_cast_sql(expression) 4964 4965 return self.func("TO_NUMBER", expression.this, fmt) 4966 4967 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 4968 if expression.args.get("safe"): 4969 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 4970 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4971 4972 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4973 this = self.sql(expression, "this") 4974 kind = self.sql(expression, "kind") 4975 settings_sql = self.expressions(expression, key="settings", sep=" ") 4976 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4977 return f"{this}({kind}{args})" 4978 4979 def dictrange_sql(self, expression: exp.DictRange) -> str: 4980 this = self.sql(expression, "this") 4981 max = self.sql(expression, "max") 4982 min = self.sql(expression, "min") 4983 return f"{this}(MIN {min} MAX {max})" 4984 4985 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4986 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4987 4988 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4989 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4990 4991 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4992 def uniquekeyproperty_sql( 4993 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4994 ) -> str: 4995 return f"{prefix} ({self.expressions(expression, flat=True)})" 4996 4997 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4998 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4999 expressions = self.expressions(expression, flat=True) 5000 expressions = f" {self.wrap(expressions)}" if expressions else "" 5001 buckets = self.sql(expression, "buckets") 5002 kind = self.sql(expression, "kind") 5003 buckets = f" BUCKETS {buckets}" if buckets else "" 5004 order = self.sql(expression, "order") 5005 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5006 5007 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5008 return "" 5009 5010 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5011 expressions = self.expressions(expression, key="expressions", flat=True) 5012 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5013 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5014 buckets = self.sql(expression, "buckets") 5015 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5016 5017 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5018 this = self.sql(expression, "this") 5019 having = self.sql(expression, "having") 5020 5021 if having: 5022 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5023 5024 return self.func("ANY_VALUE", this) 5025 5026 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5027 transform = self.func("TRANSFORM", *expression.expressions) 5028 row_format_before = self.sql(expression, "row_format_before") 5029 row_format_before = f" {row_format_before}" if row_format_before else "" 5030 record_writer = self.sql(expression, "record_writer") 5031 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5032 using = f" USING {self.sql(expression, 'command_script')}" 5033 schema = self.sql(expression, "schema") 5034 schema = f" AS {schema}" if schema else "" 5035 row_format_after = self.sql(expression, "row_format_after") 5036 row_format_after = f" {row_format_after}" if row_format_after else "" 5037 record_reader = self.sql(expression, "record_reader") 5038 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5039 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5040 5041 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5042 key_block_size = self.sql(expression, "key_block_size") 5043 if key_block_size: 5044 return f"KEY_BLOCK_SIZE = {key_block_size}" 5045 5046 using = self.sql(expression, "using") 5047 if using: 5048 return f"USING {using}" 5049 5050 parser = self.sql(expression, "parser") 5051 if parser: 5052 return f"WITH PARSER {parser}" 5053 5054 comment = self.sql(expression, "comment") 5055 if comment: 5056 return f"COMMENT {comment}" 5057 5058 visible = expression.args.get("visible") 5059 if visible is not None: 5060 return "VISIBLE" if visible else "INVISIBLE" 5061 5062 engine_attr = self.sql(expression, "engine_attr") 5063 if engine_attr: 5064 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5065 5066 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5067 if secondary_engine_attr: 5068 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5069 5070 self.unsupported("Unsupported index constraint option.") 5071 return "" 5072 5073 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5074 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5075 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5076 5077 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5078 kind = self.sql(expression, "kind") 5079 kind = f"{kind} INDEX" if kind else "INDEX" 5080 this = self.sql(expression, "this") 5081 this = f" {this}" if this else "" 5082 index_type = self.sql(expression, "index_type") 5083 index_type = f" USING {index_type}" if index_type else "" 5084 expressions = self.expressions(expression, flat=True) 5085 expressions = f" ({expressions})" if expressions else "" 5086 options = self.expressions(expression, key="options", sep=" ") 5087 options = f" {options}" if options else "" 5088 return f"{kind}{this}{index_type}{expressions}{options}" 5089 5090 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5091 if self.NVL2_SUPPORTED: 5092 return self.function_fallback_sql(expression) 5093 5094 case = exp.Case().when( 5095 expression.this.is_(exp.null()).not_(copy=False), 5096 expression.args["true"], 5097 copy=False, 5098 ) 5099 else_cond = expression.args.get("false") 5100 if else_cond: 5101 case.else_(else_cond, copy=False) 5102 5103 return self.sql(case) 5104 5105 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5106 if expression.args.get("from_first") is False: 5107 self.unsupported("NTH_VALUE FROM LAST is not supported") 5108 5109 return self.function_fallback_sql(expression) 5110 5111 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5112 this = self.sql(expression, "this") 5113 expr = self.sql(expression, "expression") 5114 position = self.sql(expression, "position") 5115 position = f", {position}" if position else "" 5116 iterator = self.sql(expression, "iterator") 5117 condition = self.sql(expression, "condition") 5118 condition = f" IF {condition}" if condition else "" 5119 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5120 5121 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5122 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5123 5124 def opclass_sql(self, expression: exp.Opclass) -> str: 5125 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5126 5127 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5128 model = self.sql(expression, "this") 5129 model = f"MODEL {model}" 5130 expr = expression.expression 5131 if expr: 5132 expr_sql = self.sql(expression, "expression") 5133 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5134 else: 5135 expr_sql = None 5136 5137 parameters = self.sql(expression, "params_struct") or None 5138 5139 return self.func(name, model, expr_sql, parameters) 5140 5141 def predict_sql(self, expression: exp.Predict) -> str: 5142 return self._ml_sql(expression, "PREDICT") 5143 5144 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5145 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5146 return self._ml_sql(expression, name) 5147 5148 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5149 return self._ml_sql(expression, "GENERATE_TEXT") 5150 5151 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5152 return self._ml_sql(expression, "GENERATE_TABLE") 5153 5154 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5155 return self._ml_sql(expression, "GENERATE_BOOL") 5156 5157 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5158 return self._ml_sql(expression, "GENERATE_INT") 5159 5160 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5161 return self._ml_sql(expression, "GENERATE_DOUBLE") 5162 5163 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5164 return self._ml_sql(expression, "TRANSLATE") 5165 5166 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5167 return self._ml_sql(expression, "FORECAST") 5168 5169 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5170 this_sql = self.sql(expression, "this") 5171 if isinstance(expression.this, exp.Table): 5172 this_sql = f"TABLE {this_sql}" 5173 5174 return self.func( 5175 "FORECAST", 5176 this_sql, 5177 expression.args.get("data_col"), 5178 expression.args.get("timestamp_col"), 5179 expression.args.get("model"), 5180 expression.args.get("id_cols"), 5181 expression.args.get("horizon"), 5182 expression.args.get("forecast_end_timestamp"), 5183 expression.args.get("confidence_level"), 5184 expression.args.get("output_historical_time_series"), 5185 expression.args.get("context_window"), 5186 ) 5187 5188 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5189 this_sql = self.sql(expression, "this") 5190 if isinstance(expression.this, exp.Table): 5191 this_sql = f"TABLE {this_sql}" 5192 5193 return self.func( 5194 "FEATURES_AT_TIME", 5195 this_sql, 5196 expression.args.get("time"), 5197 expression.args.get("num_rows"), 5198 expression.args.get("ignore_feature_nulls"), 5199 ) 5200 5201 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5202 this_sql = self.sql(expression, "this") 5203 if isinstance(expression.this, exp.Table): 5204 this_sql = f"TABLE {this_sql}" 5205 5206 query_table = self.sql(expression, "query_table") 5207 if isinstance(expression.args["query_table"], exp.Table): 5208 query_table = f"TABLE {query_table}" 5209 5210 return self.func( 5211 "VECTOR_SEARCH", 5212 this_sql, 5213 expression.args.get("column_to_search"), 5214 query_table, 5215 expression.args.get("query_column_to_search"), 5216 expression.args.get("top_k"), 5217 expression.args.get("distance_type"), 5218 expression.args.get("options"), 5219 ) 5220 5221 def forin_sql(self, expression: exp.ForIn) -> str: 5222 this = self.sql(expression, "this") 5223 expression_sql = self.sql(expression, "expression") 5224 return f"FOR {this} DO {expression_sql}" 5225 5226 def refresh_sql(self, expression: exp.Refresh) -> str: 5227 this = self.sql(expression, "this") 5228 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5229 return f"REFRESH {kind}{this}" 5230 5231 def toarray_sql(self, expression: exp.ToArray) -> str: 5232 arg = expression.this 5233 if not arg.type: 5234 import sqlglot.optimizer.annotate_types 5235 5236 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5237 5238 if arg.is_type(exp.DType.ARRAY): 5239 return self.sql(arg) 5240 5241 cond_for_null = arg.is_(exp.null()) 5242 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5243 5244 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5245 this = expression.this 5246 time_format = self.format_time(expression) 5247 5248 if time_format: 5249 return self.sql( 5250 exp.cast( 5251 exp.StrToTime(this=this, format=expression.args["format"]), 5252 exp.DType.TIME, 5253 ) 5254 ) 5255 5256 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5257 return self.sql(this) 5258 5259 return self.sql(exp.cast(this, exp.DType.TIME)) 5260 5261 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5262 this = expression.this 5263 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5264 return self.sql(this) 5265 5266 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5267 5268 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5269 this = expression.this 5270 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5271 return self.sql(this) 5272 5273 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5274 5275 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5276 this = expression.this 5277 time_format = self.format_time(expression) 5278 safe = expression.args.get("safe") 5279 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5280 return self.sql( 5281 exp.cast( 5282 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5283 exp.DType.DATE, 5284 ) 5285 ) 5286 5287 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5288 return self.sql(this) 5289 5290 if safe: 5291 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5292 5293 return self.sql(exp.cast(this, exp.DType.DATE)) 5294 5295 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5296 return self.sql( 5297 exp.func( 5298 "DATEDIFF", 5299 expression.this, 5300 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5301 "day", 5302 ) 5303 ) 5304 5305 def lastday_sql(self, expression: exp.LastDay) -> str: 5306 if self.LAST_DAY_SUPPORTS_DATE_PART: 5307 return self.function_fallback_sql(expression) 5308 5309 unit = expression.args.get("unit") 5310 if unit and unit.name.upper() != "MONTH": 5311 self.unsupported("Date parts are not supported in LAST_DAY.") 5312 5313 return self.func("LAST_DAY", expression.this) 5314 5315 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5316 import sqlglot.dialects.dialect 5317 5318 return self.func( 5319 "DATE_ADD", 5320 expression.this, 5321 expression.expression, 5322 sqlglot.dialects.dialect.unit_to_str(expression), 5323 ) 5324 5325 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5326 this = expression.this 5327 position = expression.args["position"] 5328 offset = index_offset - (expression.args.get("offset") or 0) 5329 5330 if offset: 5331 if position.is_int: 5332 value = position.to_py() 5333 if value >= 0: 5334 position = exp.Literal.number(value + offset) 5335 elif offset < 0 and value == -1: 5336 # 1-based -1 appends, which a 0-based position can only express as the size 5337 position = exp.ArraySize(this=this.copy()) 5338 else: 5339 # Negative positions count from the end, so they shift in the opposite 5340 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5341 position = exp.Literal.number(value - offset) 5342 else: 5343 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5344 5345 return self.func("ARRAY_INSERT", this, position, expression.expression) 5346 5347 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5348 if self.CAN_IMPLEMENT_ARRAY_ANY: 5349 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5350 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5351 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5352 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5353 5354 import sqlglot.dialects.dialect 5355 5356 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5357 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5358 self.unsupported("ARRAY_ANY is unsupported") 5359 5360 return self.function_fallback_sql(expression) 5361 5362 def struct_sql(self, expression: exp.Struct) -> str: 5363 expression.set( 5364 "expressions", 5365 [ 5366 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5367 if isinstance(e, exp.PropertyEQ) 5368 else e 5369 for e in expression.expressions 5370 ], 5371 ) 5372 5373 return self.function_fallback_sql(expression) 5374 5375 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5376 low = self.sql(expression, "this") 5377 high = self.sql(expression, "expression") 5378 5379 return f"{low} TO {high}" 5380 5381 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5382 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5383 tables = f" {self.expressions(expression)}" 5384 5385 exists = " IF EXISTS" if expression.args.get("exists") else "" 5386 5387 on_cluster = self.sql(expression, "cluster") 5388 on_cluster = f" {on_cluster}" if on_cluster else "" 5389 5390 identity = self.sql(expression, "identity") 5391 identity = f" {identity} IDENTITY" if identity else "" 5392 5393 option = self.sql(expression, "option") 5394 option = f" {option}" if option else "" 5395 5396 partition = self.sql(expression, "partition") 5397 partition = f" {partition}" if partition else "" 5398 5399 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5400 5401 # This transpiles T-SQL's CONVERT function 5402 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5403 def convert_sql(self, expression: exp.Convert) -> str: 5404 to = expression.this 5405 value = expression.expression 5406 style = expression.args.get("style") 5407 safe = expression.args.get("safe") 5408 strict = expression.args.get("strict") 5409 5410 if not to or not value: 5411 return "" 5412 5413 # Retrieve length of datatype and override to default if not specified 5414 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5415 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5416 5417 transformed: exp.Expr | None = None 5418 cast = exp.Cast if strict else exp.TryCast 5419 5420 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5421 if isinstance(style, exp.Literal) and style.is_int: 5422 import sqlglot.dialects.tsql 5423 5424 style_value = style.name 5425 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5426 if not converted_style: 5427 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5428 5429 fmt = exp.Literal.string(converted_style) 5430 5431 if to.this == exp.DType.DATE: 5432 transformed = exp.StrToDate(this=value, format=fmt) 5433 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5434 transformed = exp.StrToTime(this=value, format=fmt) 5435 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5436 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5437 elif to.this == exp.DType.TEXT: 5438 transformed = exp.TimeToStr(this=value, format=fmt) 5439 5440 if not transformed: 5441 transformed = cast(this=value, to=to, safe=safe) 5442 5443 return self.sql(transformed) 5444 5445 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5446 this = expression.this 5447 if isinstance(this, exp.JSONPathWildcard): 5448 this = self.json_path_part(this) 5449 return f".{this}" if this else "" 5450 5451 quoted = expression.args.get("quoted") 5452 if not ( 5453 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5454 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5455 return f".{this}" 5456 5457 this = self.json_path_part(this) 5458 5459 return ( 5460 f"[{this}]" 5461 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5462 else f".{this}" 5463 ) 5464 5465 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5466 this = self.json_path_part(expression.this) 5467 return f"[{this}]" if this else "" 5468 5469 def _simplify_unless_literal(self, expression: E) -> E: 5470 if not isinstance(expression, exp.Literal): 5471 import sqlglot.optimizer.simplify 5472 5473 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5474 5475 return expression 5476 5477 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5478 this = expression.this 5479 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5480 self.unsupported( 5481 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5482 ) 5483 return self.sql(this) 5484 5485 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5486 if self.IGNORE_NULLS_BEFORE_ORDER: 5487 from sqlglot.optimizer.scope import find_all_in_scope 5488 5489 # The first modifier here will be the one closest to the AggFunc's arg 5490 mods = sorted( 5491 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5492 key=lambda x: ( 5493 0 5494 if isinstance(x, exp.HavingMax) 5495 else (1 if isinstance(x, exp.Order) else 2) 5496 ), 5497 ) 5498 5499 if mods: 5500 mod = mods[0] 5501 this = expression.__class__(this=mod.this.copy()) 5502 this.meta["inline"] = True 5503 mod.this.replace(this) 5504 return self.sql(expression.this) 5505 5506 agg_func = expression.find(exp.AggFunc) 5507 5508 if agg_func: 5509 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5510 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5511 5512 return f"{self.sql(expression, 'this')} {text}" 5513 5514 def _replace_line_breaks(self, string: str) -> str: 5515 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5516 if self.pretty: 5517 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5518 return string 5519 5520 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5521 option = self.sql(expression, "this") 5522 5523 if expression.expressions: 5524 upper = option.upper() 5525 5526 # Snowflake FILE_FORMAT options are separated by whitespace 5527 sep = " " if upper == "FILE_FORMAT" else ", " 5528 5529 # Databricks copy/format options do not set their list of values with EQ 5530 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5531 values = self.expressions(expression, flat=True, sep=sep) 5532 return f"{option}{op}({values})" 5533 5534 value = self.sql(expression, "expression") 5535 5536 if not value: 5537 return option 5538 5539 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5540 5541 return f"{option}{op}{value}" 5542 5543 def credentials_sql(self, expression: exp.Credentials) -> str: 5544 cred_expr = expression.args.get("credentials") 5545 if isinstance(cred_expr, exp.Literal): 5546 # Redshift case: CREDENTIALS <string> 5547 credentials = self.sql(expression, "credentials") 5548 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5549 else: 5550 # Snowflake case: CREDENTIALS = (...) 5551 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5552 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5553 5554 storage = self.sql(expression, "storage") 5555 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5556 5557 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5558 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5559 5560 iam_role = self.sql(expression, "iam_role") 5561 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5562 5563 region = self.sql(expression, "region") 5564 region = f" REGION {region}" if region else "" 5565 5566 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5567 5568 def copy_sql(self, expression: exp.Copy) -> str: 5569 this = self.sql(expression, "this") 5570 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5571 5572 credentials = self.sql(expression, "credentials") 5573 credentials = self.seg(credentials) if credentials else "" 5574 files = self.expressions(expression, key="files", flat=True) 5575 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5576 5577 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5578 params = self.expressions( 5579 expression, 5580 key="params", 5581 sep=sep, 5582 new_line=True, 5583 skip_last=True, 5584 skip_first=True, 5585 indent=self.COPY_PARAMS_ARE_WRAPPED, 5586 ) 5587 5588 if params: 5589 if self.COPY_PARAMS_ARE_WRAPPED: 5590 params = f" WITH ({params})" 5591 elif not self.pretty and (files or credentials): 5592 params = f" {params}" 5593 5594 return f"COPY{this}{kind} {files}{credentials}{params}" 5595 5596 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5597 return "" 5598 5599 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5600 on_sql = "ON" if expression.args.get("on") else "OFF" 5601 filter_col: str | None = self.sql(expression, "filter_column") 5602 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5603 retention_period: str | None = self.sql(expression, "retention_period") 5604 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5605 5606 if filter_col or retention_period: 5607 on_sql = self.func("ON", filter_col, retention_period) 5608 5609 return f"DATA_DELETION={on_sql}" 5610 5611 def maskingpolicycolumnconstraint_sql( 5612 self, expression: exp.MaskingPolicyColumnConstraint 5613 ) -> str: 5614 this = self.sql(expression, "this") 5615 expressions = self.expressions(expression, flat=True) 5616 expressions = f" USING ({expressions})" if expressions else "" 5617 return f"MASKING POLICY {this}{expressions}" 5618 5619 def gapfill_sql(self, expression: exp.GapFill) -> str: 5620 this = self.sql(expression, "this") 5621 this = f"TABLE {this}" 5622 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5623 5624 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5625 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5626 5627 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5628 this = self.sql(expression, "this") 5629 expr = expression.expression 5630 5631 if isinstance(expr, exp.Func): 5632 # T-SQL's CLR functions are case sensitive 5633 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5634 else: 5635 expr = self.sql(expression, "expression") 5636 5637 return self.scope_resolution(expr, this) 5638 5639 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5640 if self.PARSE_JSON_NAME is None: 5641 return self.sql(expression.this) 5642 5643 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5644 5645 def rand_sql(self, expression: exp.Rand) -> str: 5646 lower = self.sql(expression, "lower") 5647 upper = self.sql(expression, "upper") 5648 5649 if lower and upper: 5650 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5651 return self.func("RAND", expression.this) 5652 5653 def changes_sql(self, expression: exp.Changes) -> str: 5654 information = self.sql(expression, "information") 5655 information = f"INFORMATION => {information}" 5656 at_before = self.sql(expression, "at_before") 5657 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5658 end = self.sql(expression, "end") 5659 end = f"{self.seg('')}{end}" if end else "" 5660 5661 return f"CHANGES ({information}){at_before}{end}" 5662 5663 def pad_sql(self, expression: exp.Pad) -> str: 5664 prefix = "L" if expression.args.get("is_left") else "R" 5665 5666 fill_pattern = self.sql(expression, "fill_pattern") or None 5667 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5668 fill_pattern = "' '" 5669 5670 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5671 5672 def summarize_sql(self, expression: exp.Summarize) -> str: 5673 table = " TABLE" if expression.args.get("table") else "" 5674 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5675 5676 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5677 generate_series = exp.GenerateSeries(**expression.args) 5678 5679 parent = expression.parent 5680 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5681 parent = parent.parent 5682 5683 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5684 return self.sql(exp.Unnest(expressions=[generate_series])) 5685 5686 if isinstance(parent, exp.Select): 5687 self.unsupported("GenerateSeries projection unnesting is not supported.") 5688 5689 return self.sql(generate_series) 5690 5691 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5692 if self.SUPPORTS_CONVERT_TIMEZONE: 5693 return self.function_fallback_sql(expression) 5694 5695 source_tz = expression.args.get("source_tz") 5696 target_tz = expression.args.get("target_tz") 5697 timestamp = expression.args.get("timestamp") 5698 5699 if source_tz and timestamp: 5700 timestamp = exp.AtTimeZone( 5701 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5702 ) 5703 5704 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5705 5706 return self.sql(expr) 5707 5708 def json_sql(self, expression: exp.JSON) -> str: 5709 this = self.sql(expression, "this") 5710 this = f" {this}" if this else "" 5711 5712 _with = expression.args.get("with_") 5713 5714 if _with is None: 5715 with_sql = "" 5716 elif not _with: 5717 with_sql = " WITHOUT" 5718 else: 5719 with_sql = " WITH" 5720 5721 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5722 5723 return f"JSON{this}{with_sql}{unique_sql}" 5724 5725 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5726 path = self.sql(expression, "path") 5727 returning = self.sql(expression, "returning") 5728 returning = f" RETURNING {returning}" if returning else "" 5729 5730 on_condition = self.sql(expression, "on_condition") 5731 on_condition = f" {on_condition}" if on_condition else "" 5732 5733 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5734 5735 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5736 regexp = " REGEXP" if expression.args.get("regexp") else "" 5737 return f"SKIP{regexp} {self.sql(expression.expression)}" 5738 5739 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5740 else_ = "ELSE " if expression.args.get("else_") else "" 5741 condition = self.sql(expression, "expression") 5742 condition = f"WHEN {condition} THEN " if condition else else_ 5743 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5744 return f"{condition}{insert}" 5745 5746 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5747 kind = self.sql(expression, "kind") 5748 expressions = self.seg(self.expressions(expression, sep=" ")) 5749 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5750 return res 5751 5752 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5753 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5754 empty = expression.args.get("empty") 5755 empty = ( 5756 f"DEFAULT {empty} ON EMPTY" 5757 if isinstance(empty, exp.Expr) 5758 else self.sql(expression, "empty") 5759 ) 5760 5761 error = expression.args.get("error") 5762 error = ( 5763 f"DEFAULT {error} ON ERROR" 5764 if isinstance(error, exp.Expr) 5765 else self.sql(expression, "error") 5766 ) 5767 5768 if error and empty: 5769 error = ( 5770 f"{empty} {error}" 5771 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5772 else f"{error} {empty}" 5773 ) 5774 empty = "" 5775 5776 null = self.sql(expression, "null") 5777 5778 return f"{empty}{error}{null}" 5779 5780 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5781 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5782 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5783 5784 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5785 this = self.sql(expression, "this") 5786 path = self.sql(expression, "path") 5787 5788 passing = self.expressions(expression, "passing") 5789 passing = f" PASSING {passing}" if passing else "" 5790 5791 on_condition = self.sql(expression, "on_condition") 5792 on_condition = f" {on_condition}" if on_condition else "" 5793 5794 path = f"{path}{passing}{on_condition}" 5795 5796 return self.func("JSON_EXISTS", this, path) 5797 5798 def _add_arrayagg_null_filter( 5799 self, 5800 array_agg_sql: str, 5801 array_agg_expr: exp.ArrayAgg, 5802 column_expr: exp.Expr, 5803 ) -> str: 5804 """ 5805 Add NULL filter to ARRAY_AGG if dialect requires it. 5806 5807 Args: 5808 array_agg_sql: The generated ARRAY_AGG SQL string 5809 array_agg_expr: The ArrayAgg expression node 5810 column_expr: The column/expression to filter (before ORDER BY wrapping) 5811 5812 Returns: 5813 SQL string with FILTER clause added if needed 5814 """ 5815 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5816 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5817 if not ( 5818 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5819 ): 5820 return array_agg_sql 5821 5822 parent = array_agg_expr.parent 5823 if isinstance(parent, exp.Filter): 5824 parent_cond = parent.expression.this 5825 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5826 elif column_expr.find(exp.Column): 5827 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5828 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5829 this_sql = ( 5830 self.expressions(column_expr) 5831 if isinstance(column_expr, exp.Distinct) 5832 else self.sql(column_expr) 5833 ) 5834 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5835 5836 return array_agg_sql 5837 5838 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5839 array_agg = self.function_fallback_sql(expression) 5840 column_expr = expression.this 5841 if isinstance(column_expr, exp.Order): 5842 column_expr = column_expr.this 5843 5844 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5845 5846 def slice_sql(self, expression: exp.Slice) -> str: 5847 step = self.sql(expression, "step") 5848 end = self.sql(expression.expression) 5849 begin = self.sql(expression.this) 5850 5851 sql = f"{end}:{step}" if step else end 5852 return f"{begin}:{sql}" if sql else f"{begin}:" 5853 5854 def apply_sql(self, expression: exp.Apply) -> str: 5855 this = self.sql(expression, "this") 5856 expr = self.sql(expression, "expression") 5857 5858 return f"{this} APPLY({expr})" 5859 5860 def _grant_or_revoke_sql( 5861 self, 5862 expression: exp.Grant | exp.Revoke, 5863 keyword: str, 5864 preposition: str, 5865 grant_option_prefix: str = "", 5866 grant_option_suffix: str = "", 5867 ) -> str: 5868 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5869 5870 kind = self.sql(expression, "kind") 5871 kind = f" {kind}" if kind else "" 5872 5873 securable = self.sql(expression, "securable") 5874 securable = f" {securable}" if securable else "" 5875 5876 principals = self.expressions(expression, key="principals", flat=True) 5877 5878 if not expression.args.get("grant_option"): 5879 grant_option_prefix = grant_option_suffix = "" 5880 5881 # cascade for revoke only 5882 cascade = self.sql(expression, "cascade") 5883 cascade = f" {cascade}" if cascade else "" 5884 5885 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5886 5887 def grant_sql(self, expression: exp.Grant) -> str: 5888 return self._grant_or_revoke_sql( 5889 expression, 5890 keyword="GRANT", 5891 preposition="TO", 5892 grant_option_suffix=" WITH GRANT OPTION", 5893 ) 5894 5895 def revoke_sql(self, expression: exp.Revoke) -> str: 5896 return self._grant_or_revoke_sql( 5897 expression, 5898 keyword="REVOKE", 5899 preposition="FROM", 5900 grant_option_prefix="GRANT OPTION FOR ", 5901 ) 5902 5903 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5904 this = self.sql(expression, "this") 5905 columns = self.expressions(expression, flat=True) 5906 columns = f"({columns})" if columns else "" 5907 5908 return f"{this}{columns}" 5909 5910 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5911 this = self.sql(expression, "this") 5912 5913 kind = self.sql(expression, "kind") 5914 kind = f"{kind} " if kind else "" 5915 5916 return f"{kind}{this}" 5917 5918 def columns_sql(self, expression: exp.Columns) -> str: 5919 func = self.function_fallback_sql(expression) 5920 if expression.args.get("unpack"): 5921 func = f"*{func}" 5922 5923 return func 5924 5925 def overlay_sql(self, expression: exp.Overlay) -> str: 5926 this = self.sql(expression, "this") 5927 expr = self.sql(expression, "expression") 5928 from_sql = self.sql(expression, "from_") 5929 for_sql = self.sql(expression, "for_") 5930 for_sql = f" FOR {for_sql}" if for_sql else "" 5931 5932 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5933 5934 @unsupported_args("format") 5935 def todouble_sql(self, expression: exp.ToDouble) -> str: 5936 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5937 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5938 5939 def string_sql(self, expression: exp.String) -> str: 5940 this = expression.this 5941 zone = expression.args.get("zone") 5942 5943 if zone: 5944 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5945 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5946 # set for source_tz to transpile the time conversion before the STRING cast 5947 this = exp.ConvertTimezone( 5948 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5949 ) 5950 5951 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5952 5953 def median_sql(self, expression: exp.Median) -> str: 5954 if not self.SUPPORTS_MEDIAN: 5955 return self.sql( 5956 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5957 ) 5958 5959 return self.function_fallback_sql(expression) 5960 5961 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5962 filler = self.sql(expression, "this") 5963 filler = f" {filler}" if filler else "" 5964 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5965 return f"TRUNCATE{filler} {with_count}" 5966 5967 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5968 if self.SUPPORTS_UNIX_SECONDS: 5969 return self.function_fallback_sql(expression) 5970 5971 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5972 5973 return self.sql( 5974 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5975 ) 5976 5977 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5978 dim = expression.expression 5979 5980 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5981 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5982 if not (dim.is_int and dim.name == "1"): 5983 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5984 dim = None 5985 5986 # If dimension is required but not specified, default initialize it 5987 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5988 dim = exp.Literal.number(1) 5989 5990 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5991 5992 def attach_sql(self, expression: exp.Attach) -> str: 5993 this = self.sql(expression, "this") 5994 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5995 expressions = self.expressions(expression) 5996 expressions = f" ({expressions})" if expressions else "" 5997 5998 return f"ATTACH{exists_sql} {this}{expressions}" 5999 6000 def detach_sql(self, expression: exp.Detach) -> str: 6001 kind = self.sql(expression, "kind") 6002 kind = f" {kind}" if kind else "" 6003 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6004 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6005 exists = " IF EXISTS" if expression.args.get("exists") else "" 6006 if exists: 6007 kind = kind or " DATABASE" 6008 6009 this = self.sql(expression, "this") 6010 this = f" {this}" if this else "" 6011 cluster = self.sql(expression, "cluster") 6012 cluster = f" {cluster}" if cluster else "" 6013 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6014 sync = " SYNC" if expression.args.get("sync") else "" 6015 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6016 6017 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6018 this = self.sql(expression, "this") 6019 value = self.sql(expression, "expression") 6020 value = f" {value}" if value else "" 6021 return f"{this}{value}" 6022 6023 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6024 return ( 6025 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6026 ) 6027 6028 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6029 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6030 encode = f"{encode} {self.sql(expression, 'this')}" 6031 6032 properties = expression.args.get("properties") 6033 if properties: 6034 encode = f"{encode} {self.properties(properties)}" 6035 6036 return encode 6037 6038 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6039 this = self.sql(expression, "this") 6040 include = f"INCLUDE {this}" 6041 6042 column_def = self.sql(expression, "column_def") 6043 if column_def: 6044 include = f"{include} {column_def}" 6045 6046 alias = self.sql(expression, "alias") 6047 if alias: 6048 include = f"{include} AS {alias}" 6049 6050 return include 6051 6052 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6053 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6054 name = f"{prefix} {self.sql(expression, 'this')}" 6055 return self.func("XMLELEMENT", name, *expression.expressions) 6056 6057 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6058 this = self.sql(expression, "this") 6059 expr = self.sql(expression, "expression") 6060 expr = f"({expr})" if expr else "" 6061 return f"{this}{expr}" 6062 6063 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6064 partitions = self.expressions(expression, "partition_expressions") 6065 create = self.expressions(expression, "create_expressions") 6066 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6067 6068 def partitionbyrangepropertydynamic_sql( 6069 self, expression: exp.PartitionByRangePropertyDynamic 6070 ) -> str: 6071 start = self.sql(expression, "start") 6072 end = self.sql(expression, "end") 6073 6074 every = expression.args["every"] 6075 if isinstance(every, exp.Interval) and every.this.is_string: 6076 every.this.replace(exp.Literal.number(every.name)) 6077 6078 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6079 6080 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6081 name = self.sql(expression, "this") 6082 values = self.expressions(expression, flat=True) 6083 6084 return f"NAME {name} VALUE {values}" 6085 6086 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6087 kind = self.sql(expression, "kind") 6088 sample = self.sql(expression, "sample") 6089 return f"SAMPLE {sample} {kind}" 6090 6091 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6092 kind = self.sql(expression, "kind") 6093 option = self.sql(expression, "option") 6094 option = f" {option}" if option else "" 6095 this = self.sql(expression, "this") 6096 this = f" {this}" if this else "" 6097 columns = self.expressions(expression) 6098 columns = f" {columns}" if columns else "" 6099 return f"{kind}{option} STATISTICS{this}{columns}" 6100 6101 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6102 this = self.sql(expression, "this") 6103 columns = self.expressions(expression) 6104 inner_expression = self.sql(expression, "expression") 6105 inner_expression = f" {inner_expression}" if inner_expression else "" 6106 update_options = self.sql(expression, "update_options") 6107 update_options = f" {update_options} UPDATE" if update_options else "" 6108 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6109 6110 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6111 kind = self.sql(expression, "kind") 6112 kind = f" {kind}" if kind else "" 6113 return f"DELETE{kind} STATISTICS" 6114 6115 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6116 inner_expression = self.sql(expression, "expression") 6117 return f"LIST CHAINED ROWS{inner_expression}" 6118 6119 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6120 kind = self.sql(expression, "kind") 6121 this = self.sql(expression, "this") 6122 this = f" {this}" if this else "" 6123 inner_expression = self.sql(expression, "expression") 6124 return f"VALIDATE {kind}{this}{inner_expression}" 6125 6126 def analyze_sql(self, expression: exp.Analyze) -> str: 6127 options = self.expressions(expression, key="options", sep=" ") 6128 options = f" {options}" if options else "" 6129 kind = self.sql(expression, "kind") 6130 kind = f" {kind}" if kind else "" 6131 tables = self.expressions(expression, key="tables", flat=True) 6132 tables = f" {tables}" if tables else "" 6133 mode = self.sql(expression, "mode") 6134 mode = f" {mode}" if mode else "" 6135 properties = self.sql(expression, "properties") 6136 properties = f" {properties}" if properties else "" 6137 partition = self.sql(expression, "partition") 6138 partition = f" {partition}" if partition else "" 6139 inner_expression = self.sql(expression, "expression") 6140 inner_expression = f" {inner_expression}" if inner_expression else "" 6141 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6142 6143 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6144 this = self.sql(expression, "this") 6145 namespaces = self.expressions(expression, key="namespaces") 6146 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6147 passing = self.expressions(expression, key="passing") 6148 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6149 columns = self.expressions(expression, key="columns") 6150 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6151 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6152 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6153 6154 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6155 this = self.sql(expression, "this") 6156 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6157 6158 def export_sql(self, expression: exp.Export) -> str: 6159 this = self.sql(expression, "this") 6160 connection = self.sql(expression, "connection") 6161 connection = f"WITH CONNECTION {connection} " if connection else "" 6162 options = self.sql(expression, "options") 6163 return f"EXPORT DATA {connection}{options} AS {this}" 6164 6165 def declare_sql(self, expression: exp.Declare) -> str: 6166 replace = "OR REPLACE " if expression.args.get("replace") else "" 6167 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6168 6169 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6170 variables = self.expressions(expression, "this") 6171 default = self.sql(expression, "default") 6172 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6173 6174 kind = self.sql(expression, "kind") 6175 if isinstance(expression.args.get("kind"), exp.Schema): 6176 kind = f"TABLE {kind}" 6177 6178 kind = f" {kind}" if kind else "" 6179 6180 return f"{variables}{kind}{default}" 6181 6182 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6183 kind = self.sql(expression, "kind") 6184 this = self.sql(expression, "this") 6185 set = self.sql(expression, "expression") 6186 using = self.sql(expression, "using") 6187 using = f" USING {using}" if using else "" 6188 6189 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6190 6191 return f"{kind_sql} {this} SET {set}{using}" 6192 6193 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6194 params = self.expressions(expression, key="params", flat=True) 6195 return self.func(expression.name, *expression.expressions) + f"({params})" 6196 6197 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6198 return self.func(expression.name, *expression.expressions) 6199 6200 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6201 return self.anonymousaggfunc_sql(expression) 6202 6203 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6204 return self.parameterizedagg_sql(expression) 6205 6206 def show_sql(self, expression: exp.Show) -> str: 6207 self.unsupported("Unsupported SHOW statement") 6208 return "" 6209 6210 def install_sql(self, expression: exp.Install) -> str: 6211 self.unsupported("Unsupported INSTALL statement") 6212 return "" 6213 6214 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6215 # Snowflake GET/PUT statements: 6216 # PUT <file> <internalStage> <properties> 6217 # GET <internalStage> <file> <properties> 6218 props = expression.args.get("properties") 6219 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6220 this = self.sql(expression, "this") 6221 target = self.sql(expression, "target") 6222 6223 if isinstance(expression, exp.Put): 6224 return f"PUT {this} {target}{props_sql}" 6225 else: 6226 return f"GET {target} {this}{props_sql}" 6227 6228 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6229 this = self.sql(expression, "this") 6230 expr = self.sql(expression, "expression") 6231 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6232 return f"TRANSLATE({this} USING {expr}{with_error})" 6233 6234 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6235 if self.SUPPORTS_DECODE_CASE: 6236 return self.func("DECODE", *expression.expressions) 6237 6238 decode_expr, *expressions = expression.expressions 6239 6240 ifs = [] 6241 for search, result in zip(expressions[::2], expressions[1::2]): 6242 if isinstance(search, exp.Literal): 6243 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6244 elif isinstance(search, exp.Null): 6245 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6246 else: 6247 if isinstance(search, exp.Binary): 6248 search = exp.paren(search) 6249 6250 cond = exp.or_( 6251 decode_expr.eq(search), 6252 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6253 copy=False, 6254 ) 6255 ifs.append(exp.If(this=cond, true=result)) 6256 6257 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6258 return self.sql(case) 6259 6260 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6261 this = self.sql(expression, "this") 6262 this = self.seg(this, sep="") 6263 dimensions = self.expressions( 6264 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6265 ) 6266 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6267 metrics = self.expressions( 6268 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6269 ) 6270 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6271 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6272 facts = self.seg(f"FACTS {facts}") if facts else "" 6273 where = self.sql(expression, "where") 6274 where = self.seg(f"WHERE {where}") if where else "" 6275 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6276 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6277 6278 def getextract_sql(self, expression: exp.GetExtract) -> str: 6279 this = expression.this 6280 expr = expression.expression 6281 6282 if not this.type or not expression.type: 6283 import sqlglot.optimizer.annotate_types 6284 6285 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6286 6287 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6288 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6289 6290 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6291 6292 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6293 return self.sql( 6294 exp.DateAdd( 6295 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6296 expression=expression.this, 6297 unit=exp.var("DAY"), 6298 ) 6299 ) 6300 6301 def space_sql(self: Generator, expression: exp.Space) -> str: 6302 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6303 6304 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6305 return f"BUILD {self.sql(expression, 'this')}" 6306 6307 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6308 method = self.sql(expression, "method") 6309 kind = expression.args.get("kind") 6310 if not kind: 6311 return f"REFRESH {method}" 6312 6313 every = self.sql(expression, "every") 6314 unit = self.sql(expression, "unit") 6315 every = f" EVERY {every} {unit}" if every else "" 6316 starts = self.sql(expression, "starts") 6317 starts = f" STARTS {starts}" if starts else "" 6318 6319 return f"REFRESH {method} ON {kind}{every}{starts}" 6320 6321 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6322 self.unsupported("The model!attribute syntax is not supported") 6323 return "" 6324 6325 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6326 return self.func("DIRECTORY", expression.this) 6327 6328 def uuid_sql(self, expression: exp.Uuid) -> str: 6329 is_string = expression.args.get("is_string", False) 6330 uuid_func_sql = self.func("UUID") 6331 6332 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6333 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6334 6335 return uuid_func_sql 6336 6337 def initcap_sql(self, expression: exp.Initcap) -> str: 6338 delimiters = expression.expression 6339 6340 if delimiters: 6341 # do not generate delimiters arg if we are round-tripping from default delimiters 6342 if ( 6343 delimiters.is_string 6344 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6345 ): 6346 delimiters = None 6347 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6348 self.unsupported("INITCAP does not support custom delimiters") 6349 delimiters = None 6350 6351 return self.func("INITCAP", expression.this, delimiters) 6352 6353 def localtime_sql(self, expression: exp.Localtime) -> str: 6354 this = expression.this 6355 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6356 6357 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6358 this = expression.this 6359 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6360 6361 def weekstart_name(self, expression: exp.WeekStart) -> str: 6362 import sqlglot.dialects.dialect 6363 6364 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6365 this = expression.this.name.upper() 6366 6367 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6368 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6369 6370 if dow_from_week_start_day != dow_from_week_offset: 6371 self.unsupported( 6372 f"WEEK({this}) is not supported; falling back to the default week start day" 6373 ) 6374 6375 return "WEEK" 6376 6377 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6378 name = self.weekstart_name(expression) 6379 6380 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6381 if isinstance(expression.parent, exp.DateTrunc): 6382 return self.sql(exp.Literal.string(name)) 6383 6384 return name 6385 6386 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6387 this = self.expressions(expression) 6388 charset = self.sql(expression, "charset") 6389 using = f" USING {charset}" if charset else "" 6390 return self.func(name, this + using) 6391 6392 def block_sql(self, expression: exp.Block) -> str: 6393 expressions = self.expressions(expression, sep="; ", flat=True) 6394 begin = "BEGIN " if expression.args.get("begin") else "" 6395 return f"{begin}{expressions}" if expressions else "" 6396 6397 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6398 self.unsupported("Unsupported Inline UDFs syntax") 6399 return "" 6400 6401 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6402 self.unsupported("Unsupported Stored Procedure syntax") 6403 return "" 6404 6405 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6406 self.unsupported("Unsupported If block syntax") 6407 return "" 6408 6409 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6410 self.unsupported("Unsupported Case statement syntax") 6411 return "" 6412 6413 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6414 self.unsupported("Unsupported While block syntax") 6415 return "" 6416 6417 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6418 self.unsupported("Unsupported Loop block syntax") 6419 return "" 6420 6421 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6422 self.unsupported("Unsupported Repeat block syntax") 6423 return "" 6424 6425 def leave_sql(self, expression: exp.Leave) -> str: 6426 self.unsupported("Unsupported Leave syntax") 6427 return "" 6428 6429 def iterate_sql(self, expression: exp.Iterate) -> str: 6430 self.unsupported("Unsupported Iterate syntax") 6431 return "" 6432 6433 def execute_sql(self, expression: exp.Execute) -> str: 6434 self.unsupported("Unsupported Execute syntax") 6435 return "" 6436 6437 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6438 self.unsupported("Unsupported Execute syntax") 6439 return "" 6440 6441 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6442 props = self.expressions(expression, sep=" ") 6443 return f"MODIFY {props}" 6444 6445 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6446 kind = expression.args.get("kind") 6447 return f"USING {kind} {self.sql(expression, 'this')}" 6448 6449 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6450 this = self.sql(expression, "this") 6451 to = self.sql(expression, "to") 6452 return f"RENAME INDEX {this} TO {to}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: str | tuple[str, str]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
33def unsupported_args( 34 *args: str | tuple[str, str], 35) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 36 """ 37 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 38 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 39 """ 40 diagnostic_by_arg: dict[str, str | None] = {} 41 for arg in args: 42 if isinstance(arg, str): 43 diagnostic_by_arg[arg] = None 44 else: 45 diagnostic_by_arg[arg[0]] = arg[1] 46 47 def decorator(func: GeneratorMethod) -> GeneratorMethod: 48 @wraps(func) 49 def _func(generator: G, expression: E) -> str: 50 expression_name = expression.__class__.__name__ 51 dialect_name = generator.dialect.__class__.__name__ 52 53 for arg_name, diagnostic in diagnostic_by_arg.items(): 54 if expression.args.get(arg_name): 55 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 56 arg_name, expression_name, dialect_name 57 ) 58 generator.unsupported(diagnostic) 59 60 return func(generator, expression) 61 62 return _func 63 64 return decorator
Decorator that can be used to mark certain args of an Expr subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, typing.Any] =
{'windows': <function <lambda>>, 'qualify': <function <lambda>>}
class
Generator:
98class Generator: 99 """ 100 Generator converts a given syntax tree to the corresponding SQL string. 101 102 Args: 103 pretty: Whether to format the produced SQL string. 104 Default: False. 105 identify: Determines when an identifier should be quoted. Possible values are: 106 False (default): Never quote, except in cases where it's mandatory by the dialect. 107 True: Always quote except for specials cases. 108 'safe': Only quote identifiers that are case insensitive. 109 normalize: Whether to normalize identifiers to lowercase. 110 Default: False. 111 pad: The pad size in a formatted string. For example, this affects the indentation of 112 a projection in a query, relative to its nesting level. 113 Default: 2. 114 indent: The indentation size in a formatted string. For example, this affects the 115 indentation of subqueries and filters under a `WHERE` clause. 116 Default: 2. 117 normalize_functions: How to normalize function names. Possible values are: 118 "upper" or True (default): Convert names to uppercase. 119 "lower": Convert names to lowercase. 120 False: Disables function name normalization. 121 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 122 Default ErrorLevel.WARN. 123 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 124 This is only relevant if unsupported_level is ErrorLevel.RAISE. 125 Default: 3 126 leading_comma: Whether the comma is leading or trailing in select expressions. 127 This is only relevant when generating in pretty mode. 128 Default: False 129 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 130 The default is on the smaller end because the length only represents a segment and not the true 131 line length. 132 Default: 80 133 comments: Whether to preserve comments in the output SQL code. 134 Default: True 135 """ 136 137 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 138 **JSON_PATH_PART_TRANSFORMS, 139 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 140 exp.AllowedValuesProperty: lambda self, e: ( 141 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 142 ), 143 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 144 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 145 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 146 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 147 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 148 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 149 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 150 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 151 exp.BinaryColumnConstraint: lambda *_: "BINARY", 152 exp.CaseSpecificColumnConstraint: lambda _, e: ( 153 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 154 ), 155 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 156 exp.Ceil: lambda self, e: self.ceil_floor(e), 157 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 158 exp.CharacterSetProperty: lambda self, e: ( 159 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 160 ), 161 exp.ClusteredColumnConstraint: lambda self, e: ( 162 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 163 ), 164 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 165 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 166 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 167 exp.ConvertToCharset: lambda self, e: self.func( 168 "CONVERT", e.this, e.args["dest"], e.args.get("source") 169 ), 170 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 171 exp.CredentialsProperty: lambda self, e: ( 172 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 173 ), 174 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 175 exp.SessionUser: lambda *_: "SESSION_USER", 176 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 177 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 178 exp.ApiProperty: lambda *_: "API", 179 exp.ApplicationProperty: lambda *_: "APPLICATION", 180 exp.CatalogProperty: lambda *_: "CATALOG", 181 exp.ComputeProperty: lambda *_: "COMPUTE", 182 exp.DatabaseProperty: lambda *_: "DATABASE", 183 exp.DynamicProperty: lambda *_: "DYNAMIC", 184 exp.EmptyProperty: lambda *_: "EMPTY", 185 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 186 exp.EndStatement: lambda *_: "END", 187 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 188 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 189 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 190 exp.EphemeralColumnConstraint: lambda self, e: ( 191 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 192 ), 193 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 194 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 195 exp.Except: lambda self, e: self.set_operations(e), 196 exp.ExternalProperty: lambda *_: "EXTERNAL", 197 exp.Floor: lambda self, e: self.ceil_floor(e), 198 exp.Get: lambda self, e: self.get_put_sql(e), 199 exp.GlobalProperty: lambda *_: "GLOBAL", 200 exp.HeapProperty: lambda *_: "HEAP", 201 exp.HybridProperty: lambda *_: "HYBRID", 202 exp.IcebergProperty: lambda *_: "ICEBERG", 203 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 204 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 205 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 206 exp.Intersect: lambda self, e: self.set_operations(e), 207 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 208 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 209 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 210 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 211 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 212 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 213 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 214 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 215 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 216 exp.LanguageProperty: lambda self, e: self.naked_property(e), 217 exp.LocationProperty: lambda self, e: self.naked_property(e), 218 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 219 exp.MaskingProperty: lambda *_: "MASKING", 220 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 221 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 222 exp.NetworkProperty: lambda *_: "NETWORK", 223 exp.NonClusteredColumnConstraint: lambda self, e: ( 224 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 225 ), 226 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 227 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 228 exp.OnCommitProperty: lambda _, e: ( 229 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 230 ), 231 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 232 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 233 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 234 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 235 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 236 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 237 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 238 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 239 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 240 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 241 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 242 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 243 f"PROJECTION POLICY {self.sql(e, 'this')}" 244 ), 245 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 246 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 247 exp.Put: lambda self, e: self.get_put_sql(e), 248 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 249 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 250 ), 251 exp.ReturnsProperty: lambda self, e: ( 252 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 253 ), 254 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 255 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 256 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 257 exp.SecureProperty: lambda *_: "SECURE", 258 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 259 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 260 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 261 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 262 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 263 exp.SqlReadWriteProperty: lambda _, e: e.name, 264 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 265 exp.StabilityProperty: lambda _, e: e.name, 266 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 267 exp.StreamingTableProperty: lambda *_: "STREAMING", 268 exp.StrictProperty: lambda *_: "STRICT", 269 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 270 exp.TableColumn: lambda self, e: self.sql(e.this), 271 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 272 exp.TemporaryProperty: lambda *_: "TEMPORARY", 273 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 274 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 275 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 276 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 277 exp.TransientProperty: lambda *_: "TRANSIENT", 278 exp.VirtualProperty: lambda *_: "VIRTUAL", 279 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 280 exp.Union: lambda self, e: self.set_operations(e), 281 exp.UnloggedProperty: lambda *_: "UNLOGGED", 282 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 283 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 284 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 285 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 286 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 287 exp.UtcTimestamp: lambda self, e: self.sql( 288 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 289 ), 290 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 291 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 292 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 293 exp.VolatileProperty: lambda *_: "VOLATILE", 294 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 295 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 296 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 297 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 298 exp.ForceProperty: lambda *_: "FORCE", 299 } 300 301 # Whether null ordering is supported in order by 302 # True: Full Support, None: No support, False: No support for certain cases 303 # such as window specifications, aggregate functions etc 304 NULL_ORDERING_SUPPORTED: bool | None = True 305 306 # Window functions that support NULLS FIRST/LAST 307 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 308 309 # Whether ignore nulls is inside the agg or outside. 310 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 311 IGNORE_NULLS_IN_FUNC = False 312 313 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 314 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 315 IGNORE_NULLS_BEFORE_ORDER = True 316 317 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 318 LOCKING_READS_SUPPORTED = False 319 320 # Whether the EXCEPT and INTERSECT operations can return duplicates 321 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 322 323 # Wrap derived values in parens, usually standard but spark doesn't support it 324 WRAP_DERIVED_VALUES = True 325 326 # Whether create function uses an AS before the RETURN 327 CREATE_FUNCTION_RETURN_AS = True 328 329 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 330 MATCHED_BY_SOURCE = True 331 332 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 333 SUPPORTS_MERGE_WHERE = False 334 335 # Whether the INTERVAL expression works only with values like '1 day' 336 SINGLE_STRING_INTERVAL = False 337 338 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 339 INTERVAL_ALLOWS_PLURAL_FORM = True 340 341 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 342 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 343 AUTO_REFRESH_BARE_INTERVALS = False 344 345 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 346 LIMIT_FETCH = "ALL" 347 348 # Whether limit and fetch allows expresions or just limits 349 LIMIT_ONLY_LITERALS = False 350 351 # Whether a table is allowed to be renamed with a db 352 RENAME_TABLE_WITH_DB = True 353 354 # The separator for grouping sets and rollups 355 GROUPINGS_SEP = "," 356 357 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 358 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 359 360 # The string used for creating an index on a table 361 INDEX_ON = "ON" 362 363 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 364 INOUT_SEPARATOR = " " 365 366 # Whether join hints should be generated 367 JOIN_HINTS = True 368 369 # Whether directed joins are supported 370 DIRECTED_JOINS = False 371 372 # Whether table hints should be generated 373 TABLE_HINTS = True 374 375 # Whether query hints should be generated 376 QUERY_HINTS = True 377 378 # What kind of separator to use for query hints 379 QUERY_HINT_SEP = ", " 380 381 # Whether comparing against booleans (e.g. x IS TRUE) is supported 382 IS_BOOL_ALLOWED = True 383 384 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 385 DUPLICATE_KEY_UPDATE_WITH_SET = True 386 387 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 388 LIMIT_IS_TOP = False 389 390 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 391 RETURNING_END = True 392 393 # Whether to generate an unquoted value for EXTRACT's date part argument 394 EXTRACT_ALLOWS_QUOTES = True 395 396 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 397 TZ_TO_WITH_TIME_ZONE = False 398 399 # Whether the NVL2 function is supported 400 NVL2_SUPPORTED = True 401 402 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 403 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 404 405 # Whether VALUES statements can be used as derived tables. 406 # MySQL 5 and Redshift do not allow this, so when False, it will convert 407 # SELECT * VALUES into SELECT UNION 408 VALUES_AS_TABLE = True 409 410 # Whether the word COLUMN is included when adding a column with ALTER TABLE 411 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 412 413 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 414 UNNEST_WITH_ORDINALITY = True 415 416 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 417 SEMI_ANTI_JOIN_WITH_SIDE = True 418 419 # Whether to include the type of a computed column in the CREATE DDL 420 COMPUTED_COLUMN_WITH_TYPE = True 421 422 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 423 SUPPORTS_TABLE_COPY = True 424 425 # Whether parentheses are required around the table sample's expression 426 TABLESAMPLE_REQUIRES_PARENS = True 427 428 # Whether a table sample clause's size needs to be followed by the ROWS keyword 429 TABLESAMPLE_SIZE_IS_ROWS = True 430 431 # The keyword(s) to use when generating a sample clause 432 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 433 434 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 435 TABLESAMPLE_WITH_METHOD = True 436 437 # The keyword to use when specifying the seed of a sample clause 438 TABLESAMPLE_SEED_KEYWORD = "SEED" 439 440 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 441 HISTORICAL_DATA_POST_ALIAS = False 442 443 # Whether COLLATE is a function instead of a binary operator 444 COLLATE_IS_FUNC = False 445 446 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 447 DATA_TYPE_SPECIFIERS_ALLOWED = False 448 449 # Whether conditions require booleans WHERE x = 0 vs WHERE x 450 ENSURE_BOOLS = False 451 452 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 453 CTE_RECURSIVE_KEYWORD_REQUIRED = True 454 455 # Whether CONCAT requires >1 arguments 456 SUPPORTS_SINGLE_ARG_CONCAT = True 457 458 # Whether LAST_DAY function supports a date part argument 459 LAST_DAY_SUPPORTS_DATE_PART = True 460 461 # Whether named columns are allowed in table aliases 462 SUPPORTS_TABLE_ALIAS_COLUMNS = True 463 464 # Whether named columns are allowed in CTE definitions 465 SUPPORTS_NAMED_CTE_COLUMNS = True 466 467 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 468 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 469 470 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 471 PIVOT_ALIAS_WITH_AS = True 472 473 # What delimiter to use for separating JSON key/value pairs 474 JSON_KEY_VALUE_PAIR_SEP = ":" 475 476 # INSERT OVERWRITE TABLE x override 477 INSERT_OVERWRITE = " OVERWRITE TABLE" 478 479 # Whether the SELECT .. INTO syntax is used instead of CTAS 480 SUPPORTS_SELECT_INTO = False 481 482 # Whether UNLOGGED tables can be created 483 SUPPORTS_UNLOGGED_TABLES = False 484 485 # Whether the CREATE TABLE LIKE statement is supported 486 SUPPORTS_CREATE_TABLE_LIKE = True 487 488 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 489 SUPPORTS_MODIFY_COLUMN = False 490 491 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 492 SUPPORTS_CHANGE_COLUMN = False 493 494 # Whether ALTER COLUMN can set a column's nullability together with its type 495 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 496 497 # Whether ALTER COLUMN IF EXISTS is supported 498 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 499 500 # Whether the LikeProperty needs to be specified inside of the schema clause 501 LIKE_PROPERTY_INSIDE_SCHEMA = False 502 503 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 504 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 505 MULTI_ARG_DISTINCT = True 506 507 # Whether the JSON extraction operators expect a value of type JSON 508 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 509 510 # Whether bracketed keys like ["foo"] are supported in JSON paths 511 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 512 513 # Whether to escape keys using single quotes in JSON paths 514 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 515 516 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 517 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 518 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 519 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 520 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 521 522 # The JSONPathPart expressions supported by this dialect 523 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 524 525 # Whether any(f(x) for x in array) can be implemented by this dialect 526 CAN_IMPLEMENT_ARRAY_ANY = False 527 528 # Whether the function TO_NUMBER is supported 529 SUPPORTS_TO_NUMBER = True 530 531 # Whether EXCLUDE in window specification is supported 532 SUPPORTS_WINDOW_EXCLUDE = False 533 534 # Whether or not set op modifiers apply to the outer set op or select. 535 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 536 # True means limit 1 happens after the set op, False means it it happens on y. 537 SET_OP_MODIFIERS = True 538 539 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 540 SET_OP_LIMITS = False 541 542 # Whether set operation operands can be parenthesized without a SELECT wrapper. 543 SET_OP_PARENTHESIZED_OPERANDS = True 544 545 # Whether parameters from COPY statement are wrapped in parentheses 546 COPY_PARAMS_ARE_WRAPPED = True 547 548 # Whether values of params are set with "=" token or empty space 549 COPY_PARAMS_EQ_REQUIRED = False 550 551 # Whether COPY statement has INTO keyword 552 COPY_HAS_INTO_KEYWORD = True 553 554 # Whether the conditional TRY(expression) function is supported 555 TRY_SUPPORTED = True 556 557 # Whether the UESCAPE syntax in unicode strings is supported 558 SUPPORTS_UESCAPE = True 559 560 # Function used to replace escaped unicode codes in unicode strings 561 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 562 563 # The keyword to use when generating a star projection with excluded columns 564 STAR_EXCEPT = "EXCEPT" 565 566 # The HEX function name 567 HEX_FUNC = "HEX" 568 569 # The keywords to use when prefixing & separating WITH based properties 570 WITH_PROPERTIES_PREFIX = "WITH" 571 572 # Whether to quote the generated expression of exp.JsonPath 573 QUOTE_JSON_PATH = True 574 575 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 576 PAD_FILL_PATTERN_IS_REQUIRED = False 577 578 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 579 SUPPORTS_EXPLODING_PROJECTIONS = True 580 581 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 582 ARRAY_CONCAT_IS_VAR_LEN = True 583 584 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 585 SUPPORTS_CONVERT_TIMEZONE = False 586 587 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 588 SUPPORTS_MEDIAN = True 589 590 # Whether UNIX_SECONDS(timestamp) is supported 591 SUPPORTS_UNIX_SECONDS = False 592 593 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 594 ALTER_SET_WRAPPED = False 595 596 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 597 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 598 # TODO: The normalization should be done by default once we've tested it across all dialects. 599 NORMALIZE_EXTRACT_DATE_PARTS = False 600 601 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 602 PARSE_JSON_NAME: str | None = "PARSE_JSON" 603 604 # The function name of the exp.ArraySize expression 605 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 606 607 # The syntax to use when altering the type of a column 608 ALTER_SET_TYPE = "SET DATA TYPE" 609 610 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 611 # None -> Doesn't support it at all 612 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 613 # True (Postgres) -> Explicitly requires it 614 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 615 616 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 617 SUPPORTS_DECODE_CASE = True 618 619 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 620 SUPPORTS_BETWEEN_FLAGS = False 621 622 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 623 SUPPORTS_LIKE_QUANTIFIERS = True 624 625 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 626 MATCH_AGAINST_TABLE_PREFIX: str | None = None 627 628 # Whether to include the VARIABLE keyword for SET assignments 629 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 630 631 # The keyword to use for default value assignment in DECLARE statements 632 DECLARE_DEFAULT_ASSIGNMENT = "=" 633 634 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 635 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 636 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 637 UPDATE_STATEMENT_SUPPORTS_FROM = True 638 639 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 640 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 641 642 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 643 # - Snowflake: DROP ICEBERG TABLE a.b; 644 # - DuckDB: DROP TABLE a.b; 645 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 646 647 TYPE_MAPPING: t.ClassVar = { 648 exp.DType.DATETIME2: "TIMESTAMP", 649 exp.DType.NCHAR: "CHAR", 650 exp.DType.NVARCHAR: "VARCHAR", 651 exp.DType.MEDIUMTEXT: "TEXT", 652 exp.DType.LONGTEXT: "TEXT", 653 exp.DType.TINYTEXT: "TEXT", 654 exp.DType.BLOB: "VARBINARY", 655 exp.DType.MEDIUMBLOB: "BLOB", 656 exp.DType.LONGBLOB: "BLOB", 657 exp.DType.TINYBLOB: "BLOB", 658 exp.DType.INET: "INET", 659 exp.DType.ROWVERSION: "VARBINARY", 660 exp.DType.SMALLDATETIME: "TIMESTAMP", 661 } 662 663 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 664 665 # mapping of DType to its default parameters, bounds 666 TYPE_PARAM_SETTINGS: t.ClassVar[ 667 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 668 ] = {} 669 670 TIME_PART_SINGULARS: t.ClassVar = { 671 "MICROSECONDS": "MICROSECOND", 672 "SECONDS": "SECOND", 673 "MINUTES": "MINUTE", 674 "HOURS": "HOUR", 675 "DAYS": "DAY", 676 "WEEKS": "WEEK", 677 "MONTHS": "MONTH", 678 "QUARTERS": "QUARTER", 679 "YEARS": "YEAR", 680 } 681 682 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 683 "cluster": lambda self, e: self.sql(e, "cluster"), 684 "distribute": lambda self, e: self.sql(e, "distribute"), 685 "sort": lambda self, e: self.sql(e, "sort"), 686 **AFTER_HAVING_MODIFIER_TRANSFORMS, 687 } 688 689 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 690 691 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 692 693 PARAMETER_TOKEN = "@" 694 NAMED_PLACEHOLDER_TOKEN = ":" 695 696 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 697 698 PROPERTIES_LOCATION: t.ClassVar = { 699 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 701 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 702 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 703 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 704 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 705 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 707 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 709 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 711 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 713 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 715 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 719 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 720 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 721 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 723 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 724 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 726 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 730 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 736 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 737 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 738 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 739 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 740 exp.HeapProperty: exp.Properties.Location.POST_WITH, 741 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 742 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 744 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 747 exp.JournalProperty: exp.Properties.Location.POST_NAME, 748 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 753 exp.LogProperty: exp.Properties.Location.POST_NAME, 754 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 755 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 756 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 757 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 759 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 760 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 761 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 762 exp.Order: exp.Properties.Location.POST_SCHEMA, 763 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 765 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 767 exp.Property: exp.Properties.Location.POST_WITH, 768 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 772 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 773 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 779 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 780 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 781 exp.Set: exp.Properties.Location.POST_SCHEMA, 782 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 783 exp.SetProperty: exp.Properties.Location.POST_CREATE, 784 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 785 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 786 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 787 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 788 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 790 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 791 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 792 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 794 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 795 exp.Tags: exp.Properties.Location.POST_WITH, 796 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 797 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 798 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 799 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 801 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 802 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 803 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 805 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 806 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 807 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 808 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 809 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 810 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 811 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 812 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 813 } 814 815 # Keywords that can't be used as unquoted identifier names 816 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 817 818 # Exprs whose comments are separated from them for better formatting 819 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 820 exp.Command, 821 exp.Create, 822 exp.Describe, 823 exp.Delete, 824 exp.Drop, 825 exp.From, 826 exp.Insert, 827 exp.Join, 828 exp.MultitableInserts, 829 exp.Order, 830 exp.Group, 831 exp.Having, 832 exp.Select, 833 exp.SetOperation, 834 exp.Update, 835 exp.Where, 836 exp.With, 837 ) 838 839 # Exprs that should not have their comments generated in maybe_comment 840 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 841 exp.Binary, 842 exp.SetOperation, 843 ) 844 845 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 846 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 847 exp.Column, 848 exp.Literal, 849 exp.Neg, 850 exp.Paren, 851 ) 852 853 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 854 exp.DType.NVARCHAR, 855 exp.DType.VARCHAR, 856 exp.DType.CHAR, 857 exp.DType.NCHAR, 858 } 859 860 # Exprs that need to have all CTEs under them bubbled up to them 861 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 862 863 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 864 865 MOD_OPERATOR = "%" 866 867 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 868 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 869 exp.Mul, 870 exp.Div, 871 exp.IntDiv, 872 exp.Mod, 873 ) 874 875 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 876 877 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 878 879 __slots__ = ( 880 "pretty", 881 "identify", 882 "normalize", 883 "pad", 884 "_indent", 885 "normalize_functions", 886 "unsupported_level", 887 "max_unsupported", 888 "leading_comma", 889 "max_text_width", 890 "comments", 891 "dialect", 892 "unsupported_messages", 893 "_escaped_quote_end", 894 "_escaped_byte_quote_end", 895 "_escaped_identifier_end", 896 "_next_name", 897 "_identifier_start", 898 "_identifier_end", 899 "_quote_json_path_key_using_brackets", 900 "_dispatch", 901 ) 902 903 def __init__( 904 self, 905 pretty: bool | int | None = None, 906 identify: str | bool = False, 907 normalize: bool = False, 908 pad: int = 2, 909 indent: int = 2, 910 normalize_functions: str | bool | None = None, 911 unsupported_level: ErrorLevel = ErrorLevel.WARN, 912 max_unsupported: int = 3, 913 leading_comma: bool = False, 914 max_text_width: int = 80, 915 comments: bool = True, 916 dialect: DialectType = None, 917 ): 918 import sqlglot 919 import sqlglot.dialects.dialect 920 921 self.pretty = pretty if pretty is not None else sqlglot.pretty 922 self.identify = identify 923 self.normalize = normalize 924 self.pad = pad 925 self._indent = indent 926 self.unsupported_level = unsupported_level 927 self.max_unsupported = max_unsupported 928 self.leading_comma = leading_comma 929 self.max_text_width = max_text_width 930 self.comments = comments 931 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 932 933 # This is both a Dialect property and a Generator argument, so we prioritize the latter 934 self.normalize_functions = ( 935 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 936 ) 937 938 self.unsupported_messages: list[str] = [] 939 self._escaped_quote_end: str = ( 940 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 941 ) 942 self._escaped_byte_quote_end: str = ( 943 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 944 if self.dialect.BYTE_END 945 else "" 946 ) 947 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 948 949 self._next_name = name_sequence("_t") 950 951 self._identifier_start = self.dialect.IDENTIFIER_START 952 self._identifier_end = self.dialect.IDENTIFIER_END 953 954 self._quote_json_path_key_using_brackets = True 955 956 cls = type(self) 957 dispatch = _DISPATCH_CACHE.get(cls) 958 if dispatch is None: 959 dispatch = _build_dispatch(cls) 960 _DISPATCH_CACHE[cls] = dispatch 961 self._dispatch = dispatch 962 963 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 964 """ 965 Generates the SQL string corresponding to the given syntax tree. 966 967 Args: 968 expression: The syntax tree. 969 copy: Whether to copy the expression. The generator performs mutations so 970 it is safer to copy. 971 972 Returns: 973 The SQL string corresponding to `expression`. 974 """ 975 if copy: 976 expression = expression.copy() 977 978 expression = self.preprocess(expression) 979 980 self.unsupported_messages = [] 981 sql = self.sql(expression).strip() 982 983 if self.pretty: 984 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 985 986 if self.unsupported_level == ErrorLevel.IGNORE: 987 return sql 988 989 if self.unsupported_level == ErrorLevel.WARN: 990 for msg in self.unsupported_messages: 991 logger.warning(msg) 992 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 993 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 994 995 return sql 996 997 def preprocess(self, expression: exp.Expr) -> exp.Expr: 998 """Apply generic preprocessing transformations to a given expression.""" 999 expression = self._move_ctes_to_top_level(expression) 1000 1001 if self.ENSURE_BOOLS: 1002 import sqlglot.transforms 1003 1004 expression = sqlglot.transforms.ensure_bools(expression) 1005 1006 return expression 1007 1008 def _move_ctes_to_top_level(self, expression: E) -> E: 1009 if ( 1010 not expression.parent 1011 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1012 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1013 ): 1014 import sqlglot.transforms 1015 1016 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1017 return expression 1018 1019 def unsupported(self, message: str) -> None: 1020 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1021 raise UnsupportedError(message) 1022 self.unsupported_messages.append(message) 1023 1024 def sep(self, sep: str = " ") -> str: 1025 return f"{sep.strip()}\n" if self.pretty else sep 1026 1027 def seg(self, sql: str, sep: str = " ") -> str: 1028 return f"{self.sep(sep)}{sql}" 1029 1030 def sanitize_comment(self, comment: str) -> str: 1031 comment = " " + comment if comment[0].strip() else comment 1032 comment = comment + " " if comment[-1].strip() else comment 1033 1034 # Escape block comment markers to prevent premature closure or unintended nesting. 1035 # This is necessary because single-line comments (--) are converted to block comments 1036 # (/* */) on output, and any */ in the original text would close the comment early. 1037 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1038 1039 return comment 1040 1041 def maybe_comment( 1042 self, 1043 sql: str, 1044 expression: exp.Expr | None = None, 1045 comments: list[str] | None = None, 1046 separated: bool = False, 1047 ) -> str: 1048 comments = ( 1049 ((expression and expression.comments) if comments is None else comments) # type: ignore 1050 if self.comments 1051 else None 1052 ) 1053 1054 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1055 return sql 1056 1057 comments_list = [ 1058 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1059 for comment in comments 1060 if comment 1061 ] 1062 1063 if not comments_list: 1064 return sql 1065 1066 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1067 comments_sql = self.sep().join(comments_list) 1068 return ( 1069 f"{self.sep()}{comments_sql}{sql}" 1070 if not sql or sql[0].isspace() 1071 else f"{comments_sql}{self.sep()}{sql}" 1072 ) 1073 1074 return f"{sql} {' '.join(comments_list)}" 1075 1076 def wrap(self, expression: exp.Expr | str) -> str: 1077 this_sql = ( 1078 self.sql(expression) 1079 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1080 else self.sql(expression, "this") 1081 ) 1082 if not this_sql: 1083 return "()" 1084 1085 this_sql = self.indent(this_sql, level=1, pad=0) 1086 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1087 1088 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1089 original = self.identify 1090 self.identify = False 1091 result = func(*args, **kwargs) 1092 self.identify = original 1093 return result 1094 1095 def normalize_func(self, name: str) -> str: 1096 if self.normalize_functions == "upper" or self.normalize_functions is True: 1097 return name.upper() 1098 if self.normalize_functions == "lower": 1099 return name.lower() 1100 return name 1101 1102 def indent( 1103 self, 1104 sql: str, 1105 level: int = 0, 1106 pad: int | None = None, 1107 skip_first: bool = False, 1108 skip_last: bool = False, 1109 ) -> str: 1110 if not self.pretty or not sql: 1111 return sql 1112 1113 pad = self.pad if pad is None else pad 1114 lines = sql.split("\n") 1115 1116 return "\n".join( 1117 ( 1118 line 1119 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1120 else f"{' ' * (level * self._indent + pad)}{line}" 1121 ) 1122 for i, line in enumerate(lines) 1123 ) 1124 1125 def sql( 1126 self, 1127 expression: str | exp.Expr | None, 1128 key: str | None = None, 1129 comment: bool = True, 1130 ) -> str: 1131 if not expression: 1132 return "" 1133 1134 if isinstance(expression, str): 1135 return expression 1136 1137 if key: 1138 value = expression.args.get(key) 1139 if value: 1140 return self.sql(value) 1141 return "" 1142 1143 handler = self._dispatch.get(expression.__class__) 1144 1145 if handler: 1146 sql = handler(self, expression) 1147 elif isinstance(expression, exp.Func): 1148 sql = self.function_fallback_sql(expression) 1149 elif isinstance(expression, exp.Property): 1150 sql = self.property_sql(expression) 1151 else: 1152 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1153 1154 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1155 1156 def uncache_sql(self, expression: exp.Uncache) -> str: 1157 table = self.sql(expression, "this") 1158 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1159 return f"UNCACHE TABLE{exists_sql} {table}" 1160 1161 def cache_sql(self, expression: exp.Cache) -> str: 1162 lazy = " LAZY" if expression.args.get("lazy") else "" 1163 table = self.sql(expression, "this") 1164 options = expression.args.get("options") 1165 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1166 sql = self.sql(expression, "expression") 1167 sql = f" AS{self.sep()}{sql}" if sql else "" 1168 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1169 return self.prepend_ctes(expression, sql) 1170 1171 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1172 default = "DEFAULT " if expression.args.get("default") else "" 1173 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1174 1175 def column_parts(self, expression: exp.Column) -> str: 1176 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1177 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1178 return self.sql(expression, "this") 1179 1180 return ".".join( 1181 self.sql(part) 1182 for part in ( 1183 expression.args.get("catalog"), 1184 expression.args.get("db"), 1185 expression.args.get("table"), 1186 expression.args.get("this"), 1187 ) 1188 if part 1189 ) 1190 1191 def column_sql(self, expression: exp.Column) -> str: 1192 join_mark = " (+)" if expression.args.get("join_mark") else "" 1193 1194 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1195 join_mark = "" 1196 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1197 1198 return f"{self.column_parts(expression)}{join_mark}" 1199 1200 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1201 return self.column_sql(expression) 1202 1203 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1204 this = self.sql(expression, "this") 1205 this = f" {this}" if this else "" 1206 position = self.sql(expression, "position") 1207 return f"{position}{this}" 1208 1209 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1210 column = self.sql(expression, "this") 1211 kind = self.sql(expression, "kind") 1212 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1213 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1214 kind = f"{sep}{kind}" if kind else "" 1215 constraints = f" {constraints}" if constraints else "" 1216 position = self.sql(expression, "position") 1217 position = f" {position}" if position else "" 1218 1219 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1220 kind = "" 1221 1222 return f"{exists}{column}{kind}{constraints}{position}" 1223 1224 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1225 this = self.sql(expression, "this") 1226 kind_sql = self.sql(expression, "kind").strip() 1227 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1228 1229 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1230 this = self.sql(expression, "this") 1231 if expression.args.get("not_null"): 1232 persisted = " PERSISTED NOT NULL" 1233 elif expression.args.get("persisted"): 1234 persisted = " PERSISTED" 1235 else: 1236 persisted = "" 1237 1238 return f"AS {this}{persisted}" 1239 1240 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1241 return self.token_sql(TokenType.AUTO_INCREMENT) 1242 1243 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1244 if isinstance(expression.this, list): 1245 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1246 else: 1247 this = self.sql(expression, "this") 1248 1249 return f"COMPRESS {this}" 1250 1251 def generatedasidentitycolumnconstraint_sql( 1252 self, expression: exp.GeneratedAsIdentityColumnConstraint 1253 ) -> str: 1254 this = "" 1255 if expression.this is not None: 1256 on_null = " ON NULL" if expression.args.get("on_null") else "" 1257 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1258 1259 start = expression.args.get("start") 1260 start = f"START WITH {start}" if start else "" 1261 increment = expression.args.get("increment") 1262 increment = f" INCREMENT BY {increment}" if increment else "" 1263 minvalue = expression.args.get("minvalue") 1264 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1265 maxvalue = expression.args.get("maxvalue") 1266 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1267 cycle = expression.args.get("cycle") 1268 cycle_sql = "" 1269 1270 if cycle is not None: 1271 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1272 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1273 1274 sequence_opts = "" 1275 if start or increment or cycle_sql: 1276 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1277 sequence_opts = f" ({sequence_opts.strip()})" 1278 1279 expr = self.sql(expression, "expression") 1280 expr = f"({expr})" if expr else "IDENTITY" 1281 1282 return f"GENERATED{this} AS {expr}{sequence_opts}" 1283 1284 def generatedasrowcolumnconstraint_sql( 1285 self, expression: exp.GeneratedAsRowColumnConstraint 1286 ) -> str: 1287 start = "START" if expression.args.get("start") else "END" 1288 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1289 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1290 1291 def periodforsystemtimeconstraint_sql( 1292 self, expression: exp.PeriodForSystemTimeConstraint 1293 ) -> str: 1294 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1295 1296 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1297 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1298 1299 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1300 desc = expression.args.get("desc") 1301 if desc is not None: 1302 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1303 options = self.expressions(expression, key="options", flat=True, sep=" ") 1304 options = f" {options}" if options else "" 1305 return f"PRIMARY KEY{options}" 1306 1307 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1308 this = self.sql(expression, "this") 1309 this = f" {this}" if this else "" 1310 index_type = expression.args.get("index_type") 1311 index_type = f" USING {index_type}" if index_type else "" 1312 on_conflict = self.sql(expression, "on_conflict") 1313 on_conflict = f" {on_conflict}" if on_conflict else "" 1314 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1315 options = self.expressions(expression, key="options", flat=True, sep=" ") 1316 options = f" {options}" if options else "" 1317 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1318 1319 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1320 input_ = expression.args.get("input_") 1321 output = expression.args.get("output") 1322 variadic = expression.args.get("variadic") 1323 1324 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1325 if variadic: 1326 return "VARIADIC" 1327 1328 if input_ and output: 1329 return f"IN{self.INOUT_SEPARATOR}OUT" 1330 if input_: 1331 return "IN" 1332 if output: 1333 return "OUT" 1334 1335 return "" 1336 1337 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1338 return self.sql(expression, "this") 1339 1340 def create_sql(self, expression: exp.Create) -> str: 1341 kind = self.sql(expression, "kind") 1342 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1343 1344 properties = expression.args.get("properties") 1345 1346 if ( 1347 kind == "TRIGGER" 1348 and properties 1349 and properties.expressions 1350 and isinstance(properties.expressions[0], exp.TriggerProperties) 1351 and properties.expressions[0].args.get("constraint") 1352 ): 1353 kind = f"CONSTRAINT {kind}" 1354 1355 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1356 1357 this = self.createable_sql(expression, properties_locs) 1358 1359 properties_sql = "" 1360 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1361 exp.Properties.Location.POST_WITH 1362 ): 1363 props_ast = exp.Properties( 1364 expressions=[ 1365 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1366 *properties_locs[exp.Properties.Location.POST_WITH], 1367 ] 1368 ) 1369 props_ast.parent = expression 1370 properties_sql = self.sql(props_ast) 1371 1372 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1373 properties_sql = self.sep() + properties_sql 1374 elif not self.pretty: 1375 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1376 properties_sql = f" {properties_sql}" 1377 1378 begin = " BEGIN" if expression.args.get("begin") else "" 1379 1380 expression_sql = self.sql(expression, "expression") 1381 if expression_sql: 1382 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1383 1384 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1385 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1386 ): 1387 postalias_props_sql = "" 1388 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1389 postalias_props_sql = self.properties( 1390 exp.Properties( 1391 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1392 ), 1393 wrapped=False, 1394 ) 1395 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1396 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1397 1398 postindex_props_sql = "" 1399 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1400 postindex_props_sql = self.properties( 1401 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1402 wrapped=False, 1403 prefix=" ", 1404 ) 1405 1406 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1407 indexes = f" {indexes}" if indexes else "" 1408 index_sql = indexes + postindex_props_sql 1409 1410 replace = " OR REPLACE" if expression.args.get("replace") else "" 1411 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1412 unique = " UNIQUE" if expression.args.get("unique") else "" 1413 1414 clustered = expression.args.get("clustered") 1415 if clustered is None: 1416 clustered_sql = "" 1417 elif clustered: 1418 clustered_sql = " CLUSTERED COLUMNSTORE" 1419 else: 1420 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1421 1422 postcreate_props_sql = "" 1423 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1424 postcreate_props_sql = self.properties( 1425 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1426 sep=" ", 1427 prefix=" ", 1428 wrapped=False, 1429 ) 1430 1431 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1432 1433 postexpression_props_sql = "" 1434 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1435 postexpression_props_sql = self.properties( 1436 exp.Properties( 1437 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1438 ), 1439 sep=" ", 1440 prefix=" ", 1441 wrapped=False, 1442 ) 1443 1444 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1445 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1446 no_schema_binding = ( 1447 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1448 ) 1449 1450 clone = self.sql(expression, "clone") 1451 clone = f" {clone}" if clone else "" 1452 1453 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1454 properties_expression = f"{expression_sql}{properties_sql}" 1455 else: 1456 properties_expression = f"{properties_sql}{expression_sql}" 1457 1458 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1459 return self.prepend_ctes(expression, expression_sql) 1460 1461 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1462 start = self.sql(expression, "start") 1463 start = f"START WITH {start}" if start else "" 1464 increment = self.sql(expression, "increment") 1465 increment = f" INCREMENT BY {increment}" if increment else "" 1466 minvalue = self.sql(expression, "minvalue") 1467 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1468 maxvalue = self.sql(expression, "maxvalue") 1469 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1470 owned = self.sql(expression, "owned") 1471 owned = f" OWNED BY {owned}" if owned else "" 1472 1473 cache = expression.args.get("cache") 1474 if cache is None: 1475 cache_str = "" 1476 elif cache is True: 1477 cache_str = " CACHE" 1478 else: 1479 cache_str = f" CACHE {cache}" 1480 1481 options = self.expressions(expression, key="options", flat=True, sep=" ") 1482 options = f" {options}" if options else "" 1483 1484 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1485 1486 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1487 timing = expression.args.get("timing", "") 1488 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1489 timing_events = f"{timing} {events}".strip() if timing or events else "" 1490 1491 parts = [timing_events, "ON", self.sql(expression, "table")] 1492 1493 if referenced_table := expression.args.get("referenced_table"): 1494 parts.extend(["FROM", self.sql(referenced_table)]) 1495 1496 if deferrable := expression.args.get("deferrable"): 1497 parts.append(deferrable) 1498 1499 if initially := expression.args.get("initially"): 1500 parts.append(f"INITIALLY {initially}") 1501 1502 if referencing := expression.args.get("referencing"): 1503 parts.append(self.sql(referencing)) 1504 1505 if for_each := expression.args.get("for_each"): 1506 parts.append(f"FOR EACH {for_each}") 1507 1508 if when := expression.args.get("when"): 1509 parts.append(f"WHEN ({self.sql(when)})") 1510 1511 parts.append(self.sql(expression, "execute")) 1512 1513 return self.sep().join(parts) 1514 1515 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1516 parts = [] 1517 1518 if old_alias := expression.args.get("old"): 1519 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1520 1521 if new_alias := expression.args.get("new"): 1522 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1523 1524 return f"REFERENCING {' '.join(parts)}" 1525 1526 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1527 columns = expression.args.get("columns") 1528 if columns: 1529 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1530 1531 return self.sql(expression, "this") 1532 1533 def clone_sql(self, expression: exp.Clone) -> str: 1534 this = self.sql(expression, "this") 1535 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1536 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1537 return f"{shallow}{keyword} {this}" 1538 1539 def describe_sql(self, expression: exp.Describe) -> str: 1540 style = expression.args.get("style") 1541 style = f" {style}" if style else "" 1542 partition = self.sql(expression, "partition") 1543 partition = f" {partition}" if partition else "" 1544 format = self.sql(expression, "format") 1545 format = f" {format}" if format else "" 1546 as_json = " AS JSON" if expression.args.get("as_json") else "" 1547 1548 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1549 1550 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1551 tag = self.sql(expression, "tag") 1552 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1553 1554 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1555 with_ = self.sql(expression, "with_") 1556 if with_: 1557 sql = f"{with_}{self.sep()}{sql}" 1558 return sql 1559 1560 def with_sql(self, expression: exp.With) -> str: 1561 udfs = self.expressions(expression, key="udfs", flat=True) 1562 udfs = f"WITH {udfs}" if udfs else "" 1563 1564 sql = self.expressions(expression, flat=True) 1565 1566 recursive = ( 1567 "RECURSIVE " 1568 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1569 else "" 1570 ) 1571 search = self.sql(expression, "search") 1572 search = f" {search}" if search else "" 1573 1574 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1575 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1576 1577 def cte_sql(self, expression: exp.CTE) -> str: 1578 alias = expression.args.get("alias") 1579 if alias: 1580 alias.add_comments(expression.pop_comments()) 1581 1582 alias_sql = self.sql(expression, "alias") 1583 1584 materialized = expression.args.get("materialized") 1585 if materialized is False: 1586 materialized = "NOT MATERIALIZED " 1587 elif materialized: 1588 materialized = "MATERIALIZED " 1589 1590 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1591 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1592 1593 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1594 1595 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1596 alias = self.sql(expression, "this") 1597 columns = self.expressions(expression, key="columns", flat=True) 1598 columns = f"({columns})" if columns else "" 1599 1600 if ( 1601 columns 1602 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1603 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1604 ): 1605 columns = "" 1606 self.unsupported("Named columns are not supported in table alias.") 1607 1608 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1609 alias = self._next_name() 1610 1611 return f"{alias}{columns}" 1612 1613 def bitstring_sql(self, expression: exp.BitString) -> str: 1614 this = self.sql(expression, "this") 1615 if self.dialect.BIT_START: 1616 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1617 return f"{int(this, 2)}" 1618 1619 def hexstring_sql( 1620 self, expression: exp.HexString, binary_function_repr: str | None = None 1621 ) -> str: 1622 this = self.sql(expression, "this") 1623 is_integer_type = expression.args.get("is_integer") 1624 1625 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1626 not self.dialect.HEX_START and not binary_function_repr 1627 ): 1628 # Integer representation will be returned if: 1629 # - The read dialect treats the hex value as integer literal but not the write 1630 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1631 return f"{int(this, 16)}" 1632 1633 if not is_integer_type: 1634 # Read dialect treats the hex value as BINARY/BLOB 1635 if binary_function_repr: 1636 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1637 return self.func(binary_function_repr, exp.Literal.string(this)) 1638 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1639 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1640 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1641 1642 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1643 1644 def bytestring_sql(self, expression: exp.ByteString) -> str: 1645 this = self.sql(expression, "this") 1646 if self.dialect.BYTE_START: 1647 escaped_byte_string = self.escape_str( 1648 this, 1649 escape_backslash=False, 1650 delimiter=self.dialect.BYTE_END, 1651 escaped_delimiter=self._escaped_byte_quote_end, 1652 is_byte_string=True, 1653 ) 1654 is_bytes = expression.args.get("is_bytes", False) 1655 delimited_byte_string = ( 1656 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1657 ) 1658 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1659 return self.sql( 1660 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1661 ) 1662 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1663 return self.sql( 1664 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1665 ) 1666 1667 return delimited_byte_string 1668 1669 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1670 return self.sql(exp.Literal.string(this)) 1671 1672 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1673 return "" 1674 1675 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1676 this = self.sql(expression, "this") 1677 escape = expression.args.get("escape") 1678 unicode_start = self.dialect.UNICODE_START 1679 1680 if unicode_start: 1681 escape_substitute = r"\\\1" 1682 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1683 else: 1684 escape_substitute = r"\\u\1" 1685 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1686 1687 if escape: 1688 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1689 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1690 else: 1691 escape_pattern = ESCAPED_UNICODE_RE 1692 escape_sql = "" 1693 1694 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1695 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1696 1697 if unicode_start: 1698 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1699 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1700 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1701 else: 1702 this = self.escape_str(this, escape_backslash=False) 1703 1704 return f"{left_quote}{this}{right_quote}{escape_sql}" 1705 1706 def rawstring_sql(self, expression: exp.RawString) -> str: 1707 string = expression.this 1708 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1709 string = string.replace("\\", "\\\\") 1710 1711 string = self.escape_str(string, escape_backslash=False) 1712 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1713 1714 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1715 this = self.sql(expression, "this") 1716 specifier = self.sql(expression, "expression") 1717 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1718 return f"{this}{specifier}" 1719 1720 def datatype_param_bound_limiter( 1721 self, 1722 expression: exp.DataType, 1723 type_value: exp.DType, 1724 defaults: tuple[int, ...], 1725 bounds: tuple[int | None, ...], 1726 ) -> exp.DataType: 1727 params = expression.expressions 1728 1729 if not params: 1730 if defaults: 1731 expression.set( 1732 "expressions", 1733 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1734 ) 1735 return expression 1736 1737 if not bounds: 1738 return expression 1739 1740 for i, param in enumerate(params): 1741 bound = bounds[i] if i < len(bounds) else None 1742 if bound is None: 1743 continue 1744 1745 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1746 value = ( 1747 param_value.to_py() 1748 if isinstance(param_value, exp.Literal) and param_value.is_number 1749 else None 1750 ) 1751 if isinstance(value, (int, Decimal)) and value > bound: 1752 self.unsupported( 1753 f"{type_value.value} parameter {param_value.name} exceeds " 1754 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1755 ) 1756 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1757 1758 return expression 1759 1760 def datatype_sql(self, expression: exp.DataType) -> str: 1761 nested = "" 1762 values = "" 1763 1764 expr_nested = expression.args.get("nested") 1765 type_value = expression.this 1766 1767 if ( 1768 not expr_nested 1769 and isinstance(type_value, exp.DType) 1770 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1771 ): 1772 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1773 1774 interior = ( 1775 self.expressions( 1776 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1777 ) 1778 if expr_nested and self.pretty 1779 else self.expressions(expression, flat=True) 1780 ) 1781 1782 if type_value in self.UNSUPPORTED_TYPES: 1783 self.unsupported( 1784 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1785 ) 1786 1787 type_sql: t.Any = "" 1788 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1789 type_sql = self.sql(expression, "kind") 1790 elif type_value == exp.DType.CHARACTER_SET: 1791 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1792 else: 1793 type_sql = ( 1794 self.TYPE_MAPPING.get(type_value, type_value.value) 1795 if isinstance(type_value, exp.DType) 1796 else type_value 1797 ) 1798 1799 if interior: 1800 if expr_nested: 1801 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1802 if expression.args.get("values") is not None: 1803 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1804 values = self.expressions(expression, key="values", flat=True) 1805 values = f"{delimiters[0]}{values}{delimiters[1]}" 1806 elif type_value == exp.DType.INTERVAL: 1807 nested = f" {interior}" 1808 else: 1809 nested = f"({interior})" 1810 1811 type_sql = f"{type_sql}{nested}{values}" 1812 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1813 exp.DType.TIMETZ, 1814 exp.DType.TIMESTAMPTZ, 1815 ): 1816 type_sql = f"{type_sql} WITH TIME ZONE" 1817 1818 collate = self.sql(expression, "collate") 1819 if collate: 1820 type_sql = f"{type_sql} COLLATE {collate}" 1821 1822 return type_sql 1823 1824 def directory_sql(self, expression: exp.Directory) -> str: 1825 local = "LOCAL " if expression.args.get("local") else "" 1826 row_format = self.sql(expression, "row_format") 1827 row_format = f" {row_format}" if row_format else "" 1828 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1829 1830 def delete_sql(self, expression: exp.Delete) -> str: 1831 hint = self.sql(expression, "hint") 1832 this = self.sql(expression, "this") 1833 this = f" FROM {this}" if this else "" 1834 using = self.expressions(expression, key="using") 1835 using = f" USING {using}" if using else "" 1836 cluster = self.sql(expression, "cluster") 1837 cluster = f" {cluster}" if cluster else "" 1838 where = self.sql(expression, "where") 1839 returning = self.sql(expression, "returning") 1840 order = self.sql(expression, "order") 1841 limit = self.sql(expression, "limit") 1842 tables = self.expressions(expression, key="tables") 1843 tables = f" {tables}" if tables else "" 1844 if self.RETURNING_END: 1845 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1846 else: 1847 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1848 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1849 1850 def drop_sql(self, expression: exp.Drop) -> str: 1851 tables = self.expressions(expression, key="tables", flat=True) 1852 expressions = self.expressions(expression, flat=True) 1853 expressions = f" ({expressions})" if expressions else "" 1854 kind = expression.args["kind"] 1855 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1856 iceberg = ( 1857 " ICEBERG" 1858 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1859 else "" 1860 ) 1861 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1862 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1863 on_cluster = self.sql(expression, "cluster") 1864 on_cluster = f" {on_cluster}" if on_cluster else "" 1865 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1866 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1867 cascade = " CASCADE" if expression.args.get("cascade") else "" 1868 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1869 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1870 purge = " PURGE" if expression.args.get("purge") else "" 1871 sync = " SYNC" if expression.args.get("sync") else "" 1872 force = " FORCE" if expression.args.get("force") else "" 1873 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1874 1875 def set_operation(self, expression: exp.SetOperation) -> str: 1876 op_type = type(expression) 1877 op_name = op_type.key.upper() 1878 1879 distinct = expression.args.get("distinct") 1880 if ( 1881 distinct is False 1882 and op_type in (exp.Except, exp.Intersect) 1883 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1884 ): 1885 self.unsupported(f"{op_name} ALL is not supported") 1886 1887 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1888 1889 if distinct is None: 1890 distinct = default_distinct 1891 if distinct is None: 1892 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1893 1894 if distinct is default_distinct: 1895 distinct_or_all = "" 1896 else: 1897 distinct_or_all = " DISTINCT" if distinct else " ALL" 1898 1899 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1900 side_kind = f"{side_kind} " if side_kind else "" 1901 1902 by_name = " BY NAME" if expression.args.get("by_name") else "" 1903 on = self.expressions(expression, key="on", flat=True) 1904 on = f" ON ({on})" if on else "" 1905 1906 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1907 1908 def set_operations(self, expression: exp.SetOperation) -> str: 1909 if not self.SET_OP_MODIFIERS: 1910 limit = expression.args.get("limit") 1911 order = expression.args.get("order") 1912 offset = expression.args.get("offset") 1913 1914 if limit or order or offset: 1915 select = self._move_ctes_to_top_level( 1916 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1917 ) 1918 1919 for arg in ("limit", "order", "offset"): 1920 if value := expression.args.get(arg): 1921 select.set(arg, value.pop()) 1922 return self.sql(select) 1923 1924 sqls: list[str] = [] 1925 stack: list[str | exp.Expr] = [expression] 1926 1927 while stack: 1928 node = stack.pop() 1929 1930 if isinstance(node, exp.SetOperation): 1931 stack.append(node.expression) 1932 stack.append( 1933 self.maybe_comment( 1934 self.set_operation(node), comments=node.comments, separated=True 1935 ) 1936 ) 1937 stack.append(node.this) 1938 else: 1939 if ( 1940 not self.SET_OP_LIMITS 1941 and isinstance(node, exp.Select) 1942 and node.args.get("limit") 1943 ): 1944 node = node.subquery(copy=False) 1945 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1946 node = exp.select("*").from_(node, copy=False) 1947 sqls.append(self.sql(node)) 1948 1949 this = self.sep().join(sqls) 1950 this = self.query_modifiers(expression, this) 1951 return self.prepend_ctes(expression, this) 1952 1953 def fetch_sql(self, expression: exp.Fetch) -> str: 1954 direction = expression.args.get("direction") 1955 direction = f" {direction}" if direction else "" 1956 count = self.sql(expression, "count") 1957 count = f" {count}" if count else "" 1958 limit_options = self.sql(expression, "limit_options") 1959 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1960 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1961 1962 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1963 percent = " PERCENT" if expression.args.get("percent") else "" 1964 rows = " ROWS" if expression.args.get("rows") else "" 1965 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1966 if not with_ties and rows: 1967 with_ties = " ONLY" 1968 return f"{percent}{rows}{with_ties}" 1969 1970 def filter_sql(self, expression: exp.Filter) -> str: 1971 this = self.sql(expression, "this") 1972 where = self.sql(expression, "expression").strip() 1973 return f"{this} FILTER({where})" 1974 1975 def hint_sql(self, expression: exp.Hint) -> str: 1976 if not self.QUERY_HINTS: 1977 self.unsupported("Hints are not supported") 1978 return "" 1979 1980 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1981 1982 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1983 using = self.sql(expression, "using") 1984 using = f" USING {using}" if using else "" 1985 columns = self.expressions(expression, key="columns", flat=True) 1986 columns = f"({columns})" if columns else "" 1987 partition_by = self.expressions(expression, key="partition_by", flat=True) 1988 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1989 where = self.sql(expression, "where") 1990 include = self.expressions(expression, key="include", flat=True) 1991 if include: 1992 include = f" INCLUDE ({include})" 1993 with_storage = self.expressions(expression, key="with_storage", flat=True) 1994 with_storage = f" WITH ({with_storage})" if with_storage else "" 1995 tablespace = self.sql(expression, "tablespace") 1996 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1997 on = self.sql(expression, "on") 1998 on = f" ON {on}" if on else "" 1999 2000 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2001 2002 def index_sql(self, expression: exp.Index) -> str: 2003 unique = "UNIQUE " if expression.args.get("unique") else "" 2004 primary = "PRIMARY " if expression.args.get("primary") else "" 2005 amp = "AMP " if expression.args.get("amp") else "" 2006 name = self.sql(expression, "this") 2007 name = f"{name} " if name else "" 2008 table = self.sql(expression, "table") 2009 table = f"{self.INDEX_ON} {table}" if table else "" 2010 2011 index = "INDEX " if not table else "" 2012 2013 params = self.sql(expression, "params") 2014 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2015 2016 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2017 this = expression.this 2018 if this and this.is_string: 2019 resolved = maybe_parse(this.name).sql(self.dialect) 2020 if "expressions" in expression.args: 2021 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2022 # We can't safely emit the call to other dialects since name/arg semantics may differ 2023 self.unsupported( 2024 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2025 ) 2026 return resolved 2027 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2028 return self.func("IDENTIFIER", this) 2029 2030 def identifier_sql(self, expression: exp.Identifier) -> str: 2031 text = expression.name 2032 lower = text.lower() 2033 quoted = expression.quoted 2034 text = lower if self.normalize and not quoted else text 2035 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2036 if ( 2037 quoted 2038 or self.dialect.can_quote(expression, self.identify) 2039 or lower in self.RESERVED_KEYWORDS 2040 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2041 ): 2042 text = ( 2043 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2044 ) 2045 return text 2046 2047 def hex_sql(self, expression: exp.Hex) -> str: 2048 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2049 if self.dialect.HEX_LOWERCASE: 2050 text = self.func("LOWER", text) 2051 2052 return text 2053 2054 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2055 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2056 if not self.dialect.HEX_LOWERCASE: 2057 text = self.func("LOWER", text) 2058 return text 2059 2060 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2061 input_format = self.sql(expression, "input_format") 2062 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2063 output_format = self.sql(expression, "output_format") 2064 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2065 return self.sep().join((input_format, output_format)) 2066 2067 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2068 string = self.sql(exp.Literal.string(expression.name)) 2069 return f"{prefix}{string}" 2070 2071 def partition_sql(self, expression: exp.Partition) -> str: 2072 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2073 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2074 2075 def properties_sql(self, expression: exp.Properties) -> str: 2076 root_properties = [] 2077 with_properties = [] 2078 2079 for p in expression.expressions: 2080 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2081 if p_loc == exp.Properties.Location.POST_WITH: 2082 with_properties.append(p) 2083 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2084 root_properties.append(p) 2085 2086 root_props_ast = exp.Properties(expressions=root_properties) 2087 root_props_ast.parent = expression.parent 2088 2089 with_props_ast = exp.Properties(expressions=with_properties) 2090 with_props_ast.parent = expression.parent 2091 2092 root_props = self.root_properties(root_props_ast) 2093 with_props = self.with_properties(with_props_ast) 2094 2095 if root_props and with_props and not self.pretty: 2096 with_props = " " + with_props 2097 2098 return root_props + with_props 2099 2100 def root_properties(self, properties: exp.Properties) -> str: 2101 if properties.expressions: 2102 return self.expressions(properties, indent=False, sep=" ") 2103 return "" 2104 2105 def properties( 2106 self, 2107 properties: exp.Properties, 2108 prefix: str = "", 2109 sep: str = ", ", 2110 suffix: str = "", 2111 wrapped: bool = True, 2112 ) -> str: 2113 if properties.expressions: 2114 expressions = self.expressions(properties, sep=sep, indent=False) 2115 if expressions: 2116 expressions = self.wrap(expressions) if wrapped else expressions 2117 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2118 return "" 2119 2120 def with_properties(self, properties: exp.Properties) -> str: 2121 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2122 2123 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2124 properties_locs = defaultdict(list) 2125 for p in properties.expressions: 2126 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2127 if p_loc != exp.Properties.Location.UNSUPPORTED: 2128 properties_locs[p_loc].append(p) 2129 else: 2130 self.unsupported(f"Unsupported property {p.key}") 2131 2132 return properties_locs 2133 2134 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2135 if isinstance(expression.this, exp.Dot): 2136 return self.sql(expression, "this") 2137 return f"'{expression.name}'" if string_key else expression.name 2138 2139 def property_sql(self, expression: exp.Property) -> str: 2140 property_cls = expression.__class__ 2141 if property_cls == exp.Property: 2142 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2143 2144 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2145 if not property_name: 2146 self.unsupported(f"Unsupported property {expression.key}") 2147 2148 return f"{property_name}={self.sql(expression, 'this')}" 2149 2150 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2151 return f"UUID {self.sql(expression, 'this')}" 2152 2153 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2154 if self.SUPPORTS_CREATE_TABLE_LIKE: 2155 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2156 options = f" {options}" if options else "" 2157 2158 like = f"LIKE {self.sql(expression, 'this')}{options}" 2159 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2160 like = f"({like})" 2161 2162 return like 2163 2164 if expression.expressions: 2165 self.unsupported("Transpilation of LIKE property options is unsupported") 2166 2167 select = exp.select("*").from_(expression.this).limit(0) 2168 return f"AS {self.sql(select)}" 2169 2170 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2171 no = "NO " if expression.args.get("no") else "" 2172 protection = " PROTECTION" if expression.args.get("protection") else "" 2173 return f"{no}FALLBACK{protection}" 2174 2175 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2176 no = "NO " if expression.args.get("no") else "" 2177 local = expression.args.get("local") 2178 local = f"{local} " if local else "" 2179 dual = "DUAL " if expression.args.get("dual") else "" 2180 before = "BEFORE " if expression.args.get("before") else "" 2181 after = "AFTER " if expression.args.get("after") else "" 2182 return f"{no}{local}{dual}{before}{after}JOURNAL" 2183 2184 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2185 freespace = self.sql(expression, "this") 2186 percent = " PERCENT" if expression.args.get("percent") else "" 2187 return f"FREESPACE={freespace}{percent}" 2188 2189 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2190 if expression.args.get("default"): 2191 property = "DEFAULT" 2192 elif expression.args.get("on"): 2193 property = "ON" 2194 else: 2195 property = "OFF" 2196 return f"CHECKSUM={property}" 2197 2198 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2199 if expression.args.get("no"): 2200 return "NO MERGEBLOCKRATIO" 2201 if expression.args.get("default"): 2202 return "DEFAULT MERGEBLOCKRATIO" 2203 2204 percent = " PERCENT" if expression.args.get("percent") else "" 2205 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2206 2207 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2208 expressions = self.expressions(expression, flat=True) 2209 expressions = f"({expressions})" if expressions else "" 2210 return f"USING {self.sql(expression, 'this')}{expressions}" 2211 2212 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2213 default = expression.args.get("default") 2214 minimum = expression.args.get("minimum") 2215 maximum = expression.args.get("maximum") 2216 if default or minimum or maximum: 2217 if default: 2218 prop = "DEFAULT" 2219 elif minimum: 2220 prop = "MINIMUM" 2221 else: 2222 prop = "MAXIMUM" 2223 return f"{prop} DATABLOCKSIZE" 2224 units = expression.args.get("units") 2225 units = f" {units}" if units else "" 2226 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2227 2228 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2229 autotemp = expression.args.get("autotemp") 2230 always = expression.args.get("always") 2231 default = expression.args.get("default") 2232 manual = expression.args.get("manual") 2233 never = expression.args.get("never") 2234 2235 if autotemp is not None: 2236 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2237 elif always: 2238 prop = "ALWAYS" 2239 elif default: 2240 prop = "DEFAULT" 2241 elif manual: 2242 prop = "MANUAL" 2243 elif never: 2244 prop = "NEVER" 2245 return f"BLOCKCOMPRESSION={prop}" 2246 2247 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2248 no = expression.args.get("no") 2249 no = " NO" if no else "" 2250 concurrent = expression.args.get("concurrent") 2251 concurrent = " CONCURRENT" if concurrent else "" 2252 target = self.sql(expression, "target") 2253 target = f" {target}" if target else "" 2254 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2255 2256 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2257 if isinstance(expression.this, list): 2258 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2259 if expression.this: 2260 modulus = self.sql(expression, "this") 2261 remainder = self.sql(expression, "expression") 2262 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2263 2264 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2265 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2266 return f"FROM ({from_expressions}) TO ({to_expressions})" 2267 2268 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2269 this = self.sql(expression, "this") 2270 2271 for_values_or_default = expression.expression 2272 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2273 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2274 else: 2275 for_values_or_default = " DEFAULT" 2276 2277 return f"PARTITION OF {this}{for_values_or_default}" 2278 2279 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2280 kind = expression.args.get("kind") 2281 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2282 for_or_in = expression.args.get("for_or_in") 2283 for_or_in = f" {for_or_in}" if for_or_in else "" 2284 lock_type = expression.args.get("lock_type") 2285 override = " OVERRIDE" if expression.args.get("override") else "" 2286 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2287 2288 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2289 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2290 statistics = expression.args.get("statistics") 2291 statistics_sql = "" 2292 if statistics is not None: 2293 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2294 return f"{data_sql}{statistics_sql}" 2295 2296 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2297 this = self.sql(expression, "this") 2298 this = f"HISTORY_TABLE={this}" if this else "" 2299 data_consistency: str | None = self.sql(expression, "data_consistency") 2300 data_consistency = ( 2301 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2302 ) 2303 retention_period: str | None = self.sql(expression, "retention_period") 2304 retention_period = ( 2305 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2306 ) 2307 2308 if this: 2309 on_sql = self.func("ON", this, data_consistency, retention_period) 2310 else: 2311 on_sql = "ON" if expression.args.get("on") else "OFF" 2312 2313 sql = f"SYSTEM_VERSIONING={on_sql}" 2314 2315 return f"WITH({sql})" if expression.args.get("with_") else sql 2316 2317 def insert_sql(self, expression: exp.Insert) -> str: 2318 hint = self.sql(expression, "hint") 2319 overwrite = expression.args.get("overwrite") 2320 2321 if isinstance(expression.this, exp.Directory): 2322 this = " OVERWRITE" if overwrite else " INTO" 2323 else: 2324 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2325 2326 stored = self.sql(expression, "stored") 2327 stored = f" {stored}" if stored else "" 2328 alternative = expression.args.get("alternative") 2329 alternative = f" OR {alternative}" if alternative else "" 2330 ignore = " IGNORE" if expression.args.get("ignore") else "" 2331 is_function = expression.args.get("is_function") 2332 if is_function: 2333 this = f"{this} FUNCTION" 2334 this = f"{this} {self.sql(expression, 'this')}" 2335 2336 exists = " IF EXISTS" if expression.args.get("exists") else "" 2337 where = self.sql(expression, "where") 2338 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2339 using = self.expressions(expression, key="using", flat=True) 2340 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2341 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2342 on_conflict = self.sql(expression, "conflict") 2343 on_conflict = f" {on_conflict}" if on_conflict else "" 2344 by_name = " BY NAME" if expression.args.get("by_name") else "" 2345 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2346 returning = self.sql(expression, "returning") 2347 2348 if self.RETURNING_END: 2349 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2350 else: 2351 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2352 2353 partition_by = self.sql(expression, "partition") 2354 partition_by = f" {partition_by}" if partition_by else "" 2355 settings = self.sql(expression, "settings") 2356 settings = f" {settings}" if settings else "" 2357 2358 source = self.sql(expression, "source") 2359 source = f"TABLE {source}" if source else "" 2360 2361 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2362 return self.prepend_ctes(expression, sql) 2363 2364 def introducer_sql(self, expression: exp.Introducer) -> str: 2365 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2366 2367 def kill_sql(self, expression: exp.Kill) -> str: 2368 kind = self.sql(expression, "kind") 2369 kind = f" {kind}" if kind else "" 2370 this = self.sql(expression, "this") 2371 this = f" {this}" if this else "" 2372 return f"KILL{kind}{this}" 2373 2374 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2375 return expression.name 2376 2377 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2378 return expression.name 2379 2380 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2381 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2382 2383 constraint = self.sql(expression, "constraint") 2384 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2385 2386 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2387 if conflict_keys: 2388 conflict_keys = f"({conflict_keys})" 2389 2390 index_predicate = self.sql(expression, "index_predicate") 2391 conflict_keys = f"{conflict_keys}{index_predicate} " 2392 2393 action = self.sql(expression, "action") 2394 2395 expressions = self.expressions(expression, flat=True) 2396 if expressions: 2397 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2398 expressions = f" {set_keyword}{expressions}" 2399 2400 where = self.sql(expression, "where") 2401 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2402 2403 def returning_sql(self, expression: exp.Returning) -> str: 2404 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2405 2406 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2407 fields = self.sql(expression, "fields") 2408 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2409 escaped = self.sql(expression, "escaped") 2410 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2411 items = self.sql(expression, "collection_items") 2412 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2413 keys = self.sql(expression, "map_keys") 2414 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2415 lines = self.sql(expression, "lines") 2416 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2417 null = self.sql(expression, "null") 2418 null = f" NULL DEFINED AS {null}" if null else "" 2419 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2420 2421 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2422 return f"WITH ({self.expressions(expression, flat=True)})" 2423 2424 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2425 this = f"{self.sql(expression, 'this')} INDEX" 2426 target = self.sql(expression, "target") 2427 target = f" FOR {target}" if target else "" 2428 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2429 2430 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2431 this = self.sql(expression, "this") 2432 kind = self.sql(expression, "kind") 2433 expr = self.sql(expression, "expression") 2434 return f"{this} ({kind} => {expr})" 2435 2436 def table_parts(self, expression: exp.Table) -> str: 2437 return ".".join( 2438 self.sql(part) 2439 for part in ( 2440 expression.args.get("catalog"), 2441 expression.args.get("db"), 2442 expression.args.get("this"), 2443 ) 2444 if part is not None 2445 ) 2446 2447 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2448 table = self.table_parts(expression) 2449 only = "ONLY " if expression.args.get("only") else "" 2450 partition = self.sql(expression, "partition") 2451 partition = f" {partition}" if partition else "" 2452 version = self.sql(expression, "version") 2453 version = f" {version}" if version else "" 2454 alias = self.sql(expression, "alias") 2455 alias = f"{sep}{alias}" if alias else "" 2456 2457 sample = self.sql(expression, "sample") 2458 post_alias = "" 2459 pre_alias = "" 2460 2461 if self.dialect.ALIAS_POST_TABLESAMPLE: 2462 pre_alias = sample 2463 else: 2464 post_alias = sample 2465 2466 if self.dialect.ALIAS_POST_VERSION: 2467 pre_alias = f"{pre_alias}{version}" 2468 else: 2469 post_alias = f"{post_alias}{version}" 2470 2471 hints = self.expressions(expression, key="hints", sep=" ") 2472 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2473 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2474 joins = self.indent( 2475 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2476 ) 2477 laterals = self.expressions(expression, key="laterals", sep="") 2478 2479 file_format = self.sql(expression, "format") 2480 pattern = self.sql(expression, "pattern") 2481 if file_format: 2482 pattern = f", PATTERN => {pattern}" if pattern else "" 2483 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2484 elif pattern: 2485 file_format = f" (PATTERN => {pattern})" 2486 2487 ordinality = expression.args.get("ordinality") or "" 2488 if ordinality: 2489 ordinality = f" WITH ORDINALITY{alias}" 2490 alias = "" 2491 2492 when = self.sql(expression, "when") 2493 if when: 2494 if self.HISTORICAL_DATA_POST_ALIAS: 2495 alias = f"{alias} {when}" 2496 else: 2497 table = f"{table} {when}" 2498 2499 changes = self.sql(expression, "changes") 2500 changes = f" {changes}" if changes else "" 2501 2502 rows_from = self.expressions(expression, key="rows_from") 2503 if rows_from: 2504 table = f"ROWS FROM {self.wrap(rows_from)}" 2505 2506 indexed = expression.args.get("indexed") 2507 if indexed is not None: 2508 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2509 else: 2510 indexed = "" 2511 2512 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2513 2514 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2515 table = self.func("TABLE", expression.this) 2516 alias = self.sql(expression, "alias") 2517 alias = f" AS {alias}" if alias else "" 2518 sample = self.sql(expression, "sample") 2519 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2520 joins = self.indent( 2521 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2522 ) 2523 return f"{table}{alias}{pivots}{sample}{joins}" 2524 2525 def tablesample_sql( 2526 self, 2527 expression: exp.TableSample, 2528 tablesample_keyword: str | None = None, 2529 ) -> str: 2530 method = self.sql(expression, "method") 2531 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2532 numerator = self.sql(expression, "bucket_numerator") 2533 denominator = self.sql(expression, "bucket_denominator") 2534 field = self.sql(expression, "bucket_field") 2535 field = f" ON {field}" if field else "" 2536 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2537 seed = self.sql(expression, "seed") 2538 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2539 2540 size = self.sql(expression, "size") 2541 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2542 size = f"{size} ROWS" 2543 2544 percent = self.sql(expression, "percent") 2545 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2546 percent = f"{percent} PERCENT" 2547 2548 expr = f"{bucket}{percent}{size}" 2549 if self.TABLESAMPLE_REQUIRES_PARENS: 2550 expr = f"({expr})" 2551 2552 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2553 2554 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2555 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2556 # the stored column name differs from the target dialect's natural output. 2557 columns = expression.args.get("columns") 2558 if not columns or len(expression.fields) != 1: 2559 return None 2560 2561 args = expression.args 2562 parser_cls = self.dialect.parser_class 2563 2564 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2565 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2566 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2567 2568 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2569 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2570 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2571 2572 if ( 2573 src_identify_pivot_strings == tgt_identify_pivot_strings 2574 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2575 and src_pivot_column_naming == tgt_pivot_column_naming 2576 ): 2577 return None 2578 2579 in_exprs = expression.fields[0].expressions 2580 step = len(columns) // len(in_exprs) 2581 2582 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2583 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2584 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2585 first_stored = columns[0].name 2586 2587 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2588 if not first_stored.startswith(first_base): 2589 return None 2590 2591 suffix = first_stored[len(first_base) :] 2592 2593 # Whether the target dialect would append an agg-name suffix for this pivot. 2594 # Spark single-agg uniquely drops the agg alias entirely. 2595 target_has_suffix = ( 2596 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2597 ) and any(a.alias for a in expression.expressions) 2598 source_has_suffix = suffix != "" 2599 2600 new_exprs: list[exp.Expression] = [] 2601 modified = False 2602 for val_idx, e in enumerate(in_exprs): 2603 if isinstance(e, exp.PivotAlias): 2604 new_exprs.append(e) 2605 continue 2606 2607 i = val_idx * step 2608 stored_full = columns[i].name 2609 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2610 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2611 2612 # Source had a suffix, but target won't apply one 2613 if source_has_suffix and not target_has_suffix: 2614 new_exprs.append( 2615 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2616 ) 2617 modified = True 2618 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2619 elif stored_value != target_value: 2620 new_exprs.append( 2621 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2622 ) 2623 modified = True 2624 else: 2625 new_exprs.append(e) 2626 2627 return new_exprs if modified else None 2628 2629 def pivot_sql(self, expression: exp.Pivot) -> str: 2630 expressions = self.expressions(expression, flat=True) 2631 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2632 2633 group = self.sql(expression, "group") 2634 2635 if expression.this: 2636 this = self.sql(expression, "this") 2637 if not expressions: 2638 sql = f"UNPIVOT {this}" 2639 else: 2640 on = f"{self.seg('ON')} {expressions}" 2641 into = self.sql(expression, "into") 2642 into = f"{self.seg('INTO')} {into}" if into else "" 2643 using = self.expressions(expression, key="using", flat=True) 2644 using = f"{self.seg('USING')} {using}" if using else "" 2645 sql = f"{direction} {this}{on}{into}{using}{group}" 2646 return self.prepend_ctes(expression, sql) 2647 2648 if not expression.unpivot: 2649 # Wrap IN-list values with explicit aliases where the target dialect would differ 2650 new_field_exprs = self._pivot_in_value_aliases(expression) 2651 if new_field_exprs is not None: 2652 expression.fields[0].set("expressions", new_field_exprs) 2653 2654 alias = self.sql(expression, "alias") 2655 if alias: 2656 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2657 2658 fields = self.expressions( 2659 expression, 2660 "fields", 2661 sep=" ", 2662 dynamic=True, 2663 new_line=True, 2664 skip_first=True, 2665 skip_last=True, 2666 ) 2667 2668 include_nulls = expression.args.get("include_nulls") 2669 if include_nulls is not None: 2670 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2671 else: 2672 nulls = "" 2673 2674 default_on_null = self.sql(expression, "default_on_null") 2675 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2676 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2677 return self.prepend_ctes(expression, sql) 2678 2679 def version_sql(self, expression: exp.Version) -> str: 2680 this = f"FOR {expression.name}" 2681 kind = expression.text("kind") 2682 expr = self.sql(expression, "expression") 2683 return f"{this} {kind} {expr}" 2684 2685 def tuple_sql(self, expression: exp.Tuple) -> str: 2686 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2687 2688 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2689 """ 2690 Returns (join_sql, from_sql) for UPDATE statements. 2691 - join_sql: placed after UPDATE table, before SET 2692 - from_sql: placed after SET clause (standard position) 2693 Dialects like MySQL need to convert FROM to JOIN syntax. 2694 """ 2695 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2696 return ("", self.sql(expression, "from_")) 2697 2698 # Qualify unqualified columns in SET clause with the target table 2699 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2700 target_table = expression.this 2701 if isinstance(target_table, exp.Table): 2702 target_name = exp.to_identifier(target_table.alias_or_name) 2703 for eq in expression.expressions: 2704 col = eq.this 2705 if isinstance(col, exp.Column) and not col.table: 2706 col.set("table", target_name) 2707 2708 table = from_expr.this 2709 if nested_joins := table.args.get("joins", []): 2710 table.set("joins", None) 2711 2712 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2713 for nested in nested_joins: 2714 if not nested.args.get("on") and not nested.args.get("using"): 2715 nested.set("on", exp.true()) 2716 join_sql += self.sql(nested) 2717 2718 return (join_sql, "") 2719 2720 def update_sql(self, expression: exp.Update) -> str: 2721 hint = self.sql(expression, "hint") 2722 this = self.sql(expression, "this") 2723 join_sql, from_sql = self._update_from_joins_sql(expression) 2724 set_sql = self.expressions(expression, flat=True) 2725 where_sql = self.sql(expression, "where") 2726 returning = self.sql(expression, "returning") 2727 order = self.sql(expression, "order") 2728 limit = self.sql(expression, "limit") 2729 if self.RETURNING_END: 2730 expression_sql = f"{from_sql}{where_sql}{returning}" 2731 else: 2732 expression_sql = f"{returning}{from_sql}{where_sql}" 2733 options = self.expressions(expression, key="options") 2734 options = f" OPTION({options})" if options else "" 2735 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2736 return self.prepend_ctes(expression, sql) 2737 2738 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2739 values_as_table = values_as_table and self.VALUES_AS_TABLE 2740 2741 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2742 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2743 args = self.expressions(expression) 2744 alias = self.sql(expression, "alias") 2745 values = f"VALUES{self.seg('')}{args}" 2746 values = ( 2747 f"({values})" 2748 if self.WRAP_DERIVED_VALUES 2749 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2750 else values 2751 ) 2752 values = self.query_modifiers(expression, values) 2753 return f"{values} AS {alias}" if alias else values 2754 2755 # Converts `VALUES...` expression into a series of select unions. 2756 alias_node = expression.args.get("alias") 2757 column_names = alias_node and alias_node.columns 2758 2759 selects: list[exp.Query] = [] 2760 2761 for i, tup in enumerate(expression.expressions): 2762 row = tup.expressions 2763 2764 if i == 0 and column_names: 2765 row = [ 2766 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2767 ] 2768 2769 selects.append(exp.Select(expressions=row)) 2770 2771 if self.pretty: 2772 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2773 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2774 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2775 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2776 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2777 2778 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2779 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2780 return f"({unions}){alias}" 2781 2782 def var_sql(self, expression: exp.Var) -> str: 2783 return self.sql(expression, "this") 2784 2785 @unsupported_args("expressions") 2786 def into_sql(self, expression: exp.Into) -> str: 2787 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2788 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2789 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2790 2791 def from_sql(self, expression: exp.From) -> str: 2792 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2793 2794 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2795 grouping_sets = self.expressions(expression, indent=False) 2796 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2797 2798 def rollup_sql(self, expression: exp.Rollup) -> str: 2799 expressions = self.expressions(expression, indent=False) 2800 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2801 2802 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2803 this = self.sql(expression, "this") 2804 2805 columns = self.expressions(expression, flat=True) 2806 2807 from_sql = self.sql(expression, "from_index") 2808 from_sql = f" FROM {from_sql}" if from_sql else "" 2809 2810 properties = expression.args.get("properties") 2811 properties_sql = ( 2812 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2813 ) 2814 2815 return f"{this}({columns}){from_sql}{properties_sql}" 2816 2817 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2818 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2819 2820 def cube_sql(self, expression: exp.Cube) -> str: 2821 expressions = self.expressions(expression, indent=False) 2822 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2823 2824 def group_sql(self, expression: exp.Group) -> str: 2825 group_by_all = expression.args.get("all") 2826 if group_by_all is True: 2827 modifier = " ALL" 2828 elif group_by_all is False: 2829 modifier = " DISTINCT" 2830 else: 2831 modifier = "" 2832 2833 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2834 2835 grouping_sets = self.expressions(expression, key="grouping_sets") 2836 cube = self.expressions(expression, key="cube") 2837 rollup = self.expressions(expression, key="rollup") 2838 2839 groupings = csv( 2840 self.seg(grouping_sets) if grouping_sets else "", 2841 self.seg(cube) if cube else "", 2842 self.seg(rollup) if rollup else "", 2843 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2844 sep=self.GROUPINGS_SEP, 2845 ) 2846 2847 if ( 2848 expression.expressions 2849 and groupings 2850 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2851 ): 2852 add_separator = True 2853 2854 if grouping_sets: 2855 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2856 add_separator = False 2857 else: 2858 self.unsupported( 2859 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2860 ) 2861 2862 if add_separator: 2863 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2864 2865 return f"{group_by}{groupings}" 2866 2867 def having_sql(self, expression: exp.Having) -> str: 2868 this = self.indent(self.sql(expression, "this")) 2869 return f"{self.seg('HAVING')}{self.sep()}{this}" 2870 2871 def connect_sql(self, expression: exp.Connect) -> str: 2872 start = self.sql(expression, "start") 2873 start = self.seg(f"START WITH {start}") if start else "" 2874 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2875 connect = self.sql(expression, "connect") 2876 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2877 return start + connect 2878 2879 def prior_sql(self, expression: exp.Prior) -> str: 2880 return f"PRIOR {self.sql(expression, 'this')}" 2881 2882 def join_sql(self, expression: exp.Join) -> str: 2883 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2884 side = None 2885 else: 2886 side = expression.side 2887 2888 op_sql = " ".join( 2889 op 2890 for op in ( 2891 expression.method, 2892 "GLOBAL" if expression.args.get("global_") else None, 2893 side, 2894 expression.kind, 2895 expression.hint if self.JOIN_HINTS else None, 2896 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2897 ) 2898 if op 2899 ) 2900 match_cond = self.sql(expression, "match_condition") 2901 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2902 on_sql = self.sql(expression, "on") 2903 using = expression.args.get("using") 2904 2905 if not on_sql and using: 2906 on_sql = csv(*(self.sql(column) for column in using)) 2907 2908 this = expression.this 2909 this_sql = self.sql(this) 2910 2911 exprs = self.expressions(expression) 2912 if exprs: 2913 this_sql = f"{this_sql},{self.seg(exprs)}" 2914 2915 if on_sql: 2916 on_sql = self.indent(on_sql, skip_first=True) 2917 space = self.seg(" " * self.pad) if self.pretty else " " 2918 if using: 2919 on_sql = f"{space}USING ({on_sql})" 2920 else: 2921 on_sql = f"{space}ON {on_sql}" 2922 elif not op_sql: 2923 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2924 return f" {this_sql}" 2925 2926 return f", {this_sql}" 2927 2928 if op_sql != "STRAIGHT_JOIN": 2929 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2930 2931 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2932 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2933 2934 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2935 args = self.expressions(expression, flat=True) 2936 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2937 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2938 2939 def lateral_op(self, expression: exp.Lateral) -> str: 2940 cross_apply = expression.args.get("cross_apply") 2941 2942 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2943 if cross_apply is True: 2944 op = "INNER JOIN " 2945 elif cross_apply is False: 2946 op = "LEFT JOIN " 2947 else: 2948 op = "" 2949 2950 return f"{op}LATERAL" 2951 2952 def lateral_sql(self, expression: exp.Lateral) -> str: 2953 this = self.sql(expression, "this") 2954 2955 if expression.args.get("view"): 2956 alias = expression.args["alias"] 2957 columns = self.expressions(alias, key="columns", flat=True) 2958 table = f" {alias.name}" if alias.name else "" 2959 columns = f" AS {columns}" if columns else "" 2960 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2961 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2962 2963 table_alias = expression.args.get("alias") 2964 offset = expression.this.args.get("offset") 2965 2966 if ( 2967 self.UNNEST_WITH_ORDINALITY 2968 and table_alias 2969 and isinstance(expression.this, exp.Unnest) 2970 and isinstance(offset, exp.Identifier) 2971 ): 2972 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 2973 table_alias = table_alias.copy() 2974 table_alias.append("columns", offset.copy()) 2975 2976 alias = self.sql(table_alias) 2977 alias = f" AS {alias}" if alias else "" 2978 2979 ordinality = expression.args.get("ordinality") or "" 2980 if ordinality: 2981 ordinality = f" WITH ORDINALITY{alias}" 2982 alias = "" 2983 2984 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2985 2986 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2987 this = self.sql(expression, "this") 2988 2989 args = [ 2990 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2991 for e in (expression.args.get(k) for k in ("offset", "expression")) 2992 if e 2993 ] 2994 2995 args_sql = ", ".join(self.sql(e) for e in args) 2996 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2997 expressions = self.expressions(expression, flat=True) 2998 limit_options = self.sql(expression, "limit_options") 2999 expressions = f" BY {expressions}" if expressions else "" 3000 3001 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3002 3003 def offset_sql(self, expression: exp.Offset) -> str: 3004 this = self.sql(expression, "this") 3005 value = expression.expression 3006 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3007 expressions = self.expressions(expression, flat=True) 3008 expressions = f" BY {expressions}" if expressions else "" 3009 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3010 3011 def setitem_sql(self, expression: exp.SetItem) -> str: 3012 kind = self.sql(expression, "kind") 3013 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3014 kind = "" 3015 else: 3016 kind = f"{kind} " if kind else "" 3017 this = self.sql(expression, "this") 3018 expressions = self.expressions(expression) 3019 collate = self.sql(expression, "collate") 3020 collate = f" COLLATE {collate}" if collate else "" 3021 global_ = "GLOBAL " if expression.args.get("global_") else "" 3022 return f"{global_}{kind}{this}{expressions}{collate}" 3023 3024 def set_sql(self, expression: exp.Set) -> str: 3025 expressions = f" {self.expressions(expression, flat=True)}" 3026 tag = " TAG" if expression.args.get("tag") else "" 3027 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3028 3029 def queryband_sql(self, expression: exp.QueryBand) -> str: 3030 this = self.sql(expression, "this") 3031 update = " UPDATE" if expression.args.get("update") else "" 3032 scope = self.sql(expression, "scope") 3033 scope = f" FOR {scope}" if scope else "" 3034 3035 return f"QUERY_BAND = {this}{update}{scope}" 3036 3037 def pragma_sql(self, expression: exp.Pragma) -> str: 3038 return f"PRAGMA {self.sql(expression, 'this')}" 3039 3040 def lock_sql(self, expression: exp.Lock) -> str: 3041 if not self.LOCKING_READS_SUPPORTED: 3042 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3043 return "" 3044 3045 update = expression.args["update"] 3046 key = expression.args.get("key") 3047 if update: 3048 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3049 else: 3050 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3051 expressions = self.expressions(expression, flat=True) 3052 expressions = f" OF {expressions}" if expressions else "" 3053 wait = expression.args.get("wait") 3054 3055 if wait is not None: 3056 if isinstance(wait, exp.Literal): 3057 wait = f" WAIT {self.sql(wait)}" 3058 else: 3059 wait = " NOWAIT" if wait else " SKIP LOCKED" 3060 3061 return f"{lock_type}{expressions}{wait or ''}" 3062 3063 def literal_sql(self, expression: exp.Literal) -> str: 3064 text = expression.this or "" 3065 if expression.is_string: 3066 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3067 return text 3068 3069 def escape_str( 3070 self, 3071 text: str, 3072 escape_backslash: bool = True, 3073 delimiter: str | None = None, 3074 escaped_delimiter: str | None = None, 3075 is_byte_string: bool = False, 3076 ) -> str: 3077 if is_byte_string: 3078 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3079 else: 3080 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3081 3082 if supports_escape_sequences: 3083 text = "".join( 3084 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3085 for ch in text 3086 ) 3087 3088 delimiter = delimiter or self.dialect.QUOTE_END 3089 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3090 3091 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3092 3093 def loaddata_sql(self, expression: exp.LoadData) -> str: 3094 is_overwrite = expression.args.get("overwrite") 3095 overwrite = " OVERWRITE" if is_overwrite else "" 3096 this = self.sql(expression, "this") 3097 3098 files = expression.args.get("files") 3099 if files: 3100 files_sql = self.expressions(files, flat=True) 3101 files_sql = f"FILES{self.wrap(files_sql)}" 3102 if is_overwrite: 3103 this = f" {this}" 3104 elif expression.args.get("temp"): 3105 this = f" INTO TEMP TABLE {this}" 3106 else: 3107 this = f" INTO TABLE {this}" 3108 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3109 3110 local = " LOCAL" if expression.args.get("local") else "" 3111 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3112 this = f" INTO TABLE {this}" 3113 partition = self.sql(expression, "partition") 3114 partition = f" {partition}" if partition else "" 3115 input_format = self.sql(expression, "input_format") 3116 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3117 serde = self.sql(expression, "serde") 3118 serde = f" SERDE {serde}" if serde else "" 3119 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3120 3121 def null_sql(self, *_) -> str: 3122 return "NULL" 3123 3124 def boolean_sql(self, expression: exp.Boolean) -> str: 3125 return "TRUE" if expression.this else "FALSE" 3126 3127 def booland_sql(self, expression: exp.Booland) -> str: 3128 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3129 3130 def boolor_sql(self, expression: exp.Boolor) -> str: 3131 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3132 3133 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3134 this = self.sql(expression, "this") 3135 this = f"{this} " if this else this 3136 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3137 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3138 3139 def withfill_sql(self, expression: exp.WithFill) -> str: 3140 from_sql = self.sql(expression, "from_") 3141 from_sql = f" FROM {from_sql}" if from_sql else "" 3142 to_sql = self.sql(expression, "to") 3143 to_sql = f" TO {to_sql}" if to_sql else "" 3144 step_sql = self.sql(expression, "step") 3145 step_sql = f" STEP {step_sql}" if step_sql else "" 3146 interpolated_values = [ 3147 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3148 if isinstance(e, exp.Alias) 3149 else self.sql(e, "this") 3150 for e in expression.args.get("interpolate") or [] 3151 ] 3152 interpolate = ( 3153 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3154 ) 3155 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3156 3157 def cluster_sql(self, expression: exp.Cluster) -> str: 3158 return self.op_expressions("CLUSTER BY", expression) 3159 3160 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3161 if expression.this: 3162 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3163 return "" 3164 expressions = self.expressions(expression, flat=True) 3165 return f"CLUSTER BY ({expressions})" 3166 3167 def distribute_sql(self, expression: exp.Distribute) -> str: 3168 return self.op_expressions("DISTRIBUTE BY", expression) 3169 3170 def sort_sql(self, expression: exp.Sort) -> str: 3171 return self.op_expressions("SORT BY", expression) 3172 3173 def _resolve_ordered_for_null_ordering_simulation( 3174 self, expression: exp.Ordered 3175 ) -> exp.Expr | None: 3176 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3177 3178 Returns the underlying expression of the uniquely-matching projection 3179 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3180 simulation, since the CASE is evaluated in FROM-clause scope rather 3181 than alias scope (MySQL error 1052). Returns None if no safe 3182 substitution applies, leaving the original behaviour unchanged. 3183 """ 3184 this = expression.this 3185 if not (isinstance(this, exp.Column) and not this.table): 3186 return None 3187 3188 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3189 if not isinstance(ancestor, exp.Select): 3190 return None 3191 3192 column_name = this.name 3193 matched: list[exp.Expr] = [ 3194 p.this if isinstance(p, exp.Alias) else p 3195 for p in ancestor.selects 3196 if p.output_name == column_name 3197 ] 3198 match = matched[0] if len(matched) == 1 else None 3199 3200 # Skip the substitution when it would be identical to the existing 3201 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3202 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3203 return None 3204 3205 return match 3206 3207 def ordered_sql(self, expression: exp.Ordered) -> str: 3208 desc = expression.args.get("desc") 3209 asc = not desc 3210 3211 nulls_first = expression.args.get("nulls_first") 3212 nulls_last = not nulls_first 3213 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3214 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3215 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3216 3217 this = self.sql(expression, "this") 3218 3219 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3220 nulls_sort_change = "" 3221 if nulls_first and ( 3222 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3223 ): 3224 nulls_sort_change = " NULLS FIRST" 3225 elif ( 3226 nulls_last 3227 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3228 and not nulls_are_last 3229 ): 3230 nulls_sort_change = " NULLS LAST" 3231 3232 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3233 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3234 window = expression.find_ancestor(exp.Window, exp.Select) 3235 3236 if isinstance(window, exp.Window): 3237 window_this = window.this 3238 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3239 window_this = window_this.this 3240 spec = window.args.get("spec") 3241 else: 3242 window_this = None 3243 spec = None 3244 3245 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3246 # without a spec or with a ROWS spec, but not with RANGE 3247 if not ( 3248 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3249 and (not spec or spec.text("kind").upper() == "ROWS") 3250 ): 3251 if window_this and spec: 3252 self.unsupported( 3253 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3254 ) 3255 nulls_sort_change = "" 3256 elif self.NULL_ORDERING_SUPPORTED is False and ( 3257 (asc and nulls_sort_change == " NULLS LAST") 3258 or (desc and nulls_sort_change == " NULLS FIRST") 3259 ): 3260 # BigQuery does not allow these ordering/nulls combinations when used under 3261 # an aggregation func or under a window containing one 3262 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3263 3264 if isinstance(ancestor, exp.Window): 3265 ancestor = ancestor.this 3266 if isinstance(ancestor, exp.AggFunc): 3267 self.unsupported( 3268 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3269 ) 3270 nulls_sort_change = "" 3271 elif self.NULL_ORDERING_SUPPORTED is None: 3272 if expression.this.is_int: 3273 self.unsupported( 3274 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3275 ) 3276 elif not isinstance(expression.this, exp.Rand): 3277 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3278 target = self.sql(resolved) if resolved is not None else this 3279 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3280 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3281 nulls_sort_change = "" 3282 3283 with_fill = self.sql(expression, "with_fill") 3284 with_fill = f" {with_fill}" if with_fill else "" 3285 3286 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3287 3288 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3289 window_frame = self.sql(expression, "window_frame") 3290 window_frame = f"{window_frame} " if window_frame else "" 3291 3292 this = self.sql(expression, "this") 3293 3294 return f"{window_frame}{this}" 3295 3296 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3297 partition = self.partition_by_sql(expression) 3298 order = self.sql(expression, "order") 3299 measures = self.expressions(expression, key="measures") 3300 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3301 rows = self.sql(expression, "rows") 3302 rows = self.seg(rows) if rows else "" 3303 after = self.sql(expression, "after") 3304 after = self.seg(after) if after else "" 3305 pattern = self.sql(expression, "pattern") 3306 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3307 definition_sqls = [ 3308 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3309 for definition in expression.args.get("define", []) 3310 ] 3311 definitions = self.expressions(sqls=definition_sqls) 3312 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3313 body = "".join( 3314 ( 3315 partition, 3316 order, 3317 measures, 3318 rows, 3319 after, 3320 pattern, 3321 define, 3322 ) 3323 ) 3324 alias = self.sql(expression, "alias") 3325 alias = f" {alias}" if alias else "" 3326 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3327 3328 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3329 limit = expression.args.get("limit") 3330 3331 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3332 count = limit.args.get("count") 3333 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3334 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3335 limit = exp.Limit( 3336 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3337 ) 3338 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3339 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3340 3341 return csv( 3342 *sqls, 3343 *[self.sql(join) for join in expression.args.get("joins") or []], 3344 self.sql(expression, "match"), 3345 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3346 self.sql(expression, "prewhere"), 3347 self.sql(expression, "where"), 3348 self.sql(expression, "connect"), 3349 self.sql(expression, "group"), 3350 self.sql(expression, "having"), 3351 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3352 self.sql(expression, "order"), 3353 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3354 *self.after_limit_modifiers(expression), 3355 self.sql(expression, "for_"), 3356 self.options_modifier(expression), 3357 sep="", 3358 ) 3359 3360 def options_modifier(self, expression: exp.Expr) -> str: 3361 options = self.expressions(expression, key="options") 3362 return f" {options}" if options else "" 3363 3364 def forclause_sql(self, expression: exp.ForClause) -> str: 3365 kind = expression.args["kind"] 3366 if kind == "BROWSE": 3367 return f"{self.sep()}FOR BROWSE" 3368 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3369 # the target dialect doesn't support QueryOption, so we drop the clause. 3370 options = self.expressions(expression, key="expressions") 3371 if not options: 3372 return "" 3373 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3374 3375 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3376 self.unsupported("Unsupported query option.") 3377 return "" 3378 3379 def offset_limit_modifiers( 3380 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3381 ) -> list[str]: 3382 return [ 3383 self.sql(expression, "offset") if fetch else self.sql(limit), 3384 self.sql(limit) if fetch else self.sql(expression, "offset"), 3385 ] 3386 3387 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3388 locks = self.expressions(expression, key="locks", sep=" ") 3389 locks = f" {locks}" if locks else "" 3390 return [locks, self.sql(expression, "sample")] 3391 3392 def select_sql(self, expression: exp.Select) -> str: 3393 into = expression.args.get("into") 3394 if not self.SUPPORTS_SELECT_INTO and into: 3395 into.pop() 3396 3397 hint = self.sql(expression, "hint") 3398 distinct = self.sql(expression, "distinct") 3399 distinct = f" {distinct}" if distinct else "" 3400 kind = self.sql(expression, "kind") 3401 3402 limit = expression.args.get("limit") 3403 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3404 top = self.limit_sql(limit, top=True) 3405 limit.pop() 3406 else: 3407 top = "" 3408 3409 expressions = self.expressions(expression) 3410 3411 if kind: 3412 if kind in self.SELECT_KINDS: 3413 kind = f" AS {kind}" 3414 else: 3415 if kind == "STRUCT": 3416 expressions = self.expressions( 3417 sqls=[ 3418 self.sql( 3419 exp.Struct( 3420 expressions=[ 3421 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3422 if isinstance(e, exp.Alias) 3423 else e 3424 for e in expression.expressions 3425 ] 3426 ) 3427 ) 3428 ] 3429 ) 3430 kind = "" 3431 3432 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3433 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3434 3435 exclude = expression.args.get("exclude") 3436 3437 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3438 exclude_sql = self.expressions(sqls=exclude, flat=True) 3439 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3440 3441 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3442 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3443 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3444 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3445 sql = self.query_modifiers( 3446 expression, 3447 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3448 self.sql(expression, "into", comment=False), 3449 self.sql(expression, "from_", comment=False), 3450 ) 3451 3452 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3453 if expression.args.get("with_"): 3454 sql = self.maybe_comment(sql, expression) 3455 expression.pop_comments() 3456 3457 sql = self.prepend_ctes(expression, sql) 3458 3459 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3460 expression.set("exclude", None) 3461 subquery = expression.subquery(copy=False) 3462 star = exp.Star(except_=exclude) 3463 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3464 3465 if not self.SUPPORTS_SELECT_INTO and into: 3466 if into.args.get("temporary"): 3467 table_kind = " TEMPORARY" 3468 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3469 table_kind = " UNLOGGED" 3470 else: 3471 table_kind = "" 3472 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3473 3474 return sql 3475 3476 def schema_sql(self, expression: exp.Schema) -> str: 3477 this = self.sql(expression, "this") 3478 sql = self.schema_columns_sql(expression) 3479 return f"{this} {sql}" if this and sql else this or sql 3480 3481 def schema_columns_sql(self, expression: exp.Expr) -> str: 3482 if expression.expressions: 3483 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3484 return "" 3485 3486 def star_sql(self, expression: exp.Star) -> str: 3487 except_ = self.expressions(expression, key="except_", flat=True) 3488 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3489 replace = self.expressions(expression, key="replace", flat=True) 3490 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3491 rename = self.expressions(expression, key="rename", flat=True) 3492 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3493 ilike = self.sql(expression, "ilike") 3494 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3495 return f"*{ilike}{except_}{replace}{rename}" 3496 3497 def parameter_sql(self, expression: exp.Parameter) -> str: 3498 this = self.sql(expression, "this") 3499 return f"{self.PARAMETER_TOKEN}{this}" 3500 3501 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3502 this = self.sql(expression, "this") 3503 kind = expression.text("kind") 3504 if kind: 3505 kind = f"{kind}." 3506 return f"@@{kind}{this}" 3507 3508 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3509 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3510 3511 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3512 alias = self.sql(expression, "alias") 3513 alias = f"{sep}{alias}" if alias else "" 3514 sample = self.sql(expression, "sample") 3515 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3516 alias = f"{sample}{alias}" 3517 3518 # Set to None so it's not generated again by self.query_modifiers() 3519 expression.set("sample", None) 3520 3521 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3522 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3523 return self.prepend_ctes(expression, sql) 3524 3525 def qualify_sql(self, expression: exp.Qualify) -> str: 3526 this = self.indent(self.sql(expression, "this")) 3527 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3528 3529 def unnest_sql(self, expression: exp.Unnest) -> str: 3530 args = self.expressions(expression, flat=True) 3531 3532 alias = expression.args.get("alias") 3533 offset = expression.args.get("offset") 3534 3535 if self.UNNEST_WITH_ORDINALITY: 3536 if alias and isinstance(offset, exp.Expr): 3537 alias.append("columns", offset) 3538 expression.set("offset", None) 3539 3540 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3541 columns = alias.columns 3542 alias = self.sql(columns[0]) if columns else "" 3543 else: 3544 alias = self.sql(alias) 3545 3546 alias = f" AS {alias}" if alias else alias 3547 if self.UNNEST_WITH_ORDINALITY: 3548 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3549 else: 3550 if isinstance(offset, exp.Expr): 3551 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3552 elif offset: 3553 suffix = f"{alias} WITH OFFSET" 3554 else: 3555 suffix = alias 3556 3557 return f"UNNEST({args}){suffix}" 3558 3559 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3560 return "" 3561 3562 def where_sql(self, expression: exp.Where) -> str: 3563 this = self.indent(self.sql(expression, "this")) 3564 return f"{self.seg('WHERE')}{self.sep()}{this}" 3565 3566 def window_sql(self, expression: exp.Window) -> str: 3567 this = self.sql(expression, "this") 3568 partition = self.partition_by_sql(expression) 3569 order = expression.args.get("order") 3570 order = self.order_sql(order, flat=True) if order else "" 3571 spec = self.sql(expression, "spec") 3572 alias = self.sql(expression, "alias") 3573 over = self.sql(expression, "over") or "OVER" 3574 3575 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3576 3577 first = expression.args.get("first") 3578 if first is None: 3579 first = "" 3580 else: 3581 first = "FIRST" if first else "LAST" 3582 3583 if not partition and not order and not spec and alias: 3584 return f"{this} {alias}" 3585 3586 args = self.format_args( 3587 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3588 ) 3589 return f"{this} ({args})" 3590 3591 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3592 partition = self.expressions(expression, key="partition_by", flat=True) 3593 return f"PARTITION BY {partition}" if partition else "" 3594 3595 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3596 kind = self.sql(expression, "kind") 3597 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3598 end = ( 3599 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3600 or "CURRENT ROW" 3601 ) 3602 3603 window_spec = f"{kind} BETWEEN {start} AND {end}" 3604 3605 exclude = self.sql(expression, "exclude") 3606 if exclude: 3607 if self.SUPPORTS_WINDOW_EXCLUDE: 3608 window_spec += f" EXCLUDE {exclude}" 3609 else: 3610 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3611 3612 return window_spec 3613 3614 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3615 this = self.sql(expression, "this") 3616 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3617 return f"{this} WITHIN GROUP ({expression_sql})" 3618 3619 def between_sql(self, expression: exp.Between) -> str: 3620 this = self.sql(expression, "this") 3621 low = self.sql(expression, "low") 3622 high = self.sql(expression, "high") 3623 symmetric = expression.args.get("symmetric") 3624 3625 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3626 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3627 3628 flag = ( 3629 " SYMMETRIC" 3630 if symmetric 3631 else " ASYMMETRIC" 3632 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3633 else "" # silently drop ASYMMETRIC – semantics identical 3634 ) 3635 return f"{this} BETWEEN{flag} {low} AND {high}" 3636 3637 def bracket_offset_expressions( 3638 self, expression: exp.Bracket, index_offset: int | None = None 3639 ) -> list[exp.Expr]: 3640 if expression.args.get("json_access"): 3641 return expression.expressions 3642 3643 return apply_index_offset( 3644 expression.this, 3645 expression.expressions, 3646 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3647 dialect=self.dialect, 3648 ) 3649 3650 def bracket_sql(self, expression: exp.Bracket) -> str: 3651 expressions = self.bracket_offset_expressions(expression) 3652 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3653 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3654 3655 def all_sql(self, expression: exp.All) -> str: 3656 this = self.sql(expression, "this") 3657 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3658 this = self.wrap(this) 3659 return f"ALL {this}" 3660 3661 def any_sql(self, expression: exp.Any) -> str: 3662 this = self.sql(expression, "this") 3663 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3664 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3665 this = self.wrap(this) 3666 return f"ANY{this}" 3667 return f"ANY {this}" 3668 3669 def exists_sql(self, expression: exp.Exists) -> str: 3670 return f"EXISTS{self.wrap(expression)}" 3671 3672 def case_sql(self, expression: exp.Case) -> str: 3673 this = self.sql(expression, "this") 3674 statements = [f"CASE {this}" if this else "CASE"] 3675 3676 for e in expression.args["ifs"]: 3677 statements.append(f"WHEN {self.sql(e, 'this')}") 3678 statements.append(f"THEN {self.sql(e, 'true')}") 3679 3680 default = self.sql(expression, "default") 3681 3682 if default: 3683 statements.append(f"ELSE {default}") 3684 3685 statements.append("END") 3686 3687 if self.pretty and self.too_wide(statements): 3688 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3689 3690 return " ".join(statements) 3691 3692 def constraint_sql(self, expression: exp.Constraint) -> str: 3693 this = self.sql(expression, "this") 3694 expressions = self.expressions(expression, flat=True) 3695 return f"CONSTRAINT {this} {expressions}" 3696 3697 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3698 order = expression.args.get("order") 3699 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3700 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3701 3702 def extract_sql(self, expression: exp.Extract) -> str: 3703 import sqlglot.dialects.dialect 3704 3705 this = ( 3706 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3707 if self.NORMALIZE_EXTRACT_DATE_PARTS 3708 else expression.this 3709 ) 3710 if self.EXTRACT_ALLOWS_QUOTES: 3711 this_sql = self.sql(this) 3712 elif isinstance(this, exp.WeekStart): 3713 this_sql = self.weekstart_name(this) 3714 else: 3715 this_sql = this.name 3716 expression_sql = self.sql(expression, "expression") 3717 3718 return f"EXTRACT({this_sql} FROM {expression_sql})" 3719 3720 def trim_sql(self, expression: exp.Trim) -> str: 3721 trim_type = self.sql(expression, "position") 3722 3723 if trim_type == "LEADING": 3724 func_name = "LTRIM" 3725 elif trim_type == "TRAILING": 3726 func_name = "RTRIM" 3727 else: 3728 func_name = "TRIM" 3729 3730 return self.func(func_name, expression.this, expression.expression) 3731 3732 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3733 args = expression.expressions 3734 if isinstance(expression, exp.ConcatWs): 3735 args = args[1:] # Skip the delimiter 3736 3737 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3738 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3739 3740 concat_coalesce = ( 3741 self.dialect.CONCAT_WS_COALESCE 3742 if isinstance(expression, exp.ConcatWs) 3743 else self.dialect.CONCAT_COALESCE 3744 ) 3745 3746 if not concat_coalesce and expression.args.get("coalesce"): 3747 3748 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3749 if not e.type: 3750 import sqlglot.optimizer.annotate_types 3751 3752 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3753 3754 if e.is_string or e.is_type(exp.DType.ARRAY): 3755 return e 3756 3757 return exp.func("coalesce", e, exp.Literal.string("")) 3758 3759 args = [_wrap_with_coalesce(e) for e in args] 3760 3761 return args 3762 3763 def concat_sql(self, expression: exp.Concat) -> str: 3764 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3765 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3766 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3767 # instead of coalescing them to empty string. 3768 import sqlglot.dialects.dialect 3769 3770 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3771 3772 expressions = self.convert_concat_args(expression) 3773 3774 # Some dialects don't allow a single-argument CONCAT call 3775 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3776 return self.sql(expressions[0]) 3777 3778 return self.func("CONCAT", *expressions) 3779 3780 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3781 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3782 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3783 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3784 all_args = expression.expressions 3785 expression.set("coalesce", True) 3786 return self.sql( 3787 exp.case() 3788 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3789 .else_(expression) 3790 ) 3791 3792 return self.func( 3793 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3794 ) 3795 3796 def check_sql(self, expression: exp.Check) -> str: 3797 this = self.sql(expression, key="this") 3798 return f"CHECK ({this})" 3799 3800 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3801 expressions = self.expressions(expression, flat=True) 3802 expressions = f" ({expressions})" if expressions else "" 3803 reference = self.sql(expression, "reference") 3804 reference = f" {reference}" if reference else "" 3805 delete = self.sql(expression, "delete") 3806 delete = f" ON DELETE {delete}" if delete else "" 3807 update = self.sql(expression, "update") 3808 update = f" ON UPDATE {update}" if update else "" 3809 options = self.expressions(expression, key="options", flat=True, sep=" ") 3810 options = f" {options}" if options else "" 3811 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3812 3813 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3814 this = self.sql(expression, "this") 3815 this = f" {this}" if this else "" 3816 expressions = self.expressions(expression, flat=True) 3817 include = self.sql(expression, "include") 3818 options = self.expressions(expression, key="options", flat=True, sep=" ") 3819 options = f" {options}" if options else "" 3820 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3821 3822 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3823 self.unsupported("TIMESERIES primary key columns are not supported") 3824 return self.sql(expression, "this") 3825 3826 def if_sql(self, expression: exp.If) -> str: 3827 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3828 3829 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3830 if self.MATCH_AGAINST_TABLE_PREFIX: 3831 expressions = [] 3832 for expr in expression.expressions: 3833 if isinstance(expr, exp.Table): 3834 expressions.append(f"TABLE {self.sql(expr)}") 3835 else: 3836 expressions.append(expr) 3837 else: 3838 expressions = expression.expressions 3839 3840 modifier = expression.args.get("modifier") 3841 modifier = f" {modifier}" if modifier else "" 3842 return ( 3843 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3844 ) 3845 3846 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3847 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3848 3849 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3850 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3851 3852 if self.QUOTE_JSON_PATH: 3853 path = self.escape_str(path) 3854 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3855 3856 return path 3857 3858 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3859 if isinstance(expression, exp.JSONPathPart): 3860 transform = self.TRANSFORMS.get(expression.__class__) 3861 if not callable(transform): 3862 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3863 return "" 3864 3865 return transform(self, expression) 3866 3867 if isinstance(expression, int): 3868 return str(expression) 3869 3870 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3871 escaped = expression.replace("'", "\\'") 3872 escaped = f"'{escaped}'" 3873 else: 3874 escaped = expression.replace('"', '\\"') 3875 escaped = f'"{escaped}"' 3876 3877 return escaped 3878 3879 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3880 return f"{self.sql(expression, 'this')} FORMAT JSON" 3881 3882 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3883 # Output the Teradata column FORMAT override. 3884 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3885 this = self.sql(expression, "this") 3886 fmt = self.sql(expression, "format") 3887 return f"{this} (FORMAT {fmt})" 3888 3889 def _jsonobject_sql( 3890 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3891 ) -> str: 3892 null_handling = expression.args.get("null_handling") 3893 null_handling = f" {null_handling}" if null_handling else "" 3894 3895 unique_keys = expression.args.get("unique_keys") 3896 if unique_keys is not None: 3897 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3898 else: 3899 unique_keys = "" 3900 3901 return_type = self.sql(expression, "return_type") 3902 return_type = f" RETURNING {return_type}" if return_type else "" 3903 encoding = self.sql(expression, "encoding") 3904 encoding = f" ENCODING {encoding}" if encoding else "" 3905 3906 if not name: 3907 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3908 3909 return self.func( 3910 name, 3911 *expression.expressions, 3912 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3913 ) 3914 3915 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3916 null_handling = expression.args.get("null_handling") 3917 null_handling = f" {null_handling}" if null_handling else "" 3918 return_type = self.sql(expression, "return_type") 3919 return_type = f" RETURNING {return_type}" if return_type else "" 3920 strict = " STRICT" if expression.args.get("strict") else "" 3921 return self.func( 3922 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3923 ) 3924 3925 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3926 this = self.sql(expression, "this") 3927 order = self.sql(expression, "order") 3928 null_handling = expression.args.get("null_handling") 3929 null_handling = f" {null_handling}" if null_handling else "" 3930 return_type = self.sql(expression, "return_type") 3931 return_type = f" RETURNING {return_type}" if return_type else "" 3932 strict = " STRICT" if expression.args.get("strict") else "" 3933 return self.func( 3934 "JSON_ARRAYAGG", 3935 this, 3936 suffix=f"{order}{null_handling}{return_type}{strict})", 3937 ) 3938 3939 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3940 path = self.sql(expression, "path") 3941 path = f" PATH {path}" if path else "" 3942 nested_schema = self.sql(expression, "nested_schema") 3943 3944 if nested_schema: 3945 return f"NESTED{path} {nested_schema}" 3946 3947 this = self.sql(expression, "this") 3948 kind = self.sql(expression, "kind") 3949 kind = f" {kind}" if kind else "" 3950 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3951 3952 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3953 return f"{this}{kind}{format_json}{path}{ordinality}" 3954 3955 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3956 return self.func("COLUMNS", *expression.expressions) 3957 3958 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3959 this = self.sql(expression, "this") 3960 path = self.sql(expression, "path") 3961 path = f", {path}" if path else "" 3962 error_handling = expression.args.get("error_handling") 3963 error_handling = f" {error_handling}" if error_handling else "" 3964 empty_handling = expression.args.get("empty_handling") 3965 empty_handling = f" {empty_handling}" if empty_handling else "" 3966 schema = self.sql(expression, "schema") 3967 return self.func( 3968 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3969 ) 3970 3971 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3972 this = self.sql(expression, "this") 3973 kind = self.sql(expression, "kind") 3974 path = self.sql(expression, "path") 3975 path = f" {path}" if path else "" 3976 as_json = " AS JSON" if expression.args.get("as_json") else "" 3977 return f"{this} {kind}{path}{as_json}" 3978 3979 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3980 this = self.sql(expression, "this") 3981 path = self.sql(expression, "path") 3982 path = f", {path}" if path else "" 3983 expressions = self.expressions(expression) 3984 with_ = ( 3985 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3986 if expressions 3987 else "" 3988 ) 3989 return f"OPENJSON({this}{path}){with_}" 3990 3991 def in_sql(self, expression: exp.In) -> str: 3992 query = expression.args.get("query") 3993 unnest = expression.args.get("unnest") 3994 field = expression.args.get("field") 3995 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3996 3997 if query: 3998 in_sql = self.sql(query) 3999 elif unnest: 4000 in_sql = self.in_unnest_op(unnest) 4001 elif field: 4002 in_sql = self.sql(field) 4003 else: 4004 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4005 4006 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4007 4008 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4009 return f"(SELECT {self.sql(unnest)})" 4010 4011 def interval_sql(self, expression: exp.Interval) -> str: 4012 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4013 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4014 exp.AutoRefreshProperty, 4015 ) 4016 interval_keyword = "INTERVAL" if include_keyword else "" 4017 unit_expression = expression.args.get("unit") 4018 unit = self.sql(unit_expression) if unit_expression else "" 4019 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4020 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4021 unit = f" {unit}" if unit else "" 4022 4023 if self.SINGLE_STRING_INTERVAL: 4024 this = expression.this.name if expression.this else "" 4025 if this: 4026 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4027 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4028 return f"{interval_keyword}'{this}'{unit}" 4029 return f"{interval_keyword}'{this}{unit}'" 4030 return f"{interval_keyword}{unit}" 4031 4032 this = self.sql(expression, "this") 4033 if this: 4034 if not include_keyword and expression.this.is_string: 4035 this = expression.this.name 4036 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4037 this = f"({this})" 4038 if include_keyword: 4039 this = f" {this}" 4040 4041 return f"{interval_keyword}{this}{unit}" 4042 4043 def return_sql(self, expression: exp.Return) -> str: 4044 return f"RETURN {self.sql(expression, 'this')}" 4045 4046 def reference_sql(self, expression: exp.Reference) -> str: 4047 this = self.sql(expression, "this") 4048 expressions = self.expressions(expression, flat=True) 4049 expressions = f"({expressions})" if expressions else "" 4050 options = self.expressions(expression, key="options", flat=True, sep=" ") 4051 options = f" {options}" if options else "" 4052 return f"REFERENCES {this}{expressions}{options}" 4053 4054 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4055 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4056 parent = expression.parent 4057 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4058 4059 return self.func( 4060 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4061 ) 4062 4063 def paren_sql(self, expression: exp.Paren) -> str: 4064 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4065 return f"({sql}{self.seg(')', sep='')}" 4066 4067 def neg_sql(self, expression: exp.Neg) -> str: 4068 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4069 this_sql = self.sql(expression, "this") 4070 sep = " " if this_sql[0] == "-" else "" 4071 return f"-{sep}{this_sql}" 4072 4073 def not_sql(self, expression: exp.Not) -> str: 4074 return f"NOT {self.sql(expression, 'this')}" 4075 4076 def alias_sql(self, expression: exp.Alias) -> str: 4077 alias = self.sql(expression, "alias") 4078 alias = f" AS {alias}" if alias else "" 4079 return f"{self.sql(expression, 'this')}{alias}" 4080 4081 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4082 alias = expression.args["alias"] 4083 4084 parent = expression.parent 4085 pivot = parent and parent.parent 4086 4087 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4088 identifier_alias = isinstance(alias, exp.Identifier) 4089 literal_alias = isinstance(alias, exp.Literal) 4090 4091 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4092 alias.replace(exp.Literal.string(alias.output_name)) 4093 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4094 alias.replace(exp.to_identifier(alias.output_name)) 4095 4096 return self.alias_sql(expression) 4097 4098 def aliases_sql(self, expression: exp.Aliases) -> str: 4099 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4100 4101 def atindex_sql(self, expression: exp.AtIndex) -> str: 4102 this = self.sql(expression, "this") 4103 index = self.sql(expression, "expression") 4104 return f"{this} AT {index}" 4105 4106 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4107 this = self.sql(expression, "this") 4108 zone = self.sql(expression, "zone") 4109 return f"{this} AT TIME ZONE {zone}" 4110 4111 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4112 this = self.sql(expression, "this") 4113 zone = self.sql(expression, "zone") 4114 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4115 4116 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4117 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4118 4119 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4120 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4121 4122 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4123 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4124 4125 def add_sql(self, expression: exp.Add) -> str: 4126 return self.binary(expression, "+") 4127 4128 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4129 return self.connector_sql(expression, "AND", stack) 4130 4131 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4132 return self.connector_sql(expression, "OR", stack) 4133 4134 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4135 return self.connector_sql(expression, "XOR", stack) 4136 4137 def connector_sql( 4138 self, 4139 expression: exp.Connector, 4140 op: str, 4141 stack: list[str | exp.Expr] | None = None, 4142 ) -> str: 4143 if stack is not None: 4144 stack.append(expression.right) 4145 if expression.comments and self.comments: 4146 op = self.maybe_comment(op, comments=expression.comments) 4147 4148 stack.extend((op, expression.left)) 4149 return op 4150 4151 stack = [expression] 4152 sqls: list[str] = [] 4153 ops = set() 4154 4155 while stack: 4156 node = stack.pop() 4157 if isinstance(node, exp.Connector): 4158 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4159 else: 4160 sql = self.sql(node) 4161 if sqls and sqls[-1] in ops: 4162 sqls[-1] += f" {sql}" 4163 else: 4164 sqls.append(sql) 4165 4166 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4167 return sep.join(sqls) 4168 4169 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4170 return self.binary(expression, "&") 4171 4172 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4173 return self.binary(expression, "<<") 4174 4175 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4176 return f"~{self.sql(expression, 'this')}" 4177 4178 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4179 return self.binary(expression, "|") 4180 4181 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4182 return self.binary(expression, ">>") 4183 4184 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4185 return self.binary(expression, "^") 4186 4187 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4188 format_sql = self.sql(expression, "format") 4189 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4190 to_sql = self.sql(expression, "to") 4191 to_sql = f" {to_sql}" if to_sql else "" 4192 action = self.sql(expression, "action") 4193 action = f" {action}" if action else "" 4194 default = self.sql(expression, "default") 4195 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4196 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4197 4198 # Base implementation that excludes safe, zone, and target_type metadata args 4199 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4200 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4201 4202 # Base implementation that excludes the safe and default_year metadata args 4203 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4204 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4205 4206 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4207 return self.func( 4208 "PARSE_DATETIME", 4209 expression.this, 4210 expression.args.get("format"), 4211 expression.args.get("zone"), 4212 ) 4213 4214 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4215 zone = self.sql(expression, "this") 4216 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4217 4218 def collate_sql(self, expression: exp.Collate) -> str: 4219 if self.COLLATE_IS_FUNC: 4220 return self.function_fallback_sql(expression) 4221 return self.binary(expression, "COLLATE") 4222 4223 def command_sql(self, expression: exp.Command) -> str: 4224 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4225 4226 def comment_sql(self, expression: exp.Comment) -> str: 4227 this = self.sql(expression, "this") 4228 kind = expression.args["kind"] 4229 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4230 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4231 expression_sql = self.sql(expression, "expression") 4232 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4233 4234 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4235 this = self.sql(expression, "this") 4236 delete = " DELETE" if expression.args.get("delete") else "" 4237 recompress = self.sql(expression, "recompress") 4238 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4239 to_disk = self.sql(expression, "to_disk") 4240 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4241 to_volume = self.sql(expression, "to_volume") 4242 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4243 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4244 4245 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4246 where = self.sql(expression, "where") 4247 group = self.sql(expression, "group") 4248 aggregates = self.expressions(expression, key="aggregates") 4249 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4250 4251 if not (where or group or aggregates) and len(expression.expressions) == 1: 4252 return f"TTL {self.expressions(expression, flat=True)}" 4253 4254 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4255 4256 def transaction_sql(self, expression: exp.Transaction) -> str: 4257 modes = self.expressions(expression, key="modes") 4258 modes = f" {modes}" if modes else "" 4259 return f"BEGIN{modes}" 4260 4261 def commit_sql(self, expression: exp.Commit) -> str: 4262 chain = expression.args.get("chain") 4263 if chain is not None: 4264 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4265 4266 return f"COMMIT{chain or ''}" 4267 4268 def rollback_sql(self, expression: exp.Rollback) -> str: 4269 savepoint = expression.args.get("savepoint") 4270 savepoint = f" TO {savepoint}" if savepoint else "" 4271 return f"ROLLBACK{savepoint}" 4272 4273 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4274 this = self.sql(expression, "this") 4275 4276 exists = "" 4277 if expression.args.get("exists"): 4278 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4279 exists = " IF EXISTS" 4280 else: 4281 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4282 4283 dtype = self.sql(expression, "dtype") 4284 if dtype: 4285 collate = self.sql(expression, "collate") 4286 collate = f" COLLATE {collate}" if collate else "" 4287 using = self.sql(expression, "using") 4288 using = f" USING {using}" if using else "" 4289 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4290 null_constraint = self._alter_column_null_constraint_sql(expression) 4291 4292 return ( 4293 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4294 f"{collate}{using}{null_constraint}" 4295 ) 4296 4297 default = self.sql(expression, "default") 4298 if default: 4299 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4300 4301 comment = self.sql(expression, "comment") 4302 if comment: 4303 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4304 4305 visible = expression.args.get("visible") 4306 if visible: 4307 return f"ALTER COLUMN{exists} {this} SET {visible}" 4308 4309 allow_null = expression.args.get("allow_null") 4310 drop = expression.args.get("drop") 4311 4312 if not drop and not allow_null: 4313 self.unsupported("Unsupported ALTER COLUMN syntax") 4314 4315 if allow_null is not None: 4316 keyword = "DROP" if drop else "SET" 4317 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4318 4319 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4320 4321 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4322 allow_null = expression.args.get("allow_null") 4323 if allow_null is None: 4324 return "" 4325 4326 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4327 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4328 return "" 4329 4330 return " NULL" if allow_null else " NOT NULL" 4331 4332 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4333 this = self.sql(expression, "this") 4334 rename_from = self.sql(expression, "rename_from") 4335 if rename_from: 4336 if not self.SUPPORTS_CHANGE_COLUMN: 4337 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4338 return f"CHANGE COLUMN {rename_from} {this}" 4339 if not self.SUPPORTS_MODIFY_COLUMN: 4340 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4341 return f"MODIFY COLUMN {this}" 4342 4343 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4344 this = self.sql(expression, "this") 4345 4346 visible = expression.args.get("visible") 4347 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4348 4349 return f"ALTER INDEX {this} {visible_sql}" 4350 4351 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4352 this = self.sql(expression, "this") 4353 if not isinstance(expression.this, exp.Var): 4354 this = f"KEY DISTKEY {this}" 4355 return f"ALTER DISTSTYLE {this}" 4356 4357 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4358 compound = " COMPOUND" if expression.args.get("compound") else "" 4359 this = self.sql(expression, "this") 4360 expressions = self.expressions(expression, flat=True) 4361 expressions = f"({expressions})" if expressions else "" 4362 return f"ALTER{compound} SORTKEY {this or expressions}" 4363 4364 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4365 if not self.RENAME_TABLE_WITH_DB: 4366 # Remove db from tables 4367 expression = expression.transform( 4368 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4369 ).assert_is(exp.AlterRename) 4370 this = self.sql(expression, "this") 4371 to_kw = " TO" if include_to else "" 4372 return f"RENAME{to_kw} {this}" 4373 4374 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4375 exists = " IF EXISTS" if expression.args.get("exists") else "" 4376 old_column = self.sql(expression, "this") 4377 new_column = self.sql(expression, "to") 4378 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4379 4380 def alterset_sql(self, expression: exp.AlterSet) -> str: 4381 exprs = self.expressions(expression, flat=True) 4382 if self.ALTER_SET_WRAPPED: 4383 exprs = f"({exprs})" 4384 4385 return f"SET {exprs}" 4386 4387 def alter_sql(self, expression: exp.Alter) -> str: 4388 actions = expression.args["actions"] 4389 4390 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4391 actions[0], exp.ColumnDef 4392 ): 4393 actions_sql = self.expressions(expression, key="actions", flat=True) 4394 actions_sql = f"ADD {actions_sql}" 4395 else: 4396 actions_list = [] 4397 for action in actions: 4398 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4399 action_sql = self.add_column_sql(action) 4400 else: 4401 action_sql = self.sql(action) 4402 if isinstance(action, exp.Query): 4403 action_sql = f"AS {action_sql}" 4404 4405 actions_list.append(action_sql) 4406 4407 actions_sql = self.format_args(*actions_list).lstrip("\n") 4408 4409 iceberg = ( 4410 "ICEBERG " 4411 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4412 else "" 4413 ) 4414 exists = " IF EXISTS" if expression.args.get("exists") else "" 4415 on_cluster = self.sql(expression, "cluster") 4416 on_cluster = f" {on_cluster}" if on_cluster else "" 4417 only = " ONLY" if expression.args.get("only") else "" 4418 options = self.expressions(expression, key="options") 4419 options = f", {options}" if options else "" 4420 kind = self.sql(expression, "kind") 4421 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4422 check = " WITH CHECK" if expression.args.get("check") else "" 4423 cascade = ( 4424 " CASCADE" 4425 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4426 else "" 4427 ) 4428 this = self.sql(expression, "this") 4429 this = f" {this}" if this else "" 4430 4431 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4432 4433 def altersession_sql(self, expression: exp.AlterSession) -> str: 4434 items_sql = self.expressions(expression, flat=True) 4435 keyword = "UNSET" if expression.args.get("unset") else "SET" 4436 return f"{keyword} {items_sql}" 4437 4438 def add_column_sql(self, expression: exp.Expr) -> str: 4439 sql = self.sql(expression) 4440 if isinstance(expression, exp.Schema): 4441 column_text = " COLUMNS" 4442 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4443 column_text = " COLUMN" 4444 else: 4445 column_text = "" 4446 4447 return f"ADD{column_text} {sql}" 4448 4449 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4450 expressions = self.expressions(expression) 4451 exists = " IF EXISTS " if expression.args.get("exists") else " " 4452 return f"DROP{exists}{expressions}" 4453 4454 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4455 return "DROP PRIMARY KEY" 4456 4457 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4458 return f"ADD {self.expressions(expression, indent=False)}" 4459 4460 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4461 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4462 location = self.sql(expression, "location") 4463 location = f" {location}" if location else "" 4464 return f"ADD {exists}{self.sql(expression.this)}{location}" 4465 4466 def distinct_sql(self, expression: exp.Distinct) -> str: 4467 this = self.expressions(expression, flat=True) 4468 4469 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4470 case = exp.case() 4471 for arg in expression.expressions: 4472 case = case.when(arg.is_(exp.null()), exp.null()) 4473 this = self.sql(case.else_(f"({this})")) 4474 4475 this = f" {this}" if this else "" 4476 4477 on = self.sql(expression, "on") 4478 on = f" ON {on}" if on else "" 4479 return f"DISTINCT{this}{on}" 4480 4481 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4482 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4483 4484 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4485 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4486 4487 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4488 this_sql = self.sql(expression, "this") 4489 expression_sql = self.sql(expression, "expression") 4490 kind = "MAX" if expression.args.get("max") else "MIN" 4491 return f"{this_sql} HAVING {kind} {expression_sql}" 4492 4493 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4494 return self.sql( 4495 exp.Cast( 4496 this=exp.Div(this=expression.this, expression=expression.expression), 4497 to=exp.DataType(this=exp.DType.INT), 4498 ) 4499 ) 4500 4501 def dpipe_sql(self, expression: exp.DPipe) -> str: 4502 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4503 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4504 return self.binary(expression, "||") 4505 4506 def div_sql(self, expression: exp.Div) -> str: 4507 l, r = expression.left, expression.right 4508 4509 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4510 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4511 4512 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4513 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4514 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4515 4516 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4517 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4518 return self.sql( 4519 exp.cast( 4520 l / r, 4521 to=exp.DType.BIGINT, 4522 ) 4523 ) 4524 4525 return self.binary(expression, "/") 4526 4527 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4528 n = exp._wrap(expression.this, exp.Binary) 4529 d = exp._wrap(expression.expression, exp.Binary) 4530 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4531 4532 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4533 return self.binary(expression, "OVERLAPS") 4534 4535 def distance_sql(self, expression: exp.Distance) -> str: 4536 return self.binary(expression, "<->") 4537 4538 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4539 return self.binary(expression, "<<->>") 4540 4541 def dot_sql(self, expression: exp.Dot) -> str: 4542 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4543 4544 def eq_sql(self, expression: exp.EQ) -> str: 4545 return self.binary(expression, "=") 4546 4547 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4548 return self.binary(expression, ":=") 4549 4550 def escape_sql(self, expression: exp.Escape) -> str: 4551 this = expression.this 4552 if ( 4553 isinstance(this, (exp.Like, exp.ILike)) 4554 and isinstance(this.expression, (exp.All, exp.Any)) 4555 and not self.SUPPORTS_LIKE_QUANTIFIERS 4556 ): 4557 return self._like_sql(this, escape=expression) 4558 return self.binary(expression, "ESCAPE") 4559 4560 def glob_sql(self, expression: exp.Glob) -> str: 4561 return self.binary(expression, "GLOB") 4562 4563 def gt_sql(self, expression: exp.GT) -> str: 4564 return self.binary(expression, ">") 4565 4566 def gte_sql(self, expression: exp.GTE) -> str: 4567 return self.binary(expression, ">=") 4568 4569 def is_sql(self, expression: exp.Is) -> str: 4570 negate = expression.args.get("negate") 4571 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4572 positive = bool(expression.expression.this) != bool(negate) 4573 return self.sql(expression.this if positive else exp.not_(expression.this)) 4574 return self.binary(expression, "IS NOT" if negate else "IS") 4575 4576 def _like_sql( 4577 self, 4578 expression: exp.Like | exp.ILike, 4579 escape: exp.Escape | None = None, 4580 ) -> str: 4581 this = expression.this 4582 rhs = expression.expression 4583 4584 if isinstance(expression, exp.Like): 4585 exp_class: type[exp.Like | exp.ILike] = exp.Like 4586 op = "LIKE" 4587 else: 4588 exp_class = exp.ILike 4589 op = "ILIKE" 4590 4591 if expression.args.get("negate"): 4592 op = f"NOT {op}" 4593 4594 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4595 exprs = rhs.this.unnest() 4596 4597 if isinstance(exprs, exp.Tuple): 4598 exprs = exprs.expressions 4599 else: 4600 exprs = [exprs] 4601 4602 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4603 4604 def _make_like(expr: exp.Expression) -> exp.Expression: 4605 like: exp.Expression = exp_class( 4606 this=this, expression=expr, negate=expression.args.get("negate") 4607 ) 4608 if escape: 4609 like = exp.Escape(this=like, expression=escape.expression.copy()) 4610 return like 4611 4612 like_expr: exp.Expr = _make_like(exprs[0]) 4613 for expr in exprs[1:]: 4614 like_expr = connective(like_expr, _make_like(expr), copy=False) 4615 4616 parent = escape.parent if escape else expression.parent 4617 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4618 parent, exp.Condition 4619 ): 4620 like_expr = exp.paren(like_expr, copy=False) 4621 4622 return self.sql(like_expr) 4623 4624 return self.binary(expression, op) 4625 4626 def like_sql(self, expression: exp.Like) -> str: 4627 return self._like_sql(expression) 4628 4629 def ilike_sql(self, expression: exp.ILike) -> str: 4630 return self._like_sql(expression) 4631 4632 def match_sql(self, expression: exp.Match) -> str: 4633 return self.binary(expression, "MATCH") 4634 4635 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4636 return self.binary(expression, "SIMILAR TO") 4637 4638 def lt_sql(self, expression: exp.LT) -> str: 4639 return self.binary(expression, "<") 4640 4641 def lte_sql(self, expression: exp.LTE) -> str: 4642 return self.binary(expression, "<=") 4643 4644 def mod_sql(self, expression: exp.Mod) -> str: 4645 this = self.sql(expression, "this") 4646 expr = self.sql(expression, "expression") 4647 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4648 4649 parent = expression.parent 4650 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4651 return f"({sql})" 4652 4653 return sql 4654 4655 def mul_sql(self, expression: exp.Mul) -> str: 4656 return self.binary(expression, "*") 4657 4658 def neq_sql(self, expression: exp.NEQ) -> str: 4659 return self.binary(expression, "<>") 4660 4661 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4662 return self.binary(expression, "IS NOT DISTINCT FROM") 4663 4664 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4665 return self.binary(expression, "IS DISTINCT FROM") 4666 4667 def sub_sql(self, expression: exp.Sub) -> str: 4668 return self.binary(expression, "-") 4669 4670 def trycast_sql(self, expression: exp.TryCast) -> str: 4671 return self.cast_sql(expression, safe_prefix="TRY_") 4672 4673 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4674 return self.cast_sql(expression) 4675 4676 def try_sql(self, expression: exp.Try) -> str: 4677 if not self.TRY_SUPPORTED: 4678 self.unsupported("Unsupported TRY function") 4679 return self.sql(expression, "this") 4680 4681 return self.func("TRY", expression.this) 4682 4683 def log_sql(self, expression: exp.Log) -> str: 4684 this = expression.this 4685 expr = expression.expression 4686 4687 if self.dialect.LOG_BASE_FIRST is False: 4688 this, expr = expr, this 4689 elif self.dialect.LOG_BASE_FIRST is None and expr: 4690 if this.name in ("2", "10"): 4691 return self.func(f"LOG{this.name}", expr) 4692 4693 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4694 4695 return self.func("LOG", this, expr) 4696 4697 def use_sql(self, expression: exp.Use) -> str: 4698 kind = self.sql(expression, "kind") 4699 kind = f" {kind}" if kind else "" 4700 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4701 this = f" {this}" if this else "" 4702 return f"USE{kind}{this}" 4703 4704 def binary(self, expression: exp.Binary, op: str) -> str: 4705 sqls: list[str] = [] 4706 stack: list[None | str | exp.Expr] = [expression] 4707 binary_type = type(expression) 4708 4709 while stack: 4710 node = stack.pop() 4711 4712 if type(node) is binary_type: 4713 op_func = node.args.get("operator") 4714 if op_func: 4715 op = f"OPERATOR({self.sql(op_func)})" 4716 4717 stack.append(node.args.get("expression")) 4718 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4719 stack.append(node.args.get("this")) 4720 else: 4721 sqls.append(self.sql(node)) 4722 4723 return "".join(sqls) 4724 4725 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4726 to_clause = self.sql(expression, "to") 4727 if to_clause: 4728 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4729 4730 return self.function_fallback_sql(expression) 4731 4732 def function_fallback_sql(self, expression: exp.Func) -> str: 4733 args = [] 4734 4735 for key in expression.arg_types: 4736 arg_value = expression.args.get(key) 4737 4738 if isinstance(arg_value, list): 4739 for value in arg_value: 4740 args.append(value) 4741 elif arg_value is not None: 4742 args.append(arg_value) 4743 4744 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4745 name = expression.meta_get("name") or expression.sql_name() 4746 else: 4747 name = expression.sql_name() 4748 4749 return self.func(name, *args) 4750 4751 def func( 4752 self, 4753 name: str, 4754 *args: t.Any, 4755 prefix: str = "(", 4756 suffix: str = ")", 4757 normalize: bool = True, 4758 ) -> str: 4759 name = self.normalize_func(name) if normalize else name 4760 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4761 4762 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4763 arg_sqls = tuple( 4764 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4765 ) 4766 if self.pretty and self.too_wide(arg_sqls): 4767 return self.indent( 4768 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4769 ) 4770 return sep.join(arg_sqls) 4771 4772 def too_wide(self, args: t.Iterable) -> bool: 4773 return sum(len(arg) for arg in args) > self.max_text_width 4774 4775 def format_time( 4776 self, 4777 expression: exp.Expr, 4778 inverse_time_mapping: dict[str, str] | None = None, 4779 inverse_time_trie: dict | None = None, 4780 ) -> str | None: 4781 return format_time( 4782 self.sql(expression, "format"), 4783 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4784 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4785 ) 4786 4787 def expressions( 4788 self, 4789 expression: exp.Expr | None = None, 4790 key: str | None = None, 4791 sqls: t.Collection[str | exp.Expr] | None = None, 4792 flat: bool = False, 4793 indent: bool = True, 4794 skip_first: bool = False, 4795 skip_last: bool = False, 4796 sep: str = ", ", 4797 prefix: str = "", 4798 dynamic: bool = False, 4799 new_line: bool = False, 4800 ) -> str: 4801 expressions = expression.args.get(key or "expressions") if expression else sqls 4802 4803 if not expressions: 4804 return "" 4805 4806 if flat: 4807 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4808 4809 num_sqls = len(expressions) 4810 result_sqls = [] 4811 4812 for i, e in enumerate(expressions): 4813 sql = self.sql(e, comment=False) 4814 if not sql: 4815 continue 4816 4817 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4818 4819 if self.pretty: 4820 if self.leading_comma: 4821 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4822 else: 4823 result_sqls.append( 4824 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4825 ) 4826 else: 4827 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4828 4829 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4830 if new_line: 4831 result_sqls.insert(0, "") 4832 result_sqls.append("") 4833 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4834 else: 4835 result_sql = "".join(result_sqls) 4836 4837 return ( 4838 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4839 if indent 4840 else result_sql 4841 ) 4842 4843 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4844 flat = flat or isinstance(expression.parent, exp.Properties) 4845 expressions_sql = self.expressions(expression, flat=flat) 4846 if flat: 4847 return f"{op} {expressions_sql}" 4848 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4849 4850 def naked_property(self, expression: exp.Property) -> str: 4851 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4852 if not property_name: 4853 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4854 return f"{property_name} {self.sql(expression, 'this')}" 4855 4856 def tag_sql(self, expression: exp.Tag) -> str: 4857 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4858 4859 def token_sql(self, token_type: TokenType) -> str: 4860 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4861 4862 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4863 this = self.sql(expression, "this") 4864 expressions = self.no_identify(self.expressions, expression) 4865 expressions = ( 4866 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4867 ) 4868 return f"{this}{expressions}" if expressions.strip() != "" else this 4869 4870 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4871 return self.expressions(expression, flat=True) 4872 4873 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4874 params = self.no_identify(self.expressions, expression, flat=True) 4875 body = self.sql(expression, "this") 4876 prefix = "TABLE " if expression.args.get("is_table") else "" 4877 return f"({params}) AS {prefix}{body}" 4878 4879 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4880 this = self.sql(expression, "this") 4881 expressions = self.expressions(expression, flat=True) 4882 return f"{this}({expressions})" 4883 4884 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4885 return self.binary(expression, "=>") 4886 4887 def when_sql(self, expression: exp.When) -> str: 4888 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4889 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4890 condition = self.sql(expression, "condition") 4891 condition = f" AND {condition}" if condition else "" 4892 4893 then_expression = expression.args.get("then") 4894 if isinstance(then_expression, exp.Insert): 4895 this = self.sql(then_expression, "this") 4896 this = f"INSERT {this}" if this else "INSERT" 4897 then = self.sql(then_expression, "expression") 4898 then = f"{this} VALUES {then}" if then else this 4899 elif isinstance(then_expression, exp.Update): 4900 if isinstance(then_expression.args.get("expressions"), exp.Star): 4901 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4902 else: 4903 expressions_sql = self.expressions(then_expression) 4904 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4905 else: 4906 then = self.sql(then_expression) 4907 4908 if isinstance(then_expression, (exp.Insert, exp.Update)): 4909 where = self.sql(then_expression, "where") 4910 if where and not self.SUPPORTS_MERGE_WHERE: 4911 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4912 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4913 where = "" 4914 then = f"{then}{where}" 4915 return f"WHEN {matched}{source}{condition} THEN {then}" 4916 4917 def whens_sql(self, expression: exp.Whens) -> str: 4918 return self.expressions(expression, sep=" ", indent=False) 4919 4920 def merge_sql(self, expression: exp.Merge) -> str: 4921 table = expression.this 4922 table_alias = "" 4923 4924 hints = table.args.get("hints") 4925 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4926 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4927 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4928 4929 this = self.sql(table) 4930 using = f"USING {self.sql(expression, 'using')}" 4931 whens = self.sql(expression, "whens") 4932 4933 on = self.sql(expression, "on") 4934 on = f"ON {on}" if on else "" 4935 4936 if not on: 4937 on = self.expressions(expression, key="using_cond") 4938 on = f"USING ({on})" if on else "" 4939 4940 returning = self.sql(expression, "returning") 4941 if returning: 4942 whens = f"{whens}{returning}" 4943 4944 sep = self.sep() 4945 4946 return self.prepend_ctes( 4947 expression, 4948 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4949 ) 4950 4951 @unsupported_args("format") 4952 def tochar_sql(self, expression: exp.ToChar) -> str: 4953 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4954 4955 @unsupported_args("default") 4956 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4957 if not self.SUPPORTS_TO_NUMBER: 4958 self.unsupported("Unsupported TO_NUMBER function") 4959 return self._tonumber_cast_sql(expression) 4960 4961 fmt = expression.args.get("format") 4962 if not fmt: 4963 self.unsupported("Conversion format is required for TO_NUMBER") 4964 return self._tonumber_cast_sql(expression) 4965 4966 return self.func("TO_NUMBER", expression.this, fmt) 4967 4968 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 4969 if expression.args.get("safe"): 4970 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 4971 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4972 4973 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4974 this = self.sql(expression, "this") 4975 kind = self.sql(expression, "kind") 4976 settings_sql = self.expressions(expression, key="settings", sep=" ") 4977 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4978 return f"{this}({kind}{args})" 4979 4980 def dictrange_sql(self, expression: exp.DictRange) -> str: 4981 this = self.sql(expression, "this") 4982 max = self.sql(expression, "max") 4983 min = self.sql(expression, "min") 4984 return f"{this}(MIN {min} MAX {max})" 4985 4986 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4987 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4988 4989 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4990 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4991 4992 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4993 def uniquekeyproperty_sql( 4994 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4995 ) -> str: 4996 return f"{prefix} ({self.expressions(expression, flat=True)})" 4997 4998 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4999 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5000 expressions = self.expressions(expression, flat=True) 5001 expressions = f" {self.wrap(expressions)}" if expressions else "" 5002 buckets = self.sql(expression, "buckets") 5003 kind = self.sql(expression, "kind") 5004 buckets = f" BUCKETS {buckets}" if buckets else "" 5005 order = self.sql(expression, "order") 5006 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5007 5008 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5009 return "" 5010 5011 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5012 expressions = self.expressions(expression, key="expressions", flat=True) 5013 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5014 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5015 buckets = self.sql(expression, "buckets") 5016 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5017 5018 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5019 this = self.sql(expression, "this") 5020 having = self.sql(expression, "having") 5021 5022 if having: 5023 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5024 5025 return self.func("ANY_VALUE", this) 5026 5027 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5028 transform = self.func("TRANSFORM", *expression.expressions) 5029 row_format_before = self.sql(expression, "row_format_before") 5030 row_format_before = f" {row_format_before}" if row_format_before else "" 5031 record_writer = self.sql(expression, "record_writer") 5032 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5033 using = f" USING {self.sql(expression, 'command_script')}" 5034 schema = self.sql(expression, "schema") 5035 schema = f" AS {schema}" if schema else "" 5036 row_format_after = self.sql(expression, "row_format_after") 5037 row_format_after = f" {row_format_after}" if row_format_after else "" 5038 record_reader = self.sql(expression, "record_reader") 5039 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5040 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5041 5042 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5043 key_block_size = self.sql(expression, "key_block_size") 5044 if key_block_size: 5045 return f"KEY_BLOCK_SIZE = {key_block_size}" 5046 5047 using = self.sql(expression, "using") 5048 if using: 5049 return f"USING {using}" 5050 5051 parser = self.sql(expression, "parser") 5052 if parser: 5053 return f"WITH PARSER {parser}" 5054 5055 comment = self.sql(expression, "comment") 5056 if comment: 5057 return f"COMMENT {comment}" 5058 5059 visible = expression.args.get("visible") 5060 if visible is not None: 5061 return "VISIBLE" if visible else "INVISIBLE" 5062 5063 engine_attr = self.sql(expression, "engine_attr") 5064 if engine_attr: 5065 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5066 5067 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5068 if secondary_engine_attr: 5069 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5070 5071 self.unsupported("Unsupported index constraint option.") 5072 return "" 5073 5074 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5075 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5076 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5077 5078 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5079 kind = self.sql(expression, "kind") 5080 kind = f"{kind} INDEX" if kind else "INDEX" 5081 this = self.sql(expression, "this") 5082 this = f" {this}" if this else "" 5083 index_type = self.sql(expression, "index_type") 5084 index_type = f" USING {index_type}" if index_type else "" 5085 expressions = self.expressions(expression, flat=True) 5086 expressions = f" ({expressions})" if expressions else "" 5087 options = self.expressions(expression, key="options", sep=" ") 5088 options = f" {options}" if options else "" 5089 return f"{kind}{this}{index_type}{expressions}{options}" 5090 5091 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5092 if self.NVL2_SUPPORTED: 5093 return self.function_fallback_sql(expression) 5094 5095 case = exp.Case().when( 5096 expression.this.is_(exp.null()).not_(copy=False), 5097 expression.args["true"], 5098 copy=False, 5099 ) 5100 else_cond = expression.args.get("false") 5101 if else_cond: 5102 case.else_(else_cond, copy=False) 5103 5104 return self.sql(case) 5105 5106 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5107 if expression.args.get("from_first") is False: 5108 self.unsupported("NTH_VALUE FROM LAST is not supported") 5109 5110 return self.function_fallback_sql(expression) 5111 5112 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5113 this = self.sql(expression, "this") 5114 expr = self.sql(expression, "expression") 5115 position = self.sql(expression, "position") 5116 position = f", {position}" if position else "" 5117 iterator = self.sql(expression, "iterator") 5118 condition = self.sql(expression, "condition") 5119 condition = f" IF {condition}" if condition else "" 5120 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5121 5122 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5123 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5124 5125 def opclass_sql(self, expression: exp.Opclass) -> str: 5126 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5127 5128 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5129 model = self.sql(expression, "this") 5130 model = f"MODEL {model}" 5131 expr = expression.expression 5132 if expr: 5133 expr_sql = self.sql(expression, "expression") 5134 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5135 else: 5136 expr_sql = None 5137 5138 parameters = self.sql(expression, "params_struct") or None 5139 5140 return self.func(name, model, expr_sql, parameters) 5141 5142 def predict_sql(self, expression: exp.Predict) -> str: 5143 return self._ml_sql(expression, "PREDICT") 5144 5145 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5146 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5147 return self._ml_sql(expression, name) 5148 5149 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5150 return self._ml_sql(expression, "GENERATE_TEXT") 5151 5152 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5153 return self._ml_sql(expression, "GENERATE_TABLE") 5154 5155 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5156 return self._ml_sql(expression, "GENERATE_BOOL") 5157 5158 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5159 return self._ml_sql(expression, "GENERATE_INT") 5160 5161 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5162 return self._ml_sql(expression, "GENERATE_DOUBLE") 5163 5164 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5165 return self._ml_sql(expression, "TRANSLATE") 5166 5167 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5168 return self._ml_sql(expression, "FORECAST") 5169 5170 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5171 this_sql = self.sql(expression, "this") 5172 if isinstance(expression.this, exp.Table): 5173 this_sql = f"TABLE {this_sql}" 5174 5175 return self.func( 5176 "FORECAST", 5177 this_sql, 5178 expression.args.get("data_col"), 5179 expression.args.get("timestamp_col"), 5180 expression.args.get("model"), 5181 expression.args.get("id_cols"), 5182 expression.args.get("horizon"), 5183 expression.args.get("forecast_end_timestamp"), 5184 expression.args.get("confidence_level"), 5185 expression.args.get("output_historical_time_series"), 5186 expression.args.get("context_window"), 5187 ) 5188 5189 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5190 this_sql = self.sql(expression, "this") 5191 if isinstance(expression.this, exp.Table): 5192 this_sql = f"TABLE {this_sql}" 5193 5194 return self.func( 5195 "FEATURES_AT_TIME", 5196 this_sql, 5197 expression.args.get("time"), 5198 expression.args.get("num_rows"), 5199 expression.args.get("ignore_feature_nulls"), 5200 ) 5201 5202 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5203 this_sql = self.sql(expression, "this") 5204 if isinstance(expression.this, exp.Table): 5205 this_sql = f"TABLE {this_sql}" 5206 5207 query_table = self.sql(expression, "query_table") 5208 if isinstance(expression.args["query_table"], exp.Table): 5209 query_table = f"TABLE {query_table}" 5210 5211 return self.func( 5212 "VECTOR_SEARCH", 5213 this_sql, 5214 expression.args.get("column_to_search"), 5215 query_table, 5216 expression.args.get("query_column_to_search"), 5217 expression.args.get("top_k"), 5218 expression.args.get("distance_type"), 5219 expression.args.get("options"), 5220 ) 5221 5222 def forin_sql(self, expression: exp.ForIn) -> str: 5223 this = self.sql(expression, "this") 5224 expression_sql = self.sql(expression, "expression") 5225 return f"FOR {this} DO {expression_sql}" 5226 5227 def refresh_sql(self, expression: exp.Refresh) -> str: 5228 this = self.sql(expression, "this") 5229 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5230 return f"REFRESH {kind}{this}" 5231 5232 def toarray_sql(self, expression: exp.ToArray) -> str: 5233 arg = expression.this 5234 if not arg.type: 5235 import sqlglot.optimizer.annotate_types 5236 5237 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5238 5239 if arg.is_type(exp.DType.ARRAY): 5240 return self.sql(arg) 5241 5242 cond_for_null = arg.is_(exp.null()) 5243 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5244 5245 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5246 this = expression.this 5247 time_format = self.format_time(expression) 5248 5249 if time_format: 5250 return self.sql( 5251 exp.cast( 5252 exp.StrToTime(this=this, format=expression.args["format"]), 5253 exp.DType.TIME, 5254 ) 5255 ) 5256 5257 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5258 return self.sql(this) 5259 5260 return self.sql(exp.cast(this, exp.DType.TIME)) 5261 5262 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5263 this = expression.this 5264 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5265 return self.sql(this) 5266 5267 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5268 5269 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5270 this = expression.this 5271 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5272 return self.sql(this) 5273 5274 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5275 5276 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5277 this = expression.this 5278 time_format = self.format_time(expression) 5279 safe = expression.args.get("safe") 5280 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5281 return self.sql( 5282 exp.cast( 5283 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5284 exp.DType.DATE, 5285 ) 5286 ) 5287 5288 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5289 return self.sql(this) 5290 5291 if safe: 5292 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5293 5294 return self.sql(exp.cast(this, exp.DType.DATE)) 5295 5296 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5297 return self.sql( 5298 exp.func( 5299 "DATEDIFF", 5300 expression.this, 5301 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5302 "day", 5303 ) 5304 ) 5305 5306 def lastday_sql(self, expression: exp.LastDay) -> str: 5307 if self.LAST_DAY_SUPPORTS_DATE_PART: 5308 return self.function_fallback_sql(expression) 5309 5310 unit = expression.args.get("unit") 5311 if unit and unit.name.upper() != "MONTH": 5312 self.unsupported("Date parts are not supported in LAST_DAY.") 5313 5314 return self.func("LAST_DAY", expression.this) 5315 5316 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5317 import sqlglot.dialects.dialect 5318 5319 return self.func( 5320 "DATE_ADD", 5321 expression.this, 5322 expression.expression, 5323 sqlglot.dialects.dialect.unit_to_str(expression), 5324 ) 5325 5326 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5327 this = expression.this 5328 position = expression.args["position"] 5329 offset = index_offset - (expression.args.get("offset") or 0) 5330 5331 if offset: 5332 if position.is_int: 5333 value = position.to_py() 5334 if value >= 0: 5335 position = exp.Literal.number(value + offset) 5336 elif offset < 0 and value == -1: 5337 # 1-based -1 appends, which a 0-based position can only express as the size 5338 position = exp.ArraySize(this=this.copy()) 5339 else: 5340 # Negative positions count from the end, so they shift in the opposite 5341 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5342 position = exp.Literal.number(value - offset) 5343 else: 5344 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5345 5346 return self.func("ARRAY_INSERT", this, position, expression.expression) 5347 5348 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5349 if self.CAN_IMPLEMENT_ARRAY_ANY: 5350 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5351 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5352 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5353 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5354 5355 import sqlglot.dialects.dialect 5356 5357 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5358 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5359 self.unsupported("ARRAY_ANY is unsupported") 5360 5361 return self.function_fallback_sql(expression) 5362 5363 def struct_sql(self, expression: exp.Struct) -> str: 5364 expression.set( 5365 "expressions", 5366 [ 5367 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5368 if isinstance(e, exp.PropertyEQ) 5369 else e 5370 for e in expression.expressions 5371 ], 5372 ) 5373 5374 return self.function_fallback_sql(expression) 5375 5376 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5377 low = self.sql(expression, "this") 5378 high = self.sql(expression, "expression") 5379 5380 return f"{low} TO {high}" 5381 5382 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5383 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5384 tables = f" {self.expressions(expression)}" 5385 5386 exists = " IF EXISTS" if expression.args.get("exists") else "" 5387 5388 on_cluster = self.sql(expression, "cluster") 5389 on_cluster = f" {on_cluster}" if on_cluster else "" 5390 5391 identity = self.sql(expression, "identity") 5392 identity = f" {identity} IDENTITY" if identity else "" 5393 5394 option = self.sql(expression, "option") 5395 option = f" {option}" if option else "" 5396 5397 partition = self.sql(expression, "partition") 5398 partition = f" {partition}" if partition else "" 5399 5400 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5401 5402 # This transpiles T-SQL's CONVERT function 5403 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5404 def convert_sql(self, expression: exp.Convert) -> str: 5405 to = expression.this 5406 value = expression.expression 5407 style = expression.args.get("style") 5408 safe = expression.args.get("safe") 5409 strict = expression.args.get("strict") 5410 5411 if not to or not value: 5412 return "" 5413 5414 # Retrieve length of datatype and override to default if not specified 5415 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5416 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5417 5418 transformed: exp.Expr | None = None 5419 cast = exp.Cast if strict else exp.TryCast 5420 5421 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5422 if isinstance(style, exp.Literal) and style.is_int: 5423 import sqlglot.dialects.tsql 5424 5425 style_value = style.name 5426 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5427 if not converted_style: 5428 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5429 5430 fmt = exp.Literal.string(converted_style) 5431 5432 if to.this == exp.DType.DATE: 5433 transformed = exp.StrToDate(this=value, format=fmt) 5434 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5435 transformed = exp.StrToTime(this=value, format=fmt) 5436 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5437 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5438 elif to.this == exp.DType.TEXT: 5439 transformed = exp.TimeToStr(this=value, format=fmt) 5440 5441 if not transformed: 5442 transformed = cast(this=value, to=to, safe=safe) 5443 5444 return self.sql(transformed) 5445 5446 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5447 this = expression.this 5448 if isinstance(this, exp.JSONPathWildcard): 5449 this = self.json_path_part(this) 5450 return f".{this}" if this else "" 5451 5452 quoted = expression.args.get("quoted") 5453 if not ( 5454 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5455 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5456 return f".{this}" 5457 5458 this = self.json_path_part(this) 5459 5460 return ( 5461 f"[{this}]" 5462 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5463 else f".{this}" 5464 ) 5465 5466 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5467 this = self.json_path_part(expression.this) 5468 return f"[{this}]" if this else "" 5469 5470 def _simplify_unless_literal(self, expression: E) -> E: 5471 if not isinstance(expression, exp.Literal): 5472 import sqlglot.optimizer.simplify 5473 5474 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5475 5476 return expression 5477 5478 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5479 this = expression.this 5480 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5481 self.unsupported( 5482 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5483 ) 5484 return self.sql(this) 5485 5486 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5487 if self.IGNORE_NULLS_BEFORE_ORDER: 5488 from sqlglot.optimizer.scope import find_all_in_scope 5489 5490 # The first modifier here will be the one closest to the AggFunc's arg 5491 mods = sorted( 5492 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5493 key=lambda x: ( 5494 0 5495 if isinstance(x, exp.HavingMax) 5496 else (1 if isinstance(x, exp.Order) else 2) 5497 ), 5498 ) 5499 5500 if mods: 5501 mod = mods[0] 5502 this = expression.__class__(this=mod.this.copy()) 5503 this.meta["inline"] = True 5504 mod.this.replace(this) 5505 return self.sql(expression.this) 5506 5507 agg_func = expression.find(exp.AggFunc) 5508 5509 if agg_func: 5510 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5511 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5512 5513 return f"{self.sql(expression, 'this')} {text}" 5514 5515 def _replace_line_breaks(self, string: str) -> str: 5516 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5517 if self.pretty: 5518 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5519 return string 5520 5521 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5522 option = self.sql(expression, "this") 5523 5524 if expression.expressions: 5525 upper = option.upper() 5526 5527 # Snowflake FILE_FORMAT options are separated by whitespace 5528 sep = " " if upper == "FILE_FORMAT" else ", " 5529 5530 # Databricks copy/format options do not set their list of values with EQ 5531 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5532 values = self.expressions(expression, flat=True, sep=sep) 5533 return f"{option}{op}({values})" 5534 5535 value = self.sql(expression, "expression") 5536 5537 if not value: 5538 return option 5539 5540 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5541 5542 return f"{option}{op}{value}" 5543 5544 def credentials_sql(self, expression: exp.Credentials) -> str: 5545 cred_expr = expression.args.get("credentials") 5546 if isinstance(cred_expr, exp.Literal): 5547 # Redshift case: CREDENTIALS <string> 5548 credentials = self.sql(expression, "credentials") 5549 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5550 else: 5551 # Snowflake case: CREDENTIALS = (...) 5552 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5553 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5554 5555 storage = self.sql(expression, "storage") 5556 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5557 5558 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5559 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5560 5561 iam_role = self.sql(expression, "iam_role") 5562 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5563 5564 region = self.sql(expression, "region") 5565 region = f" REGION {region}" if region else "" 5566 5567 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5568 5569 def copy_sql(self, expression: exp.Copy) -> str: 5570 this = self.sql(expression, "this") 5571 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5572 5573 credentials = self.sql(expression, "credentials") 5574 credentials = self.seg(credentials) if credentials else "" 5575 files = self.expressions(expression, key="files", flat=True) 5576 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5577 5578 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5579 params = self.expressions( 5580 expression, 5581 key="params", 5582 sep=sep, 5583 new_line=True, 5584 skip_last=True, 5585 skip_first=True, 5586 indent=self.COPY_PARAMS_ARE_WRAPPED, 5587 ) 5588 5589 if params: 5590 if self.COPY_PARAMS_ARE_WRAPPED: 5591 params = f" WITH ({params})" 5592 elif not self.pretty and (files or credentials): 5593 params = f" {params}" 5594 5595 return f"COPY{this}{kind} {files}{credentials}{params}" 5596 5597 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5598 return "" 5599 5600 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5601 on_sql = "ON" if expression.args.get("on") else "OFF" 5602 filter_col: str | None = self.sql(expression, "filter_column") 5603 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5604 retention_period: str | None = self.sql(expression, "retention_period") 5605 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5606 5607 if filter_col or retention_period: 5608 on_sql = self.func("ON", filter_col, retention_period) 5609 5610 return f"DATA_DELETION={on_sql}" 5611 5612 def maskingpolicycolumnconstraint_sql( 5613 self, expression: exp.MaskingPolicyColumnConstraint 5614 ) -> str: 5615 this = self.sql(expression, "this") 5616 expressions = self.expressions(expression, flat=True) 5617 expressions = f" USING ({expressions})" if expressions else "" 5618 return f"MASKING POLICY {this}{expressions}" 5619 5620 def gapfill_sql(self, expression: exp.GapFill) -> str: 5621 this = self.sql(expression, "this") 5622 this = f"TABLE {this}" 5623 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5624 5625 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5626 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5627 5628 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5629 this = self.sql(expression, "this") 5630 expr = expression.expression 5631 5632 if isinstance(expr, exp.Func): 5633 # T-SQL's CLR functions are case sensitive 5634 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5635 else: 5636 expr = self.sql(expression, "expression") 5637 5638 return self.scope_resolution(expr, this) 5639 5640 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5641 if self.PARSE_JSON_NAME is None: 5642 return self.sql(expression.this) 5643 5644 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5645 5646 def rand_sql(self, expression: exp.Rand) -> str: 5647 lower = self.sql(expression, "lower") 5648 upper = self.sql(expression, "upper") 5649 5650 if lower and upper: 5651 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5652 return self.func("RAND", expression.this) 5653 5654 def changes_sql(self, expression: exp.Changes) -> str: 5655 information = self.sql(expression, "information") 5656 information = f"INFORMATION => {information}" 5657 at_before = self.sql(expression, "at_before") 5658 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5659 end = self.sql(expression, "end") 5660 end = f"{self.seg('')}{end}" if end else "" 5661 5662 return f"CHANGES ({information}){at_before}{end}" 5663 5664 def pad_sql(self, expression: exp.Pad) -> str: 5665 prefix = "L" if expression.args.get("is_left") else "R" 5666 5667 fill_pattern = self.sql(expression, "fill_pattern") or None 5668 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5669 fill_pattern = "' '" 5670 5671 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5672 5673 def summarize_sql(self, expression: exp.Summarize) -> str: 5674 table = " TABLE" if expression.args.get("table") else "" 5675 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5676 5677 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5678 generate_series = exp.GenerateSeries(**expression.args) 5679 5680 parent = expression.parent 5681 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5682 parent = parent.parent 5683 5684 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5685 return self.sql(exp.Unnest(expressions=[generate_series])) 5686 5687 if isinstance(parent, exp.Select): 5688 self.unsupported("GenerateSeries projection unnesting is not supported.") 5689 5690 return self.sql(generate_series) 5691 5692 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5693 if self.SUPPORTS_CONVERT_TIMEZONE: 5694 return self.function_fallback_sql(expression) 5695 5696 source_tz = expression.args.get("source_tz") 5697 target_tz = expression.args.get("target_tz") 5698 timestamp = expression.args.get("timestamp") 5699 5700 if source_tz and timestamp: 5701 timestamp = exp.AtTimeZone( 5702 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5703 ) 5704 5705 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5706 5707 return self.sql(expr) 5708 5709 def json_sql(self, expression: exp.JSON) -> str: 5710 this = self.sql(expression, "this") 5711 this = f" {this}" if this else "" 5712 5713 _with = expression.args.get("with_") 5714 5715 if _with is None: 5716 with_sql = "" 5717 elif not _with: 5718 with_sql = " WITHOUT" 5719 else: 5720 with_sql = " WITH" 5721 5722 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5723 5724 return f"JSON{this}{with_sql}{unique_sql}" 5725 5726 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5727 path = self.sql(expression, "path") 5728 returning = self.sql(expression, "returning") 5729 returning = f" RETURNING {returning}" if returning else "" 5730 5731 on_condition = self.sql(expression, "on_condition") 5732 on_condition = f" {on_condition}" if on_condition else "" 5733 5734 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5735 5736 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5737 regexp = " REGEXP" if expression.args.get("regexp") else "" 5738 return f"SKIP{regexp} {self.sql(expression.expression)}" 5739 5740 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5741 else_ = "ELSE " if expression.args.get("else_") else "" 5742 condition = self.sql(expression, "expression") 5743 condition = f"WHEN {condition} THEN " if condition else else_ 5744 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5745 return f"{condition}{insert}" 5746 5747 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5748 kind = self.sql(expression, "kind") 5749 expressions = self.seg(self.expressions(expression, sep=" ")) 5750 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5751 return res 5752 5753 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5754 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5755 empty = expression.args.get("empty") 5756 empty = ( 5757 f"DEFAULT {empty} ON EMPTY" 5758 if isinstance(empty, exp.Expr) 5759 else self.sql(expression, "empty") 5760 ) 5761 5762 error = expression.args.get("error") 5763 error = ( 5764 f"DEFAULT {error} ON ERROR" 5765 if isinstance(error, exp.Expr) 5766 else self.sql(expression, "error") 5767 ) 5768 5769 if error and empty: 5770 error = ( 5771 f"{empty} {error}" 5772 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5773 else f"{error} {empty}" 5774 ) 5775 empty = "" 5776 5777 null = self.sql(expression, "null") 5778 5779 return f"{empty}{error}{null}" 5780 5781 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5782 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5783 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5784 5785 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5786 this = self.sql(expression, "this") 5787 path = self.sql(expression, "path") 5788 5789 passing = self.expressions(expression, "passing") 5790 passing = f" PASSING {passing}" if passing else "" 5791 5792 on_condition = self.sql(expression, "on_condition") 5793 on_condition = f" {on_condition}" if on_condition else "" 5794 5795 path = f"{path}{passing}{on_condition}" 5796 5797 return self.func("JSON_EXISTS", this, path) 5798 5799 def _add_arrayagg_null_filter( 5800 self, 5801 array_agg_sql: str, 5802 array_agg_expr: exp.ArrayAgg, 5803 column_expr: exp.Expr, 5804 ) -> str: 5805 """ 5806 Add NULL filter to ARRAY_AGG if dialect requires it. 5807 5808 Args: 5809 array_agg_sql: The generated ARRAY_AGG SQL string 5810 array_agg_expr: The ArrayAgg expression node 5811 column_expr: The column/expression to filter (before ORDER BY wrapping) 5812 5813 Returns: 5814 SQL string with FILTER clause added if needed 5815 """ 5816 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5817 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5818 if not ( 5819 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5820 ): 5821 return array_agg_sql 5822 5823 parent = array_agg_expr.parent 5824 if isinstance(parent, exp.Filter): 5825 parent_cond = parent.expression.this 5826 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5827 elif column_expr.find(exp.Column): 5828 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5829 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5830 this_sql = ( 5831 self.expressions(column_expr) 5832 if isinstance(column_expr, exp.Distinct) 5833 else self.sql(column_expr) 5834 ) 5835 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5836 5837 return array_agg_sql 5838 5839 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5840 array_agg = self.function_fallback_sql(expression) 5841 column_expr = expression.this 5842 if isinstance(column_expr, exp.Order): 5843 column_expr = column_expr.this 5844 5845 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5846 5847 def slice_sql(self, expression: exp.Slice) -> str: 5848 step = self.sql(expression, "step") 5849 end = self.sql(expression.expression) 5850 begin = self.sql(expression.this) 5851 5852 sql = f"{end}:{step}" if step else end 5853 return f"{begin}:{sql}" if sql else f"{begin}:" 5854 5855 def apply_sql(self, expression: exp.Apply) -> str: 5856 this = self.sql(expression, "this") 5857 expr = self.sql(expression, "expression") 5858 5859 return f"{this} APPLY({expr})" 5860 5861 def _grant_or_revoke_sql( 5862 self, 5863 expression: exp.Grant | exp.Revoke, 5864 keyword: str, 5865 preposition: str, 5866 grant_option_prefix: str = "", 5867 grant_option_suffix: str = "", 5868 ) -> str: 5869 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5870 5871 kind = self.sql(expression, "kind") 5872 kind = f" {kind}" if kind else "" 5873 5874 securable = self.sql(expression, "securable") 5875 securable = f" {securable}" if securable else "" 5876 5877 principals = self.expressions(expression, key="principals", flat=True) 5878 5879 if not expression.args.get("grant_option"): 5880 grant_option_prefix = grant_option_suffix = "" 5881 5882 # cascade for revoke only 5883 cascade = self.sql(expression, "cascade") 5884 cascade = f" {cascade}" if cascade else "" 5885 5886 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5887 5888 def grant_sql(self, expression: exp.Grant) -> str: 5889 return self._grant_or_revoke_sql( 5890 expression, 5891 keyword="GRANT", 5892 preposition="TO", 5893 grant_option_suffix=" WITH GRANT OPTION", 5894 ) 5895 5896 def revoke_sql(self, expression: exp.Revoke) -> str: 5897 return self._grant_or_revoke_sql( 5898 expression, 5899 keyword="REVOKE", 5900 preposition="FROM", 5901 grant_option_prefix="GRANT OPTION FOR ", 5902 ) 5903 5904 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5905 this = self.sql(expression, "this") 5906 columns = self.expressions(expression, flat=True) 5907 columns = f"({columns})" if columns else "" 5908 5909 return f"{this}{columns}" 5910 5911 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5912 this = self.sql(expression, "this") 5913 5914 kind = self.sql(expression, "kind") 5915 kind = f"{kind} " if kind else "" 5916 5917 return f"{kind}{this}" 5918 5919 def columns_sql(self, expression: exp.Columns) -> str: 5920 func = self.function_fallback_sql(expression) 5921 if expression.args.get("unpack"): 5922 func = f"*{func}" 5923 5924 return func 5925 5926 def overlay_sql(self, expression: exp.Overlay) -> str: 5927 this = self.sql(expression, "this") 5928 expr = self.sql(expression, "expression") 5929 from_sql = self.sql(expression, "from_") 5930 for_sql = self.sql(expression, "for_") 5931 for_sql = f" FOR {for_sql}" if for_sql else "" 5932 5933 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5934 5935 @unsupported_args("format") 5936 def todouble_sql(self, expression: exp.ToDouble) -> str: 5937 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5938 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5939 5940 def string_sql(self, expression: exp.String) -> str: 5941 this = expression.this 5942 zone = expression.args.get("zone") 5943 5944 if zone: 5945 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5946 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5947 # set for source_tz to transpile the time conversion before the STRING cast 5948 this = exp.ConvertTimezone( 5949 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5950 ) 5951 5952 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5953 5954 def median_sql(self, expression: exp.Median) -> str: 5955 if not self.SUPPORTS_MEDIAN: 5956 return self.sql( 5957 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5958 ) 5959 5960 return self.function_fallback_sql(expression) 5961 5962 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5963 filler = self.sql(expression, "this") 5964 filler = f" {filler}" if filler else "" 5965 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5966 return f"TRUNCATE{filler} {with_count}" 5967 5968 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5969 if self.SUPPORTS_UNIX_SECONDS: 5970 return self.function_fallback_sql(expression) 5971 5972 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5973 5974 return self.sql( 5975 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5976 ) 5977 5978 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5979 dim = expression.expression 5980 5981 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5982 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5983 if not (dim.is_int and dim.name == "1"): 5984 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5985 dim = None 5986 5987 # If dimension is required but not specified, default initialize it 5988 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5989 dim = exp.Literal.number(1) 5990 5991 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5992 5993 def attach_sql(self, expression: exp.Attach) -> str: 5994 this = self.sql(expression, "this") 5995 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5996 expressions = self.expressions(expression) 5997 expressions = f" ({expressions})" if expressions else "" 5998 5999 return f"ATTACH{exists_sql} {this}{expressions}" 6000 6001 def detach_sql(self, expression: exp.Detach) -> str: 6002 kind = self.sql(expression, "kind") 6003 kind = f" {kind}" if kind else "" 6004 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6005 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6006 exists = " IF EXISTS" if expression.args.get("exists") else "" 6007 if exists: 6008 kind = kind or " DATABASE" 6009 6010 this = self.sql(expression, "this") 6011 this = f" {this}" if this else "" 6012 cluster = self.sql(expression, "cluster") 6013 cluster = f" {cluster}" if cluster else "" 6014 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6015 sync = " SYNC" if expression.args.get("sync") else "" 6016 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6017 6018 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6019 this = self.sql(expression, "this") 6020 value = self.sql(expression, "expression") 6021 value = f" {value}" if value else "" 6022 return f"{this}{value}" 6023 6024 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6025 return ( 6026 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6027 ) 6028 6029 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6030 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6031 encode = f"{encode} {self.sql(expression, 'this')}" 6032 6033 properties = expression.args.get("properties") 6034 if properties: 6035 encode = f"{encode} {self.properties(properties)}" 6036 6037 return encode 6038 6039 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6040 this = self.sql(expression, "this") 6041 include = f"INCLUDE {this}" 6042 6043 column_def = self.sql(expression, "column_def") 6044 if column_def: 6045 include = f"{include} {column_def}" 6046 6047 alias = self.sql(expression, "alias") 6048 if alias: 6049 include = f"{include} AS {alias}" 6050 6051 return include 6052 6053 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6054 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6055 name = f"{prefix} {self.sql(expression, 'this')}" 6056 return self.func("XMLELEMENT", name, *expression.expressions) 6057 6058 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6059 this = self.sql(expression, "this") 6060 expr = self.sql(expression, "expression") 6061 expr = f"({expr})" if expr else "" 6062 return f"{this}{expr}" 6063 6064 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6065 partitions = self.expressions(expression, "partition_expressions") 6066 create = self.expressions(expression, "create_expressions") 6067 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6068 6069 def partitionbyrangepropertydynamic_sql( 6070 self, expression: exp.PartitionByRangePropertyDynamic 6071 ) -> str: 6072 start = self.sql(expression, "start") 6073 end = self.sql(expression, "end") 6074 6075 every = expression.args["every"] 6076 if isinstance(every, exp.Interval) and every.this.is_string: 6077 every.this.replace(exp.Literal.number(every.name)) 6078 6079 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6080 6081 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6082 name = self.sql(expression, "this") 6083 values = self.expressions(expression, flat=True) 6084 6085 return f"NAME {name} VALUE {values}" 6086 6087 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6088 kind = self.sql(expression, "kind") 6089 sample = self.sql(expression, "sample") 6090 return f"SAMPLE {sample} {kind}" 6091 6092 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6093 kind = self.sql(expression, "kind") 6094 option = self.sql(expression, "option") 6095 option = f" {option}" if option else "" 6096 this = self.sql(expression, "this") 6097 this = f" {this}" if this else "" 6098 columns = self.expressions(expression) 6099 columns = f" {columns}" if columns else "" 6100 return f"{kind}{option} STATISTICS{this}{columns}" 6101 6102 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6103 this = self.sql(expression, "this") 6104 columns = self.expressions(expression) 6105 inner_expression = self.sql(expression, "expression") 6106 inner_expression = f" {inner_expression}" if inner_expression else "" 6107 update_options = self.sql(expression, "update_options") 6108 update_options = f" {update_options} UPDATE" if update_options else "" 6109 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6110 6111 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6112 kind = self.sql(expression, "kind") 6113 kind = f" {kind}" if kind else "" 6114 return f"DELETE{kind} STATISTICS" 6115 6116 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6117 inner_expression = self.sql(expression, "expression") 6118 return f"LIST CHAINED ROWS{inner_expression}" 6119 6120 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6121 kind = self.sql(expression, "kind") 6122 this = self.sql(expression, "this") 6123 this = f" {this}" if this else "" 6124 inner_expression = self.sql(expression, "expression") 6125 return f"VALIDATE {kind}{this}{inner_expression}" 6126 6127 def analyze_sql(self, expression: exp.Analyze) -> str: 6128 options = self.expressions(expression, key="options", sep=" ") 6129 options = f" {options}" if options else "" 6130 kind = self.sql(expression, "kind") 6131 kind = f" {kind}" if kind else "" 6132 tables = self.expressions(expression, key="tables", flat=True) 6133 tables = f" {tables}" if tables else "" 6134 mode = self.sql(expression, "mode") 6135 mode = f" {mode}" if mode else "" 6136 properties = self.sql(expression, "properties") 6137 properties = f" {properties}" if properties else "" 6138 partition = self.sql(expression, "partition") 6139 partition = f" {partition}" if partition else "" 6140 inner_expression = self.sql(expression, "expression") 6141 inner_expression = f" {inner_expression}" if inner_expression else "" 6142 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6143 6144 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6145 this = self.sql(expression, "this") 6146 namespaces = self.expressions(expression, key="namespaces") 6147 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6148 passing = self.expressions(expression, key="passing") 6149 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6150 columns = self.expressions(expression, key="columns") 6151 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6152 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6153 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6154 6155 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6156 this = self.sql(expression, "this") 6157 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6158 6159 def export_sql(self, expression: exp.Export) -> str: 6160 this = self.sql(expression, "this") 6161 connection = self.sql(expression, "connection") 6162 connection = f"WITH CONNECTION {connection} " if connection else "" 6163 options = self.sql(expression, "options") 6164 return f"EXPORT DATA {connection}{options} AS {this}" 6165 6166 def declare_sql(self, expression: exp.Declare) -> str: 6167 replace = "OR REPLACE " if expression.args.get("replace") else "" 6168 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6169 6170 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6171 variables = self.expressions(expression, "this") 6172 default = self.sql(expression, "default") 6173 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6174 6175 kind = self.sql(expression, "kind") 6176 if isinstance(expression.args.get("kind"), exp.Schema): 6177 kind = f"TABLE {kind}" 6178 6179 kind = f" {kind}" if kind else "" 6180 6181 return f"{variables}{kind}{default}" 6182 6183 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6184 kind = self.sql(expression, "kind") 6185 this = self.sql(expression, "this") 6186 set = self.sql(expression, "expression") 6187 using = self.sql(expression, "using") 6188 using = f" USING {using}" if using else "" 6189 6190 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6191 6192 return f"{kind_sql} {this} SET {set}{using}" 6193 6194 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6195 params = self.expressions(expression, key="params", flat=True) 6196 return self.func(expression.name, *expression.expressions) + f"({params})" 6197 6198 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6199 return self.func(expression.name, *expression.expressions) 6200 6201 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6202 return self.anonymousaggfunc_sql(expression) 6203 6204 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6205 return self.parameterizedagg_sql(expression) 6206 6207 def show_sql(self, expression: exp.Show) -> str: 6208 self.unsupported("Unsupported SHOW statement") 6209 return "" 6210 6211 def install_sql(self, expression: exp.Install) -> str: 6212 self.unsupported("Unsupported INSTALL statement") 6213 return "" 6214 6215 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6216 # Snowflake GET/PUT statements: 6217 # PUT <file> <internalStage> <properties> 6218 # GET <internalStage> <file> <properties> 6219 props = expression.args.get("properties") 6220 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6221 this = self.sql(expression, "this") 6222 target = self.sql(expression, "target") 6223 6224 if isinstance(expression, exp.Put): 6225 return f"PUT {this} {target}{props_sql}" 6226 else: 6227 return f"GET {target} {this}{props_sql}" 6228 6229 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6230 this = self.sql(expression, "this") 6231 expr = self.sql(expression, "expression") 6232 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6233 return f"TRANSLATE({this} USING {expr}{with_error})" 6234 6235 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6236 if self.SUPPORTS_DECODE_CASE: 6237 return self.func("DECODE", *expression.expressions) 6238 6239 decode_expr, *expressions = expression.expressions 6240 6241 ifs = [] 6242 for search, result in zip(expressions[::2], expressions[1::2]): 6243 if isinstance(search, exp.Literal): 6244 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6245 elif isinstance(search, exp.Null): 6246 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6247 else: 6248 if isinstance(search, exp.Binary): 6249 search = exp.paren(search) 6250 6251 cond = exp.or_( 6252 decode_expr.eq(search), 6253 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6254 copy=False, 6255 ) 6256 ifs.append(exp.If(this=cond, true=result)) 6257 6258 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6259 return self.sql(case) 6260 6261 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6262 this = self.sql(expression, "this") 6263 this = self.seg(this, sep="") 6264 dimensions = self.expressions( 6265 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6266 ) 6267 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6268 metrics = self.expressions( 6269 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6270 ) 6271 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6272 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6273 facts = self.seg(f"FACTS {facts}") if facts else "" 6274 where = self.sql(expression, "where") 6275 where = self.seg(f"WHERE {where}") if where else "" 6276 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6277 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6278 6279 def getextract_sql(self, expression: exp.GetExtract) -> str: 6280 this = expression.this 6281 expr = expression.expression 6282 6283 if not this.type or not expression.type: 6284 import sqlglot.optimizer.annotate_types 6285 6286 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6287 6288 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6289 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6290 6291 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6292 6293 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6294 return self.sql( 6295 exp.DateAdd( 6296 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6297 expression=expression.this, 6298 unit=exp.var("DAY"), 6299 ) 6300 ) 6301 6302 def space_sql(self: Generator, expression: exp.Space) -> str: 6303 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6304 6305 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6306 return f"BUILD {self.sql(expression, 'this')}" 6307 6308 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6309 method = self.sql(expression, "method") 6310 kind = expression.args.get("kind") 6311 if not kind: 6312 return f"REFRESH {method}" 6313 6314 every = self.sql(expression, "every") 6315 unit = self.sql(expression, "unit") 6316 every = f" EVERY {every} {unit}" if every else "" 6317 starts = self.sql(expression, "starts") 6318 starts = f" STARTS {starts}" if starts else "" 6319 6320 return f"REFRESH {method} ON {kind}{every}{starts}" 6321 6322 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6323 self.unsupported("The model!attribute syntax is not supported") 6324 return "" 6325 6326 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6327 return self.func("DIRECTORY", expression.this) 6328 6329 def uuid_sql(self, expression: exp.Uuid) -> str: 6330 is_string = expression.args.get("is_string", False) 6331 uuid_func_sql = self.func("UUID") 6332 6333 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6334 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6335 6336 return uuid_func_sql 6337 6338 def initcap_sql(self, expression: exp.Initcap) -> str: 6339 delimiters = expression.expression 6340 6341 if delimiters: 6342 # do not generate delimiters arg if we are round-tripping from default delimiters 6343 if ( 6344 delimiters.is_string 6345 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6346 ): 6347 delimiters = None 6348 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6349 self.unsupported("INITCAP does not support custom delimiters") 6350 delimiters = None 6351 6352 return self.func("INITCAP", expression.this, delimiters) 6353 6354 def localtime_sql(self, expression: exp.Localtime) -> str: 6355 this = expression.this 6356 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6357 6358 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6359 this = expression.this 6360 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6361 6362 def weekstart_name(self, expression: exp.WeekStart) -> str: 6363 import sqlglot.dialects.dialect 6364 6365 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6366 this = expression.this.name.upper() 6367 6368 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6369 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6370 6371 if dow_from_week_start_day != dow_from_week_offset: 6372 self.unsupported( 6373 f"WEEK({this}) is not supported; falling back to the default week start day" 6374 ) 6375 6376 return "WEEK" 6377 6378 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6379 name = self.weekstart_name(expression) 6380 6381 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6382 if isinstance(expression.parent, exp.DateTrunc): 6383 return self.sql(exp.Literal.string(name)) 6384 6385 return name 6386 6387 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6388 this = self.expressions(expression) 6389 charset = self.sql(expression, "charset") 6390 using = f" USING {charset}" if charset else "" 6391 return self.func(name, this + using) 6392 6393 def block_sql(self, expression: exp.Block) -> str: 6394 expressions = self.expressions(expression, sep="; ", flat=True) 6395 begin = "BEGIN " if expression.args.get("begin") else "" 6396 return f"{begin}{expressions}" if expressions else "" 6397 6398 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6399 self.unsupported("Unsupported Inline UDFs syntax") 6400 return "" 6401 6402 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6403 self.unsupported("Unsupported Stored Procedure syntax") 6404 return "" 6405 6406 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6407 self.unsupported("Unsupported If block syntax") 6408 return "" 6409 6410 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6411 self.unsupported("Unsupported Case statement syntax") 6412 return "" 6413 6414 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6415 self.unsupported("Unsupported While block syntax") 6416 return "" 6417 6418 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6419 self.unsupported("Unsupported Loop block syntax") 6420 return "" 6421 6422 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6423 self.unsupported("Unsupported Repeat block syntax") 6424 return "" 6425 6426 def leave_sql(self, expression: exp.Leave) -> str: 6427 self.unsupported("Unsupported Leave syntax") 6428 return "" 6429 6430 def iterate_sql(self, expression: exp.Iterate) -> str: 6431 self.unsupported("Unsupported Iterate syntax") 6432 return "" 6433 6434 def execute_sql(self, expression: exp.Execute) -> str: 6435 self.unsupported("Unsupported Execute syntax") 6436 return "" 6437 6438 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6439 self.unsupported("Unsupported Execute syntax") 6440 return "" 6441 6442 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6443 props = self.expressions(expression, sep=" ") 6444 return f"MODIFY {props}" 6445 6446 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6447 kind = expression.args.get("kind") 6448 return f"USING {kind} {self.sql(expression, 'this')}" 6449 6450 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6451 this = self.sql(expression, "this") 6452 to = self.sql(expression, "to") 6453 return f"RENAME INDEX {this} TO {to}"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether 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: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize 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: Whether 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 to preserve comments in the output SQL code. Default: True
Generator( pretty: bool | int | None = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: str | bool | None = 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, type[sqlglot.dialects.Dialect], NoneType] = None)
903 def __init__( 904 self, 905 pretty: bool | int | None = None, 906 identify: str | bool = False, 907 normalize: bool = False, 908 pad: int = 2, 909 indent: int = 2, 910 normalize_functions: str | bool | None = None, 911 unsupported_level: ErrorLevel = ErrorLevel.WARN, 912 max_unsupported: int = 3, 913 leading_comma: bool = False, 914 max_text_width: int = 80, 915 comments: bool = True, 916 dialect: DialectType = None, 917 ): 918 import sqlglot 919 import sqlglot.dialects.dialect 920 921 self.pretty = pretty if pretty is not None else sqlglot.pretty 922 self.identify = identify 923 self.normalize = normalize 924 self.pad = pad 925 self._indent = indent 926 self.unsupported_level = unsupported_level 927 self.max_unsupported = max_unsupported 928 self.leading_comma = leading_comma 929 self.max_text_width = max_text_width 930 self.comments = comments 931 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 932 933 # This is both a Dialect property and a Generator argument, so we prioritize the latter 934 self.normalize_functions = ( 935 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 936 ) 937 938 self.unsupported_messages: list[str] = [] 939 self._escaped_quote_end: str = ( 940 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 941 ) 942 self._escaped_byte_quote_end: str = ( 943 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 944 if self.dialect.BYTE_END 945 else "" 946 ) 947 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 948 949 self._next_name = name_sequence("_t") 950 951 self._identifier_start = self.dialect.IDENTIFIER_START 952 self._identifier_end = self.dialect.IDENTIFIER_END 953 954 self._quote_json_path_key_using_brackets = True 955 956 cls = type(self) 957 dispatch = _DISPATCH_CACHE.get(cls) 958 if dispatch is None: 959 dispatch = _build_dispatch(cls) 960 _DISPATCH_CACHE[cls] = dispatch 961 self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] =
{<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsTopKey'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] =
()
SUPPORTED_JSON_PATH_PARTS: ClassVar =
{<class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NCHAR: 'NCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
MOD_PAREN_PARENT_TYPES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Mul'>, <class 'sqlglot.expressions.core.Div'>, <class 'sqlglot.expressions.core.IntDiv'>, <class 'sqlglot.expressions.core.Mod'>)
963 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 964 """ 965 Generates the SQL string corresponding to the given syntax tree. 966 967 Args: 968 expression: The syntax tree. 969 copy: Whether to copy the expression. The generator performs mutations so 970 it is safer to copy. 971 972 Returns: 973 The SQL string corresponding to `expression`. 974 """ 975 if copy: 976 expression = expression.copy() 977 978 expression = self.preprocess(expression) 979 980 self.unsupported_messages = [] 981 sql = self.sql(expression).strip() 982 983 if self.pretty: 984 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 985 986 if self.unsupported_level == ErrorLevel.IGNORE: 987 return sql 988 989 if self.unsupported_level == ErrorLevel.WARN: 990 for msg in self.unsupported_messages: 991 logger.warning(msg) 992 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 993 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 994 995 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:
The SQL string corresponding to
expression.
997 def preprocess(self, expression: exp.Expr) -> exp.Expr: 998 """Apply generic preprocessing transformations to a given expression.""" 999 expression = self._move_ctes_to_top_level(expression) 1000 1001 if self.ENSURE_BOOLS: 1002 import sqlglot.transforms 1003 1004 expression = sqlglot.transforms.ensure_bools(expression) 1005 1006 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1030 def sanitize_comment(self, comment: str) -> str: 1031 comment = " " + comment if comment[0].strip() else comment 1032 comment = comment + " " if comment[-1].strip() else comment 1033 1034 # Escape block comment markers to prevent premature closure or unintended nesting. 1035 # This is necessary because single-line comments (--) are converted to block comments 1036 # (/* */) on output, and any */ in the original text would close the comment early. 1037 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1038 1039 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1041 def maybe_comment( 1042 self, 1043 sql: str, 1044 expression: exp.Expr | None = None, 1045 comments: list[str] | None = None, 1046 separated: bool = False, 1047 ) -> str: 1048 comments = ( 1049 ((expression and expression.comments) if comments is None else comments) # type: ignore 1050 if self.comments 1051 else None 1052 ) 1053 1054 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1055 return sql 1056 1057 comments_list = [ 1058 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1059 for comment in comments 1060 if comment 1061 ] 1062 1063 if not comments_list: 1064 return sql 1065 1066 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1067 comments_sql = self.sep().join(comments_list) 1068 return ( 1069 f"{self.sep()}{comments_sql}{sql}" 1070 if not sql or sql[0].isspace() 1071 else f"{comments_sql}{self.sep()}{sql}" 1072 ) 1073 1074 return f"{sql} {' '.join(comments_list)}"
1076 def wrap(self, expression: exp.Expr | str) -> str: 1077 this_sql = ( 1078 self.sql(expression) 1079 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1080 else self.sql(expression, "this") 1081 ) 1082 if not this_sql: 1083 return "()" 1084 1085 this_sql = self.indent(this_sql, level=1, pad=0) 1086 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: int | None = None, skip_first: bool = False, skip_last: bool = False) -> str:
1102 def indent( 1103 self, 1104 sql: str, 1105 level: int = 0, 1106 pad: int | None = None, 1107 skip_first: bool = False, 1108 skip_last: bool = False, 1109 ) -> str: 1110 if not self.pretty or not sql: 1111 return sql 1112 1113 pad = self.pad if pad is None else pad 1114 lines = sql.split("\n") 1115 1116 return "\n".join( 1117 ( 1118 line 1119 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1120 else f"{' ' * (level * self._indent + pad)}{line}" 1121 ) 1122 for i, line in enumerate(lines) 1123 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1125 def sql( 1126 self, 1127 expression: str | exp.Expr | None, 1128 key: str | None = None, 1129 comment: bool = True, 1130 ) -> str: 1131 if not expression: 1132 return "" 1133 1134 if isinstance(expression, str): 1135 return expression 1136 1137 if key: 1138 value = expression.args.get(key) 1139 if value: 1140 return self.sql(value) 1141 return "" 1142 1143 handler = self._dispatch.get(expression.__class__) 1144 1145 if handler: 1146 sql = handler(self, expression) 1147 elif isinstance(expression, exp.Func): 1148 sql = self.function_fallback_sql(expression) 1149 elif isinstance(expression, exp.Property): 1150 sql = self.property_sql(expression) 1151 else: 1152 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1153 1154 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1161 def cache_sql(self, expression: exp.Cache) -> str: 1162 lazy = " LAZY" if expression.args.get("lazy") else "" 1163 table = self.sql(expression, "this") 1164 options = expression.args.get("options") 1165 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1166 sql = self.sql(expression, "expression") 1167 sql = f" AS{self.sep()}{sql}" if sql else "" 1168 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1169 return self.prepend_ctes(expression, sql)
1175 def column_parts(self, expression: exp.Column) -> str: 1176 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1177 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1178 return self.sql(expression, "this") 1179 1180 return ".".join( 1181 self.sql(part) 1182 for part in ( 1183 expression.args.get("catalog"), 1184 expression.args.get("db"), 1185 expression.args.get("table"), 1186 expression.args.get("this"), 1187 ) 1188 if part 1189 )
1191 def column_sql(self, expression: exp.Column) -> str: 1192 join_mark = " (+)" if expression.args.get("join_mark") else "" 1193 1194 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1195 join_mark = "" 1196 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1197 1198 return f"{self.column_parts(expression)}{join_mark}"
1209 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1210 column = self.sql(expression, "this") 1211 kind = self.sql(expression, "kind") 1212 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1213 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1214 kind = f"{sep}{kind}" if kind else "" 1215 constraints = f" {constraints}" if constraints else "" 1216 position = self.sql(expression, "position") 1217 position = f" {position}" if position else "" 1218 1219 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1220 kind = "" 1221 1222 return f"{exists}{column}{kind}{constraints}{position}"
def
columnconstraint_sql( self, expression: sqlglot.expressions.constraints.ColumnConstraint) -> str:
def
computedcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.ComputedColumnConstraint) -> str:
1229 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1230 this = self.sql(expression, "this") 1231 if expression.args.get("not_null"): 1232 persisted = " PERSISTED NOT NULL" 1233 elif expression.args.get("persisted"): 1234 persisted = " PERSISTED" 1235 else: 1236 persisted = "" 1237 1238 return f"AS {this}{persisted}"
def
autoincrementcolumnconstraint_sql( self, _: sqlglot.expressions.constraints.AutoIncrementColumnConstraint) -> str:
def
compresscolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
1251 def generatedasidentitycolumnconstraint_sql( 1252 self, expression: exp.GeneratedAsIdentityColumnConstraint 1253 ) -> str: 1254 this = "" 1255 if expression.this is not None: 1256 on_null = " ON NULL" if expression.args.get("on_null") else "" 1257 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1258 1259 start = expression.args.get("start") 1260 start = f"START WITH {start}" if start else "" 1261 increment = expression.args.get("increment") 1262 increment = f" INCREMENT BY {increment}" if increment else "" 1263 minvalue = expression.args.get("minvalue") 1264 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1265 maxvalue = expression.args.get("maxvalue") 1266 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1267 cycle = expression.args.get("cycle") 1268 cycle_sql = "" 1269 1270 if cycle is not None: 1271 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1272 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1273 1274 sequence_opts = "" 1275 if start or increment or cycle_sql: 1276 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1277 sequence_opts = f" ({sequence_opts.strip()})" 1278 1279 expr = self.sql(expression, "expression") 1280 expr = f"({expr})" if expr else "IDENTITY" 1281 1282 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1284 def generatedasrowcolumnconstraint_sql( 1285 self, expression: exp.GeneratedAsRowColumnConstraint 1286 ) -> str: 1287 start = "START" if expression.args.get("start") else "END" 1288 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1289 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.constraints.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.NotNullColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.PrimaryKeyColumnConstraint) -> str:
1299 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1300 desc = expression.args.get("desc") 1301 if desc is not None: 1302 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1303 options = self.expressions(expression, key="options", flat=True, sep=" ") 1304 options = f" {options}" if options else "" 1305 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1307 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1308 this = self.sql(expression, "this") 1309 this = f" {this}" if this else "" 1310 index_type = expression.args.get("index_type") 1311 index_type = f" USING {index_type}" if index_type else "" 1312 on_conflict = self.sql(expression, "on_conflict") 1313 on_conflict = f" {on_conflict}" if on_conflict else "" 1314 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1315 options = self.expressions(expression, key="options", flat=True, sep=" ") 1316 options = f" {options}" if options else "" 1317 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1319 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1320 input_ = expression.args.get("input_") 1321 output = expression.args.get("output") 1322 variadic = expression.args.get("variadic") 1323 1324 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1325 if variadic: 1326 return "VARIADIC" 1327 1328 if input_ and output: 1329 return f"IN{self.INOUT_SEPARATOR}OUT" 1330 if input_: 1331 return "IN" 1332 if output: 1333 return "OUT" 1334 1335 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1340 def create_sql(self, expression: exp.Create) -> str: 1341 kind = self.sql(expression, "kind") 1342 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1343 1344 properties = expression.args.get("properties") 1345 1346 if ( 1347 kind == "TRIGGER" 1348 and properties 1349 and properties.expressions 1350 and isinstance(properties.expressions[0], exp.TriggerProperties) 1351 and properties.expressions[0].args.get("constraint") 1352 ): 1353 kind = f"CONSTRAINT {kind}" 1354 1355 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1356 1357 this = self.createable_sql(expression, properties_locs) 1358 1359 properties_sql = "" 1360 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1361 exp.Properties.Location.POST_WITH 1362 ): 1363 props_ast = exp.Properties( 1364 expressions=[ 1365 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1366 *properties_locs[exp.Properties.Location.POST_WITH], 1367 ] 1368 ) 1369 props_ast.parent = expression 1370 properties_sql = self.sql(props_ast) 1371 1372 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1373 properties_sql = self.sep() + properties_sql 1374 elif not self.pretty: 1375 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1376 properties_sql = f" {properties_sql}" 1377 1378 begin = " BEGIN" if expression.args.get("begin") else "" 1379 1380 expression_sql = self.sql(expression, "expression") 1381 if expression_sql: 1382 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1383 1384 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1385 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1386 ): 1387 postalias_props_sql = "" 1388 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1389 postalias_props_sql = self.properties( 1390 exp.Properties( 1391 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1392 ), 1393 wrapped=False, 1394 ) 1395 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1396 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1397 1398 postindex_props_sql = "" 1399 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1400 postindex_props_sql = self.properties( 1401 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1402 wrapped=False, 1403 prefix=" ", 1404 ) 1405 1406 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1407 indexes = f" {indexes}" if indexes else "" 1408 index_sql = indexes + postindex_props_sql 1409 1410 replace = " OR REPLACE" if expression.args.get("replace") else "" 1411 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1412 unique = " UNIQUE" if expression.args.get("unique") else "" 1413 1414 clustered = expression.args.get("clustered") 1415 if clustered is None: 1416 clustered_sql = "" 1417 elif clustered: 1418 clustered_sql = " CLUSTERED COLUMNSTORE" 1419 else: 1420 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1421 1422 postcreate_props_sql = "" 1423 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1424 postcreate_props_sql = self.properties( 1425 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1426 sep=" ", 1427 prefix=" ", 1428 wrapped=False, 1429 ) 1430 1431 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1432 1433 postexpression_props_sql = "" 1434 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1435 postexpression_props_sql = self.properties( 1436 exp.Properties( 1437 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1438 ), 1439 sep=" ", 1440 prefix=" ", 1441 wrapped=False, 1442 ) 1443 1444 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1445 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1446 no_schema_binding = ( 1447 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1448 ) 1449 1450 clone = self.sql(expression, "clone") 1451 clone = f" {clone}" if clone else "" 1452 1453 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1454 properties_expression = f"{expression_sql}{properties_sql}" 1455 else: 1456 properties_expression = f"{properties_sql}{expression_sql}" 1457 1458 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1459 return self.prepend_ctes(expression, expression_sql)
1461 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1462 start = self.sql(expression, "start") 1463 start = f"START WITH {start}" if start else "" 1464 increment = self.sql(expression, "increment") 1465 increment = f" INCREMENT BY {increment}" if increment else "" 1466 minvalue = self.sql(expression, "minvalue") 1467 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1468 maxvalue = self.sql(expression, "maxvalue") 1469 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1470 owned = self.sql(expression, "owned") 1471 owned = f" OWNED BY {owned}" if owned else "" 1472 1473 cache = expression.args.get("cache") 1474 if cache is None: 1475 cache_str = "" 1476 elif cache is True: 1477 cache_str = " CACHE" 1478 else: 1479 cache_str = f" CACHE {cache}" 1480 1481 options = self.expressions(expression, key="options", flat=True, sep=" ") 1482 options = f" {options}" if options else "" 1483 1484 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1486 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1487 timing = expression.args.get("timing", "") 1488 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1489 timing_events = f"{timing} {events}".strip() if timing or events else "" 1490 1491 parts = [timing_events, "ON", self.sql(expression, "table")] 1492 1493 if referenced_table := expression.args.get("referenced_table"): 1494 parts.extend(["FROM", self.sql(referenced_table)]) 1495 1496 if deferrable := expression.args.get("deferrable"): 1497 parts.append(deferrable) 1498 1499 if initially := expression.args.get("initially"): 1500 parts.append(f"INITIALLY {initially}") 1501 1502 if referencing := expression.args.get("referencing"): 1503 parts.append(self.sql(referencing)) 1504 1505 if for_each := expression.args.get("for_each"): 1506 parts.append(f"FOR EACH {for_each}") 1507 1508 if when := expression.args.get("when"): 1509 parts.append(f"WHEN ({self.sql(when)})") 1510 1511 parts.append(self.sql(expression, "execute")) 1512 1513 return self.sep().join(parts)
1515 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1516 parts = [] 1517 1518 if old_alias := expression.args.get("old"): 1519 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1520 1521 if new_alias := expression.args.get("new"): 1522 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1523 1524 return f"REFERENCING {' '.join(parts)}"
1533 def clone_sql(self, expression: exp.Clone) -> str: 1534 this = self.sql(expression, "this") 1535 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1536 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1537 return f"{shallow}{keyword} {this}"
1539 def describe_sql(self, expression: exp.Describe) -> str: 1540 style = expression.args.get("style") 1541 style = f" {style}" if style else "" 1542 partition = self.sql(expression, "partition") 1543 partition = f" {partition}" if partition else "" 1544 format = self.sql(expression, "format") 1545 format = f" {format}" if format else "" 1546 as_json = " AS JSON" if expression.args.get("as_json") else "" 1547 1548 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1560 def with_sql(self, expression: exp.With) -> str: 1561 udfs = self.expressions(expression, key="udfs", flat=True) 1562 udfs = f"WITH {udfs}" if udfs else "" 1563 1564 sql = self.expressions(expression, flat=True) 1565 1566 recursive = ( 1567 "RECURSIVE " 1568 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1569 else "" 1570 ) 1571 search = self.sql(expression, "search") 1572 search = f" {search}" if search else "" 1573 1574 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1575 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1577 def cte_sql(self, expression: exp.CTE) -> str: 1578 alias = expression.args.get("alias") 1579 if alias: 1580 alias.add_comments(expression.pop_comments()) 1581 1582 alias_sql = self.sql(expression, "alias") 1583 1584 materialized = expression.args.get("materialized") 1585 if materialized is False: 1586 materialized = "NOT MATERIALIZED " 1587 elif materialized: 1588 materialized = "MATERIALIZED " 1589 1590 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1591 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1592 1593 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1595 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1596 alias = self.sql(expression, "this") 1597 columns = self.expressions(expression, key="columns", flat=True) 1598 columns = f"({columns})" if columns else "" 1599 1600 if ( 1601 columns 1602 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1603 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1604 ): 1605 columns = "" 1606 self.unsupported("Named columns are not supported in table alias.") 1607 1608 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1609 alias = self._next_name() 1610 1611 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1619 def hexstring_sql( 1620 self, expression: exp.HexString, binary_function_repr: str | None = None 1621 ) -> str: 1622 this = self.sql(expression, "this") 1623 is_integer_type = expression.args.get("is_integer") 1624 1625 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1626 not self.dialect.HEX_START and not binary_function_repr 1627 ): 1628 # Integer representation will be returned if: 1629 # - The read dialect treats the hex value as integer literal but not the write 1630 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1631 return f"{int(this, 16)}" 1632 1633 if not is_integer_type: 1634 # Read dialect treats the hex value as BINARY/BLOB 1635 if binary_function_repr: 1636 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1637 return self.func(binary_function_repr, exp.Literal.string(this)) 1638 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1639 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1640 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1641 1642 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1644 def bytestring_sql(self, expression: exp.ByteString) -> str: 1645 this = self.sql(expression, "this") 1646 if self.dialect.BYTE_START: 1647 escaped_byte_string = self.escape_str( 1648 this, 1649 escape_backslash=False, 1650 delimiter=self.dialect.BYTE_END, 1651 escaped_delimiter=self._escaped_byte_quote_end, 1652 is_byte_string=True, 1653 ) 1654 is_bytes = expression.args.get("is_bytes", False) 1655 delimited_byte_string = ( 1656 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1657 ) 1658 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1659 return self.sql( 1660 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1661 ) 1662 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1663 return self.sql( 1664 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1665 ) 1666 1667 return delimited_byte_string 1668 1669 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1670 return self.sql(exp.Literal.string(this)) 1671 1672 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1673 return ""
1675 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1676 this = self.sql(expression, "this") 1677 escape = expression.args.get("escape") 1678 unicode_start = self.dialect.UNICODE_START 1679 1680 if unicode_start: 1681 escape_substitute = r"\\\1" 1682 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1683 else: 1684 escape_substitute = r"\\u\1" 1685 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1686 1687 if escape: 1688 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1689 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1690 else: 1691 escape_pattern = ESCAPED_UNICODE_RE 1692 escape_sql = "" 1693 1694 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1695 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1696 1697 if unicode_start: 1698 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1699 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1700 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1701 else: 1702 this = self.escape_str(this, escape_backslash=False) 1703 1704 return f"{left_quote}{this}{right_quote}{escape_sql}"
1706 def rawstring_sql(self, expression: exp.RawString) -> str: 1707 string = expression.this 1708 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1709 string = string.replace("\\", "\\\\") 1710 1711 string = self.escape_str(string, escape_backslash=False) 1712 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def
datatype_param_bound_limiter( self, expression: sqlglot.expressions.datatypes.DataType, type_value: sqlglot.expressions.datatypes.DType, defaults: tuple[int, ...], bounds: tuple[int | None, ...]) -> sqlglot.expressions.datatypes.DataType:
1720 def datatype_param_bound_limiter( 1721 self, 1722 expression: exp.DataType, 1723 type_value: exp.DType, 1724 defaults: tuple[int, ...], 1725 bounds: tuple[int | None, ...], 1726 ) -> exp.DataType: 1727 params = expression.expressions 1728 1729 if not params: 1730 if defaults: 1731 expression.set( 1732 "expressions", 1733 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1734 ) 1735 return expression 1736 1737 if not bounds: 1738 return expression 1739 1740 for i, param in enumerate(params): 1741 bound = bounds[i] if i < len(bounds) else None 1742 if bound is None: 1743 continue 1744 1745 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1746 value = ( 1747 param_value.to_py() 1748 if isinstance(param_value, exp.Literal) and param_value.is_number 1749 else None 1750 ) 1751 if isinstance(value, (int, Decimal)) and value > bound: 1752 self.unsupported( 1753 f"{type_value.value} parameter {param_value.name} exceeds " 1754 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1755 ) 1756 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1757 1758 return expression
1760 def datatype_sql(self, expression: exp.DataType) -> str: 1761 nested = "" 1762 values = "" 1763 1764 expr_nested = expression.args.get("nested") 1765 type_value = expression.this 1766 1767 if ( 1768 not expr_nested 1769 and isinstance(type_value, exp.DType) 1770 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1771 ): 1772 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1773 1774 interior = ( 1775 self.expressions( 1776 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1777 ) 1778 if expr_nested and self.pretty 1779 else self.expressions(expression, flat=True) 1780 ) 1781 1782 if type_value in self.UNSUPPORTED_TYPES: 1783 self.unsupported( 1784 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1785 ) 1786 1787 type_sql: t.Any = "" 1788 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1789 type_sql = self.sql(expression, "kind") 1790 elif type_value == exp.DType.CHARACTER_SET: 1791 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1792 else: 1793 type_sql = ( 1794 self.TYPE_MAPPING.get(type_value, type_value.value) 1795 if isinstance(type_value, exp.DType) 1796 else type_value 1797 ) 1798 1799 if interior: 1800 if expr_nested: 1801 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1802 if expression.args.get("values") is not None: 1803 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1804 values = self.expressions(expression, key="values", flat=True) 1805 values = f"{delimiters[0]}{values}{delimiters[1]}" 1806 elif type_value == exp.DType.INTERVAL: 1807 nested = f" {interior}" 1808 else: 1809 nested = f"({interior})" 1810 1811 type_sql = f"{type_sql}{nested}{values}" 1812 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1813 exp.DType.TIMETZ, 1814 exp.DType.TIMESTAMPTZ, 1815 ): 1816 type_sql = f"{type_sql} WITH TIME ZONE" 1817 1818 collate = self.sql(expression, "collate") 1819 if collate: 1820 type_sql = f"{type_sql} COLLATE {collate}" 1821 1822 return type_sql
1824 def directory_sql(self, expression: exp.Directory) -> str: 1825 local = "LOCAL " if expression.args.get("local") else "" 1826 row_format = self.sql(expression, "row_format") 1827 row_format = f" {row_format}" if row_format else "" 1828 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1830 def delete_sql(self, expression: exp.Delete) -> str: 1831 hint = self.sql(expression, "hint") 1832 this = self.sql(expression, "this") 1833 this = f" FROM {this}" if this else "" 1834 using = self.expressions(expression, key="using") 1835 using = f" USING {using}" if using else "" 1836 cluster = self.sql(expression, "cluster") 1837 cluster = f" {cluster}" if cluster else "" 1838 where = self.sql(expression, "where") 1839 returning = self.sql(expression, "returning") 1840 order = self.sql(expression, "order") 1841 limit = self.sql(expression, "limit") 1842 tables = self.expressions(expression, key="tables") 1843 tables = f" {tables}" if tables else "" 1844 if self.RETURNING_END: 1845 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1846 else: 1847 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1848 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1850 def drop_sql(self, expression: exp.Drop) -> str: 1851 tables = self.expressions(expression, key="tables", flat=True) 1852 expressions = self.expressions(expression, flat=True) 1853 expressions = f" ({expressions})" if expressions else "" 1854 kind = expression.args["kind"] 1855 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1856 iceberg = ( 1857 " ICEBERG" 1858 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1859 else "" 1860 ) 1861 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1862 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1863 on_cluster = self.sql(expression, "cluster") 1864 on_cluster = f" {on_cluster}" if on_cluster else "" 1865 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1866 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1867 cascade = " CASCADE" if expression.args.get("cascade") else "" 1868 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1869 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1870 purge = " PURGE" if expression.args.get("purge") else "" 1871 sync = " SYNC" if expression.args.get("sync") else "" 1872 force = " FORCE" if expression.args.get("force") else "" 1873 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1875 def set_operation(self, expression: exp.SetOperation) -> str: 1876 op_type = type(expression) 1877 op_name = op_type.key.upper() 1878 1879 distinct = expression.args.get("distinct") 1880 if ( 1881 distinct is False 1882 and op_type in (exp.Except, exp.Intersect) 1883 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1884 ): 1885 self.unsupported(f"{op_name} ALL is not supported") 1886 1887 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1888 1889 if distinct is None: 1890 distinct = default_distinct 1891 if distinct is None: 1892 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1893 1894 if distinct is default_distinct: 1895 distinct_or_all = "" 1896 else: 1897 distinct_or_all = " DISTINCT" if distinct else " ALL" 1898 1899 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1900 side_kind = f"{side_kind} " if side_kind else "" 1901 1902 by_name = " BY NAME" if expression.args.get("by_name") else "" 1903 on = self.expressions(expression, key="on", flat=True) 1904 on = f" ON ({on})" if on else "" 1905 1906 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1908 def set_operations(self, expression: exp.SetOperation) -> str: 1909 if not self.SET_OP_MODIFIERS: 1910 limit = expression.args.get("limit") 1911 order = expression.args.get("order") 1912 offset = expression.args.get("offset") 1913 1914 if limit or order or offset: 1915 select = self._move_ctes_to_top_level( 1916 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1917 ) 1918 1919 for arg in ("limit", "order", "offset"): 1920 if value := expression.args.get(arg): 1921 select.set(arg, value.pop()) 1922 return self.sql(select) 1923 1924 sqls: list[str] = [] 1925 stack: list[str | exp.Expr] = [expression] 1926 1927 while stack: 1928 node = stack.pop() 1929 1930 if isinstance(node, exp.SetOperation): 1931 stack.append(node.expression) 1932 stack.append( 1933 self.maybe_comment( 1934 self.set_operation(node), comments=node.comments, separated=True 1935 ) 1936 ) 1937 stack.append(node.this) 1938 else: 1939 if ( 1940 not self.SET_OP_LIMITS 1941 and isinstance(node, exp.Select) 1942 and node.args.get("limit") 1943 ): 1944 node = node.subquery(copy=False) 1945 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1946 node = exp.select("*").from_(node, copy=False) 1947 sqls.append(self.sql(node)) 1948 1949 this = self.sep().join(sqls) 1950 this = self.query_modifiers(expression, this) 1951 return self.prepend_ctes(expression, this)
1953 def fetch_sql(self, expression: exp.Fetch) -> str: 1954 direction = expression.args.get("direction") 1955 direction = f" {direction}" if direction else "" 1956 count = self.sql(expression, "count") 1957 count = f" {count}" if count else "" 1958 limit_options = self.sql(expression, "limit_options") 1959 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1960 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1962 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1963 percent = " PERCENT" if expression.args.get("percent") else "" 1964 rows = " ROWS" if expression.args.get("rows") else "" 1965 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1966 if not with_ties and rows: 1967 with_ties = " ONLY" 1968 return f"{percent}{rows}{with_ties}"
1982 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1983 using = self.sql(expression, "using") 1984 using = f" USING {using}" if using else "" 1985 columns = self.expressions(expression, key="columns", flat=True) 1986 columns = f"({columns})" if columns else "" 1987 partition_by = self.expressions(expression, key="partition_by", flat=True) 1988 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1989 where = self.sql(expression, "where") 1990 include = self.expressions(expression, key="include", flat=True) 1991 if include: 1992 include = f" INCLUDE ({include})" 1993 with_storage = self.expressions(expression, key="with_storage", flat=True) 1994 with_storage = f" WITH ({with_storage})" if with_storage else "" 1995 tablespace = self.sql(expression, "tablespace") 1996 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1997 on = self.sql(expression, "on") 1998 on = f" ON {on}" if on else "" 1999 2000 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2002 def index_sql(self, expression: exp.Index) -> str: 2003 unique = "UNIQUE " if expression.args.get("unique") else "" 2004 primary = "PRIMARY " if expression.args.get("primary") else "" 2005 amp = "AMP " if expression.args.get("amp") else "" 2006 name = self.sql(expression, "this") 2007 name = f"{name} " if name else "" 2008 table = self.sql(expression, "table") 2009 table = f"{self.INDEX_ON} {table}" if table else "" 2010 2011 index = "INDEX " if not table else "" 2012 2013 params = self.sql(expression, "params") 2014 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2016 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2017 this = expression.this 2018 if this and this.is_string: 2019 resolved = maybe_parse(this.name).sql(self.dialect) 2020 if "expressions" in expression.args: 2021 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2022 # We can't safely emit the call to other dialects since name/arg semantics may differ 2023 self.unsupported( 2024 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2025 ) 2026 return resolved 2027 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2028 return self.func("IDENTIFIER", this)
2030 def identifier_sql(self, expression: exp.Identifier) -> str: 2031 text = expression.name 2032 lower = text.lower() 2033 quoted = expression.quoted 2034 text = lower if self.normalize and not quoted else text 2035 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2036 if ( 2037 quoted 2038 or self.dialect.can_quote(expression, self.identify) 2039 or lower in self.RESERVED_KEYWORDS 2040 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2041 ): 2042 text = ( 2043 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2044 ) 2045 return text
2060 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2061 input_format = self.sql(expression, "input_format") 2062 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2063 output_format = self.sql(expression, "output_format") 2064 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2065 return self.sep().join((input_format, output_format))
2075 def properties_sql(self, expression: exp.Properties) -> str: 2076 root_properties = [] 2077 with_properties = [] 2078 2079 for p in expression.expressions: 2080 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2081 if p_loc == exp.Properties.Location.POST_WITH: 2082 with_properties.append(p) 2083 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2084 root_properties.append(p) 2085 2086 root_props_ast = exp.Properties(expressions=root_properties) 2087 root_props_ast.parent = expression.parent 2088 2089 with_props_ast = exp.Properties(expressions=with_properties) 2090 with_props_ast.parent = expression.parent 2091 2092 root_props = self.root_properties(root_props_ast) 2093 with_props = self.with_properties(with_props_ast) 2094 2095 if root_props and with_props and not self.pretty: 2096 with_props = " " + with_props 2097 2098 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2105 def properties( 2106 self, 2107 properties: exp.Properties, 2108 prefix: str = "", 2109 sep: str = ", ", 2110 suffix: str = "", 2111 wrapped: bool = True, 2112 ) -> str: 2113 if properties.expressions: 2114 expressions = self.expressions(properties, sep=sep, indent=False) 2115 if expressions: 2116 expressions = self.wrap(expressions) if wrapped else expressions 2117 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2118 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2123 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2124 properties_locs = defaultdict(list) 2125 for p in properties.expressions: 2126 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2127 if p_loc != exp.Properties.Location.UNSUPPORTED: 2128 properties_locs[p_loc].append(p) 2129 else: 2130 self.unsupported(f"Unsupported property {p.key}") 2131 2132 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2139 def property_sql(self, expression: exp.Property) -> str: 2140 property_cls = expression.__class__ 2141 if property_cls == exp.Property: 2142 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2143 2144 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2145 if not property_name: 2146 self.unsupported(f"Unsupported property {expression.key}") 2147 2148 return f"{property_name}={self.sql(expression, 'this')}"
2153 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2154 if self.SUPPORTS_CREATE_TABLE_LIKE: 2155 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2156 options = f" {options}" if options else "" 2157 2158 like = f"LIKE {self.sql(expression, 'this')}{options}" 2159 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2160 like = f"({like})" 2161 2162 return like 2163 2164 if expression.expressions: 2165 self.unsupported("Transpilation of LIKE property options is unsupported") 2166 2167 select = exp.select("*").from_(expression.this).limit(0) 2168 return f"AS {self.sql(select)}"
2175 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2176 no = "NO " if expression.args.get("no") else "" 2177 local = expression.args.get("local") 2178 local = f"{local} " if local else "" 2179 dual = "DUAL " if expression.args.get("dual") else "" 2180 before = "BEFORE " if expression.args.get("before") else "" 2181 after = "AFTER " if expression.args.get("after") else "" 2182 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
freespaceproperty_sql( self, expression: sqlglot.expressions.properties.FreespaceProperty) -> str:
def
mergeblockratioproperty_sql( self, expression: sqlglot.expressions.properties.MergeBlockRatioProperty) -> str:
2198 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2199 if expression.args.get("no"): 2200 return "NO MERGEBLOCKRATIO" 2201 if expression.args.get("default"): 2202 return "DEFAULT MERGEBLOCKRATIO" 2203 2204 percent = " PERCENT" if expression.args.get("percent") else "" 2205 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2212 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2213 default = expression.args.get("default") 2214 minimum = expression.args.get("minimum") 2215 maximum = expression.args.get("maximum") 2216 if default or minimum or maximum: 2217 if default: 2218 prop = "DEFAULT" 2219 elif minimum: 2220 prop = "MINIMUM" 2221 else: 2222 prop = "MAXIMUM" 2223 return f"{prop} DATABLOCKSIZE" 2224 units = expression.args.get("units") 2225 units = f" {units}" if units else "" 2226 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2228 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2229 autotemp = expression.args.get("autotemp") 2230 always = expression.args.get("always") 2231 default = expression.args.get("default") 2232 manual = expression.args.get("manual") 2233 never = expression.args.get("never") 2234 2235 if autotemp is not None: 2236 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2237 elif always: 2238 prop = "ALWAYS" 2239 elif default: 2240 prop = "DEFAULT" 2241 elif manual: 2242 prop = "MANUAL" 2243 elif never: 2244 prop = "NEVER" 2245 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2247 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2248 no = expression.args.get("no") 2249 no = " NO" if no else "" 2250 concurrent = expression.args.get("concurrent") 2251 concurrent = " CONCURRENT" if concurrent else "" 2252 target = self.sql(expression, "target") 2253 target = f" {target}" if target else "" 2254 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2256 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2257 if isinstance(expression.this, list): 2258 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2259 if expression.this: 2260 modulus = self.sql(expression, "this") 2261 remainder = self.sql(expression, "expression") 2262 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2263 2264 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2265 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2266 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2268 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2269 this = self.sql(expression, "this") 2270 2271 for_values_or_default = expression.expression 2272 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2273 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2274 else: 2275 for_values_or_default = " DEFAULT" 2276 2277 return f"PARTITION OF {this}{for_values_or_default}"
2279 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2280 kind = expression.args.get("kind") 2281 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2282 for_or_in = expression.args.get("for_or_in") 2283 for_or_in = f" {for_or_in}" if for_or_in else "" 2284 lock_type = expression.args.get("lock_type") 2285 override = " OVERRIDE" if expression.args.get("override") else "" 2286 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2288 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2289 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2290 statistics = expression.args.get("statistics") 2291 statistics_sql = "" 2292 if statistics is not None: 2293 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2294 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2296 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2297 this = self.sql(expression, "this") 2298 this = f"HISTORY_TABLE={this}" if this else "" 2299 data_consistency: str | None = self.sql(expression, "data_consistency") 2300 data_consistency = ( 2301 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2302 ) 2303 retention_period: str | None = self.sql(expression, "retention_period") 2304 retention_period = ( 2305 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2306 ) 2307 2308 if this: 2309 on_sql = self.func("ON", this, data_consistency, retention_period) 2310 else: 2311 on_sql = "ON" if expression.args.get("on") else "OFF" 2312 2313 sql = f"SYSTEM_VERSIONING={on_sql}" 2314 2315 return f"WITH({sql})" if expression.args.get("with_") else sql
2317 def insert_sql(self, expression: exp.Insert) -> str: 2318 hint = self.sql(expression, "hint") 2319 overwrite = expression.args.get("overwrite") 2320 2321 if isinstance(expression.this, exp.Directory): 2322 this = " OVERWRITE" if overwrite else " INTO" 2323 else: 2324 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2325 2326 stored = self.sql(expression, "stored") 2327 stored = f" {stored}" if stored else "" 2328 alternative = expression.args.get("alternative") 2329 alternative = f" OR {alternative}" if alternative else "" 2330 ignore = " IGNORE" if expression.args.get("ignore") else "" 2331 is_function = expression.args.get("is_function") 2332 if is_function: 2333 this = f"{this} FUNCTION" 2334 this = f"{this} {self.sql(expression, 'this')}" 2335 2336 exists = " IF EXISTS" if expression.args.get("exists") else "" 2337 where = self.sql(expression, "where") 2338 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2339 using = self.expressions(expression, key="using", flat=True) 2340 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2341 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2342 on_conflict = self.sql(expression, "conflict") 2343 on_conflict = f" {on_conflict}" if on_conflict else "" 2344 by_name = " BY NAME" if expression.args.get("by_name") else "" 2345 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2346 returning = self.sql(expression, "returning") 2347 2348 if self.RETURNING_END: 2349 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2350 else: 2351 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2352 2353 partition_by = self.sql(expression, "partition") 2354 partition_by = f" {partition_by}" if partition_by else "" 2355 settings = self.sql(expression, "settings") 2356 settings = f" {settings}" if settings else "" 2357 2358 source = self.sql(expression, "source") 2359 source = f"TABLE {source}" if source else "" 2360 2361 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2362 return self.prepend_ctes(expression, sql)
2380 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2381 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2382 2383 constraint = self.sql(expression, "constraint") 2384 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2385 2386 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2387 if conflict_keys: 2388 conflict_keys = f"({conflict_keys})" 2389 2390 index_predicate = self.sql(expression, "index_predicate") 2391 conflict_keys = f"{conflict_keys}{index_predicate} " 2392 2393 action = self.sql(expression, "action") 2394 2395 expressions = self.expressions(expression, flat=True) 2396 if expressions: 2397 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2398 expressions = f" {set_keyword}{expressions}" 2399 2400 where = self.sql(expression, "where") 2401 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2406 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2407 fields = self.sql(expression, "fields") 2408 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2409 escaped = self.sql(expression, "escaped") 2410 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2411 items = self.sql(expression, "collection_items") 2412 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2413 keys = self.sql(expression, "map_keys") 2414 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2415 lines = self.sql(expression, "lines") 2416 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2417 null = self.sql(expression, "null") 2418 null = f" NULL DEFINED AS {null}" if null else "" 2419 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2447 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2448 table = self.table_parts(expression) 2449 only = "ONLY " if expression.args.get("only") else "" 2450 partition = self.sql(expression, "partition") 2451 partition = f" {partition}" if partition else "" 2452 version = self.sql(expression, "version") 2453 version = f" {version}" if version else "" 2454 alias = self.sql(expression, "alias") 2455 alias = f"{sep}{alias}" if alias else "" 2456 2457 sample = self.sql(expression, "sample") 2458 post_alias = "" 2459 pre_alias = "" 2460 2461 if self.dialect.ALIAS_POST_TABLESAMPLE: 2462 pre_alias = sample 2463 else: 2464 post_alias = sample 2465 2466 if self.dialect.ALIAS_POST_VERSION: 2467 pre_alias = f"{pre_alias}{version}" 2468 else: 2469 post_alias = f"{post_alias}{version}" 2470 2471 hints = self.expressions(expression, key="hints", sep=" ") 2472 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2473 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2474 joins = self.indent( 2475 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2476 ) 2477 laterals = self.expressions(expression, key="laterals", sep="") 2478 2479 file_format = self.sql(expression, "format") 2480 pattern = self.sql(expression, "pattern") 2481 if file_format: 2482 pattern = f", PATTERN => {pattern}" if pattern else "" 2483 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2484 elif pattern: 2485 file_format = f" (PATTERN => {pattern})" 2486 2487 ordinality = expression.args.get("ordinality") or "" 2488 if ordinality: 2489 ordinality = f" WITH ORDINALITY{alias}" 2490 alias = "" 2491 2492 when = self.sql(expression, "when") 2493 if when: 2494 if self.HISTORICAL_DATA_POST_ALIAS: 2495 alias = f"{alias} {when}" 2496 else: 2497 table = f"{table} {when}" 2498 2499 changes = self.sql(expression, "changes") 2500 changes = f" {changes}" if changes else "" 2501 2502 rows_from = self.expressions(expression, key="rows_from") 2503 if rows_from: 2504 table = f"ROWS FROM {self.wrap(rows_from)}" 2505 2506 indexed = expression.args.get("indexed") 2507 if indexed is not None: 2508 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2509 else: 2510 indexed = "" 2511 2512 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2514 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2515 table = self.func("TABLE", expression.this) 2516 alias = self.sql(expression, "alias") 2517 alias = f" AS {alias}" if alias else "" 2518 sample = self.sql(expression, "sample") 2519 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2520 joins = self.indent( 2521 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2522 ) 2523 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2525 def tablesample_sql( 2526 self, 2527 expression: exp.TableSample, 2528 tablesample_keyword: str | None = None, 2529 ) -> str: 2530 method = self.sql(expression, "method") 2531 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2532 numerator = self.sql(expression, "bucket_numerator") 2533 denominator = self.sql(expression, "bucket_denominator") 2534 field = self.sql(expression, "bucket_field") 2535 field = f" ON {field}" if field else "" 2536 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2537 seed = self.sql(expression, "seed") 2538 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2539 2540 size = self.sql(expression, "size") 2541 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2542 size = f"{size} ROWS" 2543 2544 percent = self.sql(expression, "percent") 2545 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2546 percent = f"{percent} PERCENT" 2547 2548 expr = f"{bucket}{percent}{size}" 2549 if self.TABLESAMPLE_REQUIRES_PARENS: 2550 expr = f"({expr})" 2551 2552 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2629 def pivot_sql(self, expression: exp.Pivot) -> str: 2630 expressions = self.expressions(expression, flat=True) 2631 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2632 2633 group = self.sql(expression, "group") 2634 2635 if expression.this: 2636 this = self.sql(expression, "this") 2637 if not expressions: 2638 sql = f"UNPIVOT {this}" 2639 else: 2640 on = f"{self.seg('ON')} {expressions}" 2641 into = self.sql(expression, "into") 2642 into = f"{self.seg('INTO')} {into}" if into else "" 2643 using = self.expressions(expression, key="using", flat=True) 2644 using = f"{self.seg('USING')} {using}" if using else "" 2645 sql = f"{direction} {this}{on}{into}{using}{group}" 2646 return self.prepend_ctes(expression, sql) 2647 2648 if not expression.unpivot: 2649 # Wrap IN-list values with explicit aliases where the target dialect would differ 2650 new_field_exprs = self._pivot_in_value_aliases(expression) 2651 if new_field_exprs is not None: 2652 expression.fields[0].set("expressions", new_field_exprs) 2653 2654 alias = self.sql(expression, "alias") 2655 if alias: 2656 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2657 2658 fields = self.expressions( 2659 expression, 2660 "fields", 2661 sep=" ", 2662 dynamic=True, 2663 new_line=True, 2664 skip_first=True, 2665 skip_last=True, 2666 ) 2667 2668 include_nulls = expression.args.get("include_nulls") 2669 if include_nulls is not None: 2670 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2671 else: 2672 nulls = "" 2673 2674 default_on_null = self.sql(expression, "default_on_null") 2675 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2676 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2677 return self.prepend_ctes(expression, sql)
2720 def update_sql(self, expression: exp.Update) -> str: 2721 hint = self.sql(expression, "hint") 2722 this = self.sql(expression, "this") 2723 join_sql, from_sql = self._update_from_joins_sql(expression) 2724 set_sql = self.expressions(expression, flat=True) 2725 where_sql = self.sql(expression, "where") 2726 returning = self.sql(expression, "returning") 2727 order = self.sql(expression, "order") 2728 limit = self.sql(expression, "limit") 2729 if self.RETURNING_END: 2730 expression_sql = f"{from_sql}{where_sql}{returning}" 2731 else: 2732 expression_sql = f"{returning}{from_sql}{where_sql}" 2733 options = self.expressions(expression, key="options") 2734 options = f" OPTION({options})" if options else "" 2735 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2736 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2738 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2739 values_as_table = values_as_table and self.VALUES_AS_TABLE 2740 2741 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2742 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2743 args = self.expressions(expression) 2744 alias = self.sql(expression, "alias") 2745 values = f"VALUES{self.seg('')}{args}" 2746 values = ( 2747 f"({values})" 2748 if self.WRAP_DERIVED_VALUES 2749 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2750 else values 2751 ) 2752 values = self.query_modifiers(expression, values) 2753 return f"{values} AS {alias}" if alias else values 2754 2755 # Converts `VALUES...` expression into a series of select unions. 2756 alias_node = expression.args.get("alias") 2757 column_names = alias_node and alias_node.columns 2758 2759 selects: list[exp.Query] = [] 2760 2761 for i, tup in enumerate(expression.expressions): 2762 row = tup.expressions 2763 2764 if i == 0 and column_names: 2765 row = [ 2766 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2767 ] 2768 2769 selects.append(exp.Select(expressions=row)) 2770 2771 if self.pretty: 2772 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2773 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2774 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2775 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2776 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2777 2778 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2779 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2780 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2785 @unsupported_args("expressions") 2786 def into_sql(self, expression: exp.Into) -> str: 2787 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2788 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2789 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2802 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2803 this = self.sql(expression, "this") 2804 2805 columns = self.expressions(expression, flat=True) 2806 2807 from_sql = self.sql(expression, "from_index") 2808 from_sql = f" FROM {from_sql}" if from_sql else "" 2809 2810 properties = expression.args.get("properties") 2811 properties_sql = ( 2812 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2813 ) 2814 2815 return f"{this}({columns}){from_sql}{properties_sql}"
2824 def group_sql(self, expression: exp.Group) -> str: 2825 group_by_all = expression.args.get("all") 2826 if group_by_all is True: 2827 modifier = " ALL" 2828 elif group_by_all is False: 2829 modifier = " DISTINCT" 2830 else: 2831 modifier = "" 2832 2833 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2834 2835 grouping_sets = self.expressions(expression, key="grouping_sets") 2836 cube = self.expressions(expression, key="cube") 2837 rollup = self.expressions(expression, key="rollup") 2838 2839 groupings = csv( 2840 self.seg(grouping_sets) if grouping_sets else "", 2841 self.seg(cube) if cube else "", 2842 self.seg(rollup) if rollup else "", 2843 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2844 sep=self.GROUPINGS_SEP, 2845 ) 2846 2847 if ( 2848 expression.expressions 2849 and groupings 2850 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2851 ): 2852 add_separator = True 2853 2854 if grouping_sets: 2855 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2856 add_separator = False 2857 else: 2858 self.unsupported( 2859 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2860 ) 2861 2862 if add_separator: 2863 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2864 2865 return f"{group_by}{groupings}"
2871 def connect_sql(self, expression: exp.Connect) -> str: 2872 start = self.sql(expression, "start") 2873 start = self.seg(f"START WITH {start}") if start else "" 2874 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2875 connect = self.sql(expression, "connect") 2876 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2877 return start + connect
2882 def join_sql(self, expression: exp.Join) -> str: 2883 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2884 side = None 2885 else: 2886 side = expression.side 2887 2888 op_sql = " ".join( 2889 op 2890 for op in ( 2891 expression.method, 2892 "GLOBAL" if expression.args.get("global_") else None, 2893 side, 2894 expression.kind, 2895 expression.hint if self.JOIN_HINTS else None, 2896 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2897 ) 2898 if op 2899 ) 2900 match_cond = self.sql(expression, "match_condition") 2901 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2902 on_sql = self.sql(expression, "on") 2903 using = expression.args.get("using") 2904 2905 if not on_sql and using: 2906 on_sql = csv(*(self.sql(column) for column in using)) 2907 2908 this = expression.this 2909 this_sql = self.sql(this) 2910 2911 exprs = self.expressions(expression) 2912 if exprs: 2913 this_sql = f"{this_sql},{self.seg(exprs)}" 2914 2915 if on_sql: 2916 on_sql = self.indent(on_sql, skip_first=True) 2917 space = self.seg(" " * self.pad) if self.pretty else " " 2918 if using: 2919 on_sql = f"{space}USING ({on_sql})" 2920 else: 2921 on_sql = f"{space}ON {on_sql}" 2922 elif not op_sql: 2923 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2924 return f" {this_sql}" 2925 2926 return f", {this_sql}" 2927 2928 if op_sql != "STRAIGHT_JOIN": 2929 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2930 2931 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2932 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def
lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2939 def lateral_op(self, expression: exp.Lateral) -> str: 2940 cross_apply = expression.args.get("cross_apply") 2941 2942 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2943 if cross_apply is True: 2944 op = "INNER JOIN " 2945 elif cross_apply is False: 2946 op = "LEFT JOIN " 2947 else: 2948 op = "" 2949 2950 return f"{op}LATERAL"
2952 def lateral_sql(self, expression: exp.Lateral) -> str: 2953 this = self.sql(expression, "this") 2954 2955 if expression.args.get("view"): 2956 alias = expression.args["alias"] 2957 columns = self.expressions(alias, key="columns", flat=True) 2958 table = f" {alias.name}" if alias.name else "" 2959 columns = f" AS {columns}" if columns else "" 2960 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2961 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2962 2963 table_alias = expression.args.get("alias") 2964 offset = expression.this.args.get("offset") 2965 2966 if ( 2967 self.UNNEST_WITH_ORDINALITY 2968 and table_alias 2969 and isinstance(expression.this, exp.Unnest) 2970 and isinstance(offset, exp.Identifier) 2971 ): 2972 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 2973 table_alias = table_alias.copy() 2974 table_alias.append("columns", offset.copy()) 2975 2976 alias = self.sql(table_alias) 2977 alias = f" AS {alias}" if alias else "" 2978 2979 ordinality = expression.args.get("ordinality") or "" 2980 if ordinality: 2981 ordinality = f" WITH ORDINALITY{alias}" 2982 alias = "" 2983 2984 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2986 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2987 this = self.sql(expression, "this") 2988 2989 args = [ 2990 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2991 for e in (expression.args.get(k) for k in ("offset", "expression")) 2992 if e 2993 ] 2994 2995 args_sql = ", ".join(self.sql(e) for e in args) 2996 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2997 expressions = self.expressions(expression, flat=True) 2998 limit_options = self.sql(expression, "limit_options") 2999 expressions = f" BY {expressions}" if expressions else "" 3000 3001 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3003 def offset_sql(self, expression: exp.Offset) -> str: 3004 this = self.sql(expression, "this") 3005 value = expression.expression 3006 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3007 expressions = self.expressions(expression, flat=True) 3008 expressions = f" BY {expressions}" if expressions else "" 3009 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3011 def setitem_sql(self, expression: exp.SetItem) -> str: 3012 kind = self.sql(expression, "kind") 3013 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3014 kind = "" 3015 else: 3016 kind = f"{kind} " if kind else "" 3017 this = self.sql(expression, "this") 3018 expressions = self.expressions(expression) 3019 collate = self.sql(expression, "collate") 3020 collate = f" COLLATE {collate}" if collate else "" 3021 global_ = "GLOBAL " if expression.args.get("global_") else "" 3022 return f"{global_}{kind}{this}{expressions}{collate}"
3029 def queryband_sql(self, expression: exp.QueryBand) -> str: 3030 this = self.sql(expression, "this") 3031 update = " UPDATE" if expression.args.get("update") else "" 3032 scope = self.sql(expression, "scope") 3033 scope = f" FOR {scope}" if scope else "" 3034 3035 return f"QUERY_BAND = {this}{update}{scope}"
3040 def lock_sql(self, expression: exp.Lock) -> str: 3041 if not self.LOCKING_READS_SUPPORTED: 3042 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3043 return "" 3044 3045 update = expression.args["update"] 3046 key = expression.args.get("key") 3047 if update: 3048 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3049 else: 3050 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3051 expressions = self.expressions(expression, flat=True) 3052 expressions = f" OF {expressions}" if expressions else "" 3053 wait = expression.args.get("wait") 3054 3055 if wait is not None: 3056 if isinstance(wait, exp.Literal): 3057 wait = f" WAIT {self.sql(wait)}" 3058 else: 3059 wait = " NOWAIT" if wait else " SKIP LOCKED" 3060 3061 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str( self, text: str, escape_backslash: bool = True, delimiter: str | None = None, escaped_delimiter: str | None = None, is_byte_string: bool = False) -> str:
3069 def escape_str( 3070 self, 3071 text: str, 3072 escape_backslash: bool = True, 3073 delimiter: str | None = None, 3074 escaped_delimiter: str | None = None, 3075 is_byte_string: bool = False, 3076 ) -> str: 3077 if is_byte_string: 3078 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3079 else: 3080 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3081 3082 if supports_escape_sequences: 3083 text = "".join( 3084 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3085 for ch in text 3086 ) 3087 3088 delimiter = delimiter or self.dialect.QUOTE_END 3089 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3090 3091 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3093 def loaddata_sql(self, expression: exp.LoadData) -> str: 3094 is_overwrite = expression.args.get("overwrite") 3095 overwrite = " OVERWRITE" if is_overwrite else "" 3096 this = self.sql(expression, "this") 3097 3098 files = expression.args.get("files") 3099 if files: 3100 files_sql = self.expressions(files, flat=True) 3101 files_sql = f"FILES{self.wrap(files_sql)}" 3102 if is_overwrite: 3103 this = f" {this}" 3104 elif expression.args.get("temp"): 3105 this = f" INTO TEMP TABLE {this}" 3106 else: 3107 this = f" INTO TABLE {this}" 3108 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3109 3110 local = " LOCAL" if expression.args.get("local") else "" 3111 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3112 this = f" INTO TABLE {this}" 3113 partition = self.sql(expression, "partition") 3114 partition = f" {partition}" if partition else "" 3115 input_format = self.sql(expression, "input_format") 3116 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3117 serde = self.sql(expression, "serde") 3118 serde = f" SERDE {serde}" if serde else "" 3119 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3133 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3134 this = self.sql(expression, "this") 3135 this = f"{this} " if this else this 3136 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3137 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3139 def withfill_sql(self, expression: exp.WithFill) -> str: 3140 from_sql = self.sql(expression, "from_") 3141 from_sql = f" FROM {from_sql}" if from_sql else "" 3142 to_sql = self.sql(expression, "to") 3143 to_sql = f" TO {to_sql}" if to_sql else "" 3144 step_sql = self.sql(expression, "step") 3145 step_sql = f" STEP {step_sql}" if step_sql else "" 3146 interpolated_values = [ 3147 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3148 if isinstance(e, exp.Alias) 3149 else self.sql(e, "this") 3150 for e in expression.args.get("interpolate") or [] 3151 ] 3152 interpolate = ( 3153 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3154 ) 3155 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3207 def ordered_sql(self, expression: exp.Ordered) -> str: 3208 desc = expression.args.get("desc") 3209 asc = not desc 3210 3211 nulls_first = expression.args.get("nulls_first") 3212 nulls_last = not nulls_first 3213 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3214 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3215 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3216 3217 this = self.sql(expression, "this") 3218 3219 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3220 nulls_sort_change = "" 3221 if nulls_first and ( 3222 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3223 ): 3224 nulls_sort_change = " NULLS FIRST" 3225 elif ( 3226 nulls_last 3227 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3228 and not nulls_are_last 3229 ): 3230 nulls_sort_change = " NULLS LAST" 3231 3232 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3233 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3234 window = expression.find_ancestor(exp.Window, exp.Select) 3235 3236 if isinstance(window, exp.Window): 3237 window_this = window.this 3238 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3239 window_this = window_this.this 3240 spec = window.args.get("spec") 3241 else: 3242 window_this = None 3243 spec = None 3244 3245 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3246 # without a spec or with a ROWS spec, but not with RANGE 3247 if not ( 3248 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3249 and (not spec or spec.text("kind").upper() == "ROWS") 3250 ): 3251 if window_this and spec: 3252 self.unsupported( 3253 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3254 ) 3255 nulls_sort_change = "" 3256 elif self.NULL_ORDERING_SUPPORTED is False and ( 3257 (asc and nulls_sort_change == " NULLS LAST") 3258 or (desc and nulls_sort_change == " NULLS FIRST") 3259 ): 3260 # BigQuery does not allow these ordering/nulls combinations when used under 3261 # an aggregation func or under a window containing one 3262 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3263 3264 if isinstance(ancestor, exp.Window): 3265 ancestor = ancestor.this 3266 if isinstance(ancestor, exp.AggFunc): 3267 self.unsupported( 3268 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3269 ) 3270 nulls_sort_change = "" 3271 elif self.NULL_ORDERING_SUPPORTED is None: 3272 if expression.this.is_int: 3273 self.unsupported( 3274 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3275 ) 3276 elif not isinstance(expression.this, exp.Rand): 3277 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3278 target = self.sql(resolved) if resolved is not None else this 3279 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3280 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3281 nulls_sort_change = "" 3282 3283 with_fill = self.sql(expression, "with_fill") 3284 with_fill = f" {with_fill}" if with_fill else "" 3285 3286 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3296 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3297 partition = self.partition_by_sql(expression) 3298 order = self.sql(expression, "order") 3299 measures = self.expressions(expression, key="measures") 3300 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3301 rows = self.sql(expression, "rows") 3302 rows = self.seg(rows) if rows else "" 3303 after = self.sql(expression, "after") 3304 after = self.seg(after) if after else "" 3305 pattern = self.sql(expression, "pattern") 3306 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3307 definition_sqls = [ 3308 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3309 for definition in expression.args.get("define", []) 3310 ] 3311 definitions = self.expressions(sqls=definition_sqls) 3312 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3313 body = "".join( 3314 ( 3315 partition, 3316 order, 3317 measures, 3318 rows, 3319 after, 3320 pattern, 3321 define, 3322 ) 3323 ) 3324 alias = self.sql(expression, "alias") 3325 alias = f" {alias}" if alias else "" 3326 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3328 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3329 limit = expression.args.get("limit") 3330 3331 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3332 count = limit.args.get("count") 3333 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3334 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3335 limit = exp.Limit( 3336 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3337 ) 3338 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3339 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3340 3341 return csv( 3342 *sqls, 3343 *[self.sql(join) for join in expression.args.get("joins") or []], 3344 self.sql(expression, "match"), 3345 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3346 self.sql(expression, "prewhere"), 3347 self.sql(expression, "where"), 3348 self.sql(expression, "connect"), 3349 self.sql(expression, "group"), 3350 self.sql(expression, "having"), 3351 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3352 self.sql(expression, "order"), 3353 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3354 *self.after_limit_modifiers(expression), 3355 self.sql(expression, "for_"), 3356 self.options_modifier(expression), 3357 sep="", 3358 )
3364 def forclause_sql(self, expression: exp.ForClause) -> str: 3365 kind = expression.args["kind"] 3366 if kind == "BROWSE": 3367 return f"{self.sep()}FOR BROWSE" 3368 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3369 # the target dialect doesn't support QueryOption, so we drop the clause. 3370 options = self.expressions(expression, key="expressions") 3371 if not options: 3372 return "" 3373 return f"{self.sep()}FOR {kind}{self.seg(options)}"
def
offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
3392 def select_sql(self, expression: exp.Select) -> str: 3393 into = expression.args.get("into") 3394 if not self.SUPPORTS_SELECT_INTO and into: 3395 into.pop() 3396 3397 hint = self.sql(expression, "hint") 3398 distinct = self.sql(expression, "distinct") 3399 distinct = f" {distinct}" if distinct else "" 3400 kind = self.sql(expression, "kind") 3401 3402 limit = expression.args.get("limit") 3403 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3404 top = self.limit_sql(limit, top=True) 3405 limit.pop() 3406 else: 3407 top = "" 3408 3409 expressions = self.expressions(expression) 3410 3411 if kind: 3412 if kind in self.SELECT_KINDS: 3413 kind = f" AS {kind}" 3414 else: 3415 if kind == "STRUCT": 3416 expressions = self.expressions( 3417 sqls=[ 3418 self.sql( 3419 exp.Struct( 3420 expressions=[ 3421 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3422 if isinstance(e, exp.Alias) 3423 else e 3424 for e in expression.expressions 3425 ] 3426 ) 3427 ) 3428 ] 3429 ) 3430 kind = "" 3431 3432 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3433 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3434 3435 exclude = expression.args.get("exclude") 3436 3437 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3438 exclude_sql = self.expressions(sqls=exclude, flat=True) 3439 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3440 3441 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3442 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3443 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3444 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3445 sql = self.query_modifiers( 3446 expression, 3447 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3448 self.sql(expression, "into", comment=False), 3449 self.sql(expression, "from_", comment=False), 3450 ) 3451 3452 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3453 if expression.args.get("with_"): 3454 sql = self.maybe_comment(sql, expression) 3455 expression.pop_comments() 3456 3457 sql = self.prepend_ctes(expression, sql) 3458 3459 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3460 expression.set("exclude", None) 3461 subquery = expression.subquery(copy=False) 3462 star = exp.Star(except_=exclude) 3463 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3464 3465 if not self.SUPPORTS_SELECT_INTO and into: 3466 if into.args.get("temporary"): 3467 table_kind = " TEMPORARY" 3468 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3469 table_kind = " UNLOGGED" 3470 else: 3471 table_kind = "" 3472 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3473 3474 return sql
3486 def star_sql(self, expression: exp.Star) -> str: 3487 except_ = self.expressions(expression, key="except_", flat=True) 3488 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3489 replace = self.expressions(expression, key="replace", flat=True) 3490 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3491 rename = self.expressions(expression, key="rename", flat=True) 3492 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3493 ilike = self.sql(expression, "ilike") 3494 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3495 return f"*{ilike}{except_}{replace}{rename}"
3511 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3512 alias = self.sql(expression, "alias") 3513 alias = f"{sep}{alias}" if alias else "" 3514 sample = self.sql(expression, "sample") 3515 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3516 alias = f"{sample}{alias}" 3517 3518 # Set to None so it's not generated again by self.query_modifiers() 3519 expression.set("sample", None) 3520 3521 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3522 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3523 return self.prepend_ctes(expression, sql)
3529 def unnest_sql(self, expression: exp.Unnest) -> str: 3530 args = self.expressions(expression, flat=True) 3531 3532 alias = expression.args.get("alias") 3533 offset = expression.args.get("offset") 3534 3535 if self.UNNEST_WITH_ORDINALITY: 3536 if alias and isinstance(offset, exp.Expr): 3537 alias.append("columns", offset) 3538 expression.set("offset", None) 3539 3540 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3541 columns = alias.columns 3542 alias = self.sql(columns[0]) if columns else "" 3543 else: 3544 alias = self.sql(alias) 3545 3546 alias = f" AS {alias}" if alias else alias 3547 if self.UNNEST_WITH_ORDINALITY: 3548 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3549 else: 3550 if isinstance(offset, exp.Expr): 3551 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3552 elif offset: 3553 suffix = f"{alias} WITH OFFSET" 3554 else: 3555 suffix = alias 3556 3557 return f"UNNEST({args}){suffix}"
3566 def window_sql(self, expression: exp.Window) -> str: 3567 this = self.sql(expression, "this") 3568 partition = self.partition_by_sql(expression) 3569 order = expression.args.get("order") 3570 order = self.order_sql(order, flat=True) if order else "" 3571 spec = self.sql(expression, "spec") 3572 alias = self.sql(expression, "alias") 3573 over = self.sql(expression, "over") or "OVER" 3574 3575 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3576 3577 first = expression.args.get("first") 3578 if first is None: 3579 first = "" 3580 else: 3581 first = "FIRST" if first else "LAST" 3582 3583 if not partition and not order and not spec and alias: 3584 return f"{this} {alias}" 3585 3586 args = self.format_args( 3587 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3588 ) 3589 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3595 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3596 kind = self.sql(expression, "kind") 3597 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3598 end = ( 3599 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3600 or "CURRENT ROW" 3601 ) 3602 3603 window_spec = f"{kind} BETWEEN {start} AND {end}" 3604 3605 exclude = self.sql(expression, "exclude") 3606 if exclude: 3607 if self.SUPPORTS_WINDOW_EXCLUDE: 3608 window_spec += f" EXCLUDE {exclude}" 3609 else: 3610 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3611 3612 return window_spec
3619 def between_sql(self, expression: exp.Between) -> str: 3620 this = self.sql(expression, "this") 3621 low = self.sql(expression, "low") 3622 high = self.sql(expression, "high") 3623 symmetric = expression.args.get("symmetric") 3624 3625 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3626 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3627 3628 flag = ( 3629 " SYMMETRIC" 3630 if symmetric 3631 else " ASYMMETRIC" 3632 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3633 else "" # silently drop ASYMMETRIC – semantics identical 3634 ) 3635 return f"{this} BETWEEN{flag} {low} AND {high}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.core.Bracket, index_offset: int | None = None) -> list[sqlglot.expressions.core.Expr]:
3637 def bracket_offset_expressions( 3638 self, expression: exp.Bracket, index_offset: int | None = None 3639 ) -> list[exp.Expr]: 3640 if expression.args.get("json_access"): 3641 return expression.expressions 3642 3643 return apply_index_offset( 3644 expression.this, 3645 expression.expressions, 3646 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3647 dialect=self.dialect, 3648 )
3661 def any_sql(self, expression: exp.Any) -> str: 3662 this = self.sql(expression, "this") 3663 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3664 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3665 this = self.wrap(this) 3666 return f"ANY{this}" 3667 return f"ANY {this}"
3672 def case_sql(self, expression: exp.Case) -> str: 3673 this = self.sql(expression, "this") 3674 statements = [f"CASE {this}" if this else "CASE"] 3675 3676 for e in expression.args["ifs"]: 3677 statements.append(f"WHEN {self.sql(e, 'this')}") 3678 statements.append(f"THEN {self.sql(e, 'true')}") 3679 3680 default = self.sql(expression, "default") 3681 3682 if default: 3683 statements.append(f"ELSE {default}") 3684 3685 statements.append("END") 3686 3687 if self.pretty and self.too_wide(statements): 3688 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3689 3690 return " ".join(statements)
3702 def extract_sql(self, expression: exp.Extract) -> str: 3703 import sqlglot.dialects.dialect 3704 3705 this = ( 3706 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3707 if self.NORMALIZE_EXTRACT_DATE_PARTS 3708 else expression.this 3709 ) 3710 if self.EXTRACT_ALLOWS_QUOTES: 3711 this_sql = self.sql(this) 3712 elif isinstance(this, exp.WeekStart): 3713 this_sql = self.weekstart_name(this) 3714 else: 3715 this_sql = this.name 3716 expression_sql = self.sql(expression, "expression") 3717 3718 return f"EXTRACT({this_sql} FROM {expression_sql})"
3720 def trim_sql(self, expression: exp.Trim) -> str: 3721 trim_type = self.sql(expression, "position") 3722 3723 if trim_type == "LEADING": 3724 func_name = "LTRIM" 3725 elif trim_type == "TRAILING": 3726 func_name = "RTRIM" 3727 else: 3728 func_name = "TRIM" 3729 3730 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3732 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3733 args = expression.expressions 3734 if isinstance(expression, exp.ConcatWs): 3735 args = args[1:] # Skip the delimiter 3736 3737 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3738 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3739 3740 concat_coalesce = ( 3741 self.dialect.CONCAT_WS_COALESCE 3742 if isinstance(expression, exp.ConcatWs) 3743 else self.dialect.CONCAT_COALESCE 3744 ) 3745 3746 if not concat_coalesce and expression.args.get("coalesce"): 3747 3748 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3749 if not e.type: 3750 import sqlglot.optimizer.annotate_types 3751 3752 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3753 3754 if e.is_string or e.is_type(exp.DType.ARRAY): 3755 return e 3756 3757 return exp.func("coalesce", e, exp.Literal.string("")) 3758 3759 args = [_wrap_with_coalesce(e) for e in args] 3760 3761 return args
3763 def concat_sql(self, expression: exp.Concat) -> str: 3764 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3765 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3766 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3767 # instead of coalescing them to empty string. 3768 import sqlglot.dialects.dialect 3769 3770 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3771 3772 expressions = self.convert_concat_args(expression) 3773 3774 # Some dialects don't allow a single-argument CONCAT call 3775 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3776 return self.sql(expressions[0]) 3777 3778 return self.func("CONCAT", *expressions)
3780 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3781 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3782 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3783 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3784 all_args = expression.expressions 3785 expression.set("coalesce", True) 3786 return self.sql( 3787 exp.case() 3788 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3789 .else_(expression) 3790 ) 3791 3792 return self.func( 3793 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3794 )
3800 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3801 expressions = self.expressions(expression, flat=True) 3802 expressions = f" ({expressions})" if expressions else "" 3803 reference = self.sql(expression, "reference") 3804 reference = f" {reference}" if reference else "" 3805 delete = self.sql(expression, "delete") 3806 delete = f" ON DELETE {delete}" if delete else "" 3807 update = self.sql(expression, "update") 3808 update = f" ON UPDATE {update}" if update else "" 3809 options = self.expressions(expression, key="options", flat=True, sep=" ") 3810 options = f" {options}" if options else "" 3811 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3813 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3814 this = self.sql(expression, "this") 3815 this = f" {this}" if this else "" 3816 expressions = self.expressions(expression, flat=True) 3817 include = self.sql(expression, "include") 3818 options = self.expressions(expression, key="options", flat=True, sep=" ") 3819 options = f" {options}" if options else "" 3820 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3829 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3830 if self.MATCH_AGAINST_TABLE_PREFIX: 3831 expressions = [] 3832 for expr in expression.expressions: 3833 if isinstance(expr, exp.Table): 3834 expressions.append(f"TABLE {self.sql(expr)}") 3835 else: 3836 expressions.append(expr) 3837 else: 3838 expressions = expression.expressions 3839 3840 modifier = expression.args.get("modifier") 3841 modifier = f" {modifier}" if modifier else "" 3842 return ( 3843 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3844 )
3858 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3859 if isinstance(expression, exp.JSONPathPart): 3860 transform = self.TRANSFORMS.get(expression.__class__) 3861 if not callable(transform): 3862 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3863 return "" 3864 3865 return transform(self, expression) 3866 3867 if isinstance(expression, int): 3868 return str(expression) 3869 3870 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3871 escaped = expression.replace("'", "\\'") 3872 escaped = f"'{escaped}'" 3873 else: 3874 escaped = expression.replace('"', '\\"') 3875 escaped = f'"{escaped}"' 3876 3877 return escaped
3882 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3883 # Output the Teradata column FORMAT override. 3884 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3885 this = self.sql(expression, "this") 3886 fmt = self.sql(expression, "format") 3887 return f"{this} (FORMAT {fmt})"
3915 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3916 null_handling = expression.args.get("null_handling") 3917 null_handling = f" {null_handling}" if null_handling else "" 3918 return_type = self.sql(expression, "return_type") 3919 return_type = f" RETURNING {return_type}" if return_type else "" 3920 strict = " STRICT" if expression.args.get("strict") else "" 3921 return self.func( 3922 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3923 )
3925 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3926 this = self.sql(expression, "this") 3927 order = self.sql(expression, "order") 3928 null_handling = expression.args.get("null_handling") 3929 null_handling = f" {null_handling}" if null_handling else "" 3930 return_type = self.sql(expression, "return_type") 3931 return_type = f" RETURNING {return_type}" if return_type else "" 3932 strict = " STRICT" if expression.args.get("strict") else "" 3933 return self.func( 3934 "JSON_ARRAYAGG", 3935 this, 3936 suffix=f"{order}{null_handling}{return_type}{strict})", 3937 )
3939 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3940 path = self.sql(expression, "path") 3941 path = f" PATH {path}" if path else "" 3942 nested_schema = self.sql(expression, "nested_schema") 3943 3944 if nested_schema: 3945 return f"NESTED{path} {nested_schema}" 3946 3947 this = self.sql(expression, "this") 3948 kind = self.sql(expression, "kind") 3949 kind = f" {kind}" if kind else "" 3950 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3951 3952 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3953 return f"{this}{kind}{format_json}{path}{ordinality}"
3958 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3959 this = self.sql(expression, "this") 3960 path = self.sql(expression, "path") 3961 path = f", {path}" if path else "" 3962 error_handling = expression.args.get("error_handling") 3963 error_handling = f" {error_handling}" if error_handling else "" 3964 empty_handling = expression.args.get("empty_handling") 3965 empty_handling = f" {empty_handling}" if empty_handling else "" 3966 schema = self.sql(expression, "schema") 3967 return self.func( 3968 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3969 )
3971 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3972 this = self.sql(expression, "this") 3973 kind = self.sql(expression, "kind") 3974 path = self.sql(expression, "path") 3975 path = f" {path}" if path else "" 3976 as_json = " AS JSON" if expression.args.get("as_json") else "" 3977 return f"{this} {kind}{path}{as_json}"
3979 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3980 this = self.sql(expression, "this") 3981 path = self.sql(expression, "path") 3982 path = f", {path}" if path else "" 3983 expressions = self.expressions(expression) 3984 with_ = ( 3985 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3986 if expressions 3987 else "" 3988 ) 3989 return f"OPENJSON({this}{path}){with_}"
3991 def in_sql(self, expression: exp.In) -> str: 3992 query = expression.args.get("query") 3993 unnest = expression.args.get("unnest") 3994 field = expression.args.get("field") 3995 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3996 3997 if query: 3998 in_sql = self.sql(query) 3999 elif unnest: 4000 in_sql = self.in_unnest_op(unnest) 4001 elif field: 4002 in_sql = self.sql(field) 4003 else: 4004 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4005 4006 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4011 def interval_sql(self, expression: exp.Interval) -> str: 4012 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4013 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4014 exp.AutoRefreshProperty, 4015 ) 4016 interval_keyword = "INTERVAL" if include_keyword else "" 4017 unit_expression = expression.args.get("unit") 4018 unit = self.sql(unit_expression) if unit_expression else "" 4019 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4020 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4021 unit = f" {unit}" if unit else "" 4022 4023 if self.SINGLE_STRING_INTERVAL: 4024 this = expression.this.name if expression.this else "" 4025 if this: 4026 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4027 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4028 return f"{interval_keyword}'{this}'{unit}" 4029 return f"{interval_keyword}'{this}{unit}'" 4030 return f"{interval_keyword}{unit}" 4031 4032 this = self.sql(expression, "this") 4033 if this: 4034 if not include_keyword and expression.this.is_string: 4035 this = expression.this.name 4036 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4037 this = f"({this})" 4038 if include_keyword: 4039 this = f" {this}" 4040 4041 return f"{interval_keyword}{this}{unit}"
4046 def reference_sql(self, expression: exp.Reference) -> str: 4047 this = self.sql(expression, "this") 4048 expressions = self.expressions(expression, flat=True) 4049 expressions = f"({expressions})" if expressions else "" 4050 options = self.expressions(expression, key="options", flat=True, sep=" ") 4051 options = f" {options}" if options else "" 4052 return f"REFERENCES {this}{expressions}{options}"
4054 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4055 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4056 parent = expression.parent 4057 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4058 4059 return self.func( 4060 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4061 )
4081 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4082 alias = expression.args["alias"] 4083 4084 parent = expression.parent 4085 pivot = parent and parent.parent 4086 4087 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4088 identifier_alias = isinstance(alias, exp.Identifier) 4089 literal_alias = isinstance(alias, exp.Literal) 4090 4091 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4092 alias.replace(exp.Literal.string(alias.output_name)) 4093 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4094 alias.replace(exp.to_identifier(alias.output_name)) 4095 4096 return self.alias_sql(expression)
def
fromiso8601timestamp_sql( self, expression: sqlglot.expressions.temporal.FromISO8601Timestamp) -> str:
def
fromiso8601timestampnanos_sql( self, expression: sqlglot.expressions.temporal.FromISO8601TimestampNanos) -> str:
def
and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.core.Or, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.core.Xor, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.core.Connector, op: str, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
4137 def connector_sql( 4138 self, 4139 expression: exp.Connector, 4140 op: str, 4141 stack: list[str | exp.Expr] | None = None, 4142 ) -> str: 4143 if stack is not None: 4144 stack.append(expression.right) 4145 if expression.comments and self.comments: 4146 op = self.maybe_comment(op, comments=expression.comments) 4147 4148 stack.extend((op, expression.left)) 4149 return op 4150 4151 stack = [expression] 4152 sqls: list[str] = [] 4153 ops = set() 4154 4155 while stack: 4156 node = stack.pop() 4157 if isinstance(node, exp.Connector): 4158 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4159 else: 4160 sql = self.sql(node) 4161 if sqls and sqls[-1] in ops: 4162 sqls[-1] += f" {sql}" 4163 else: 4164 sqls.append(sql) 4165 4166 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4167 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4187 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4188 format_sql = self.sql(expression, "format") 4189 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4190 to_sql = self.sql(expression, "to") 4191 to_sql = f" {to_sql}" if to_sql else "" 4192 action = self.sql(expression, "action") 4193 action = f" {action}" if action else "" 4194 default = self.sql(expression, "default") 4195 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4196 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4226 def comment_sql(self, expression: exp.Comment) -> str: 4227 this = self.sql(expression, "this") 4228 kind = expression.args["kind"] 4229 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4230 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4231 expression_sql = self.sql(expression, "expression") 4232 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4234 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4235 this = self.sql(expression, "this") 4236 delete = " DELETE" if expression.args.get("delete") else "" 4237 recompress = self.sql(expression, "recompress") 4238 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4239 to_disk = self.sql(expression, "to_disk") 4240 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4241 to_volume = self.sql(expression, "to_volume") 4242 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4243 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4245 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4246 where = self.sql(expression, "where") 4247 group = self.sql(expression, "group") 4248 aggregates = self.expressions(expression, key="aggregates") 4249 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4250 4251 if not (where or group or aggregates) and len(expression.expressions) == 1: 4252 return f"TTL {self.expressions(expression, flat=True)}" 4253 4254 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4273 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4274 this = self.sql(expression, "this") 4275 4276 exists = "" 4277 if expression.args.get("exists"): 4278 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4279 exists = " IF EXISTS" 4280 else: 4281 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4282 4283 dtype = self.sql(expression, "dtype") 4284 if dtype: 4285 collate = self.sql(expression, "collate") 4286 collate = f" COLLATE {collate}" if collate else "" 4287 using = self.sql(expression, "using") 4288 using = f" USING {using}" if using else "" 4289 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4290 null_constraint = self._alter_column_null_constraint_sql(expression) 4291 4292 return ( 4293 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4294 f"{collate}{using}{null_constraint}" 4295 ) 4296 4297 default = self.sql(expression, "default") 4298 if default: 4299 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4300 4301 comment = self.sql(expression, "comment") 4302 if comment: 4303 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4304 4305 visible = expression.args.get("visible") 4306 if visible: 4307 return f"ALTER COLUMN{exists} {this} SET {visible}" 4308 4309 allow_null = expression.args.get("allow_null") 4310 drop = expression.args.get("drop") 4311 4312 if not drop and not allow_null: 4313 self.unsupported("Unsupported ALTER COLUMN syntax") 4314 4315 if allow_null is not None: 4316 keyword = "DROP" if drop else "SET" 4317 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4318 4319 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4332 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4333 this = self.sql(expression, "this") 4334 rename_from = self.sql(expression, "rename_from") 4335 if rename_from: 4336 if not self.SUPPORTS_CHANGE_COLUMN: 4337 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4338 return f"CHANGE COLUMN {rename_from} {this}" 4339 if not self.SUPPORTS_MODIFY_COLUMN: 4340 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4341 return f"MODIFY COLUMN {this}"
4357 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4358 compound = " COMPOUND" if expression.args.get("compound") else "" 4359 this = self.sql(expression, "this") 4360 expressions = self.expressions(expression, flat=True) 4361 expressions = f"({expressions})" if expressions else "" 4362 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4364 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4365 if not self.RENAME_TABLE_WITH_DB: 4366 # Remove db from tables 4367 expression = expression.transform( 4368 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4369 ).assert_is(exp.AlterRename) 4370 this = self.sql(expression, "this") 4371 to_kw = " TO" if include_to else "" 4372 return f"RENAME{to_kw} {this}"
4387 def alter_sql(self, expression: exp.Alter) -> str: 4388 actions = expression.args["actions"] 4389 4390 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4391 actions[0], exp.ColumnDef 4392 ): 4393 actions_sql = self.expressions(expression, key="actions", flat=True) 4394 actions_sql = f"ADD {actions_sql}" 4395 else: 4396 actions_list = [] 4397 for action in actions: 4398 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4399 action_sql = self.add_column_sql(action) 4400 else: 4401 action_sql = self.sql(action) 4402 if isinstance(action, exp.Query): 4403 action_sql = f"AS {action_sql}" 4404 4405 actions_list.append(action_sql) 4406 4407 actions_sql = self.format_args(*actions_list).lstrip("\n") 4408 4409 iceberg = ( 4410 "ICEBERG " 4411 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4412 else "" 4413 ) 4414 exists = " IF EXISTS" if expression.args.get("exists") else "" 4415 on_cluster = self.sql(expression, "cluster") 4416 on_cluster = f" {on_cluster}" if on_cluster else "" 4417 only = " ONLY" if expression.args.get("only") else "" 4418 options = self.expressions(expression, key="options") 4419 options = f", {options}" if options else "" 4420 kind = self.sql(expression, "kind") 4421 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4422 check = " WITH CHECK" if expression.args.get("check") else "" 4423 cascade = ( 4424 " CASCADE" 4425 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4426 else "" 4427 ) 4428 this = self.sql(expression, "this") 4429 this = f" {this}" if this else "" 4430 4431 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4438 def add_column_sql(self, expression: exp.Expr) -> str: 4439 sql = self.sql(expression) 4440 if isinstance(expression, exp.Schema): 4441 column_text = " COLUMNS" 4442 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4443 column_text = " COLUMN" 4444 else: 4445 column_text = "" 4446 4447 return f"ADD{column_text} {sql}"
4460 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4461 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4462 location = self.sql(expression, "location") 4463 location = f" {location}" if location else "" 4464 return f"ADD {exists}{self.sql(expression.this)}{location}"
4466 def distinct_sql(self, expression: exp.Distinct) -> str: 4467 this = self.expressions(expression, flat=True) 4468 4469 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4470 case = exp.case() 4471 for arg in expression.expressions: 4472 case = case.when(arg.is_(exp.null()), exp.null()) 4473 this = self.sql(case.else_(f"({this})")) 4474 4475 this = f" {this}" if this else "" 4476 4477 on = self.sql(expression, "on") 4478 on = f" ON {on}" if on else "" 4479 return f"DISTINCT{this}{on}"
4506 def div_sql(self, expression: exp.Div) -> str: 4507 l, r = expression.left, expression.right 4508 4509 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4510 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4511 4512 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4513 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4514 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4515 4516 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4517 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4518 return self.sql( 4519 exp.cast( 4520 l / r, 4521 to=exp.DType.BIGINT, 4522 ) 4523 ) 4524 4525 return self.binary(expression, "/")
4550 def escape_sql(self, expression: exp.Escape) -> str: 4551 this = expression.this 4552 if ( 4553 isinstance(this, (exp.Like, exp.ILike)) 4554 and isinstance(this.expression, (exp.All, exp.Any)) 4555 and not self.SUPPORTS_LIKE_QUANTIFIERS 4556 ): 4557 return self._like_sql(this, escape=expression) 4558 return self.binary(expression, "ESCAPE")
4569 def is_sql(self, expression: exp.Is) -> str: 4570 negate = expression.args.get("negate") 4571 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4572 positive = bool(expression.expression.this) != bool(negate) 4573 return self.sql(expression.this if positive else exp.not_(expression.this)) 4574 return self.binary(expression, "IS NOT" if negate else "IS")
4644 def mod_sql(self, expression: exp.Mod) -> str: 4645 this = self.sql(expression, "this") 4646 expr = self.sql(expression, "expression") 4647 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4648 4649 parent = expression.parent 4650 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4651 return f"({sql})" 4652 4653 return sql
4683 def log_sql(self, expression: exp.Log) -> str: 4684 this = expression.this 4685 expr = expression.expression 4686 4687 if self.dialect.LOG_BASE_FIRST is False: 4688 this, expr = expr, this 4689 elif self.dialect.LOG_BASE_FIRST is None and expr: 4690 if this.name in ("2", "10"): 4691 return self.func(f"LOG{this.name}", expr) 4692 4693 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4694 4695 return self.func("LOG", this, expr)
4704 def binary(self, expression: exp.Binary, op: str) -> str: 4705 sqls: list[str] = [] 4706 stack: list[None | str | exp.Expr] = [expression] 4707 binary_type = type(expression) 4708 4709 while stack: 4710 node = stack.pop() 4711 4712 if type(node) is binary_type: 4713 op_func = node.args.get("operator") 4714 if op_func: 4715 op = f"OPERATOR({self.sql(op_func)})" 4716 4717 stack.append(node.args.get("expression")) 4718 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4719 stack.append(node.args.get("this")) 4720 else: 4721 sqls.append(self.sql(node)) 4722 4723 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4732 def function_fallback_sql(self, expression: exp.Func) -> str: 4733 args = [] 4734 4735 for key in expression.arg_types: 4736 arg_value = expression.args.get(key) 4737 4738 if isinstance(arg_value, list): 4739 for value in arg_value: 4740 args.append(value) 4741 elif arg_value is not None: 4742 args.append(arg_value) 4743 4744 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4745 name = expression.meta_get("name") or expression.sql_name() 4746 else: 4747 name = expression.sql_name() 4748 4749 return self.func(name, *args)
def
func( self, name: str, *args: Any, prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
def
format_args(self, *args: Any, sep: str = ', ') -> str:
4762 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4763 arg_sqls = tuple( 4764 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4765 ) 4766 if self.pretty and self.too_wide(arg_sqls): 4767 return self.indent( 4768 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4769 ) 4770 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.core.Expr, inverse_time_mapping: dict[str, str] | None = None, inverse_time_trie: dict | None = None) -> str | None:
4775 def format_time( 4776 self, 4777 expression: exp.Expr, 4778 inverse_time_mapping: dict[str, str] | None = None, 4779 inverse_time_trie: dict | None = None, 4780 ) -> str | None: 4781 return format_time( 4782 self.sql(expression, "format"), 4783 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4784 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4785 )
def
expressions( self, expression: sqlglot.expressions.core.Expr | None = None, key: str | None = None, sqls: Optional[Collection[str | sqlglot.expressions.core.Expr]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
4787 def expressions( 4788 self, 4789 expression: exp.Expr | None = None, 4790 key: str | None = None, 4791 sqls: t.Collection[str | exp.Expr] | None = None, 4792 flat: bool = False, 4793 indent: bool = True, 4794 skip_first: bool = False, 4795 skip_last: bool = False, 4796 sep: str = ", ", 4797 prefix: str = "", 4798 dynamic: bool = False, 4799 new_line: bool = False, 4800 ) -> str: 4801 expressions = expression.args.get(key or "expressions") if expression else sqls 4802 4803 if not expressions: 4804 return "" 4805 4806 if flat: 4807 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4808 4809 num_sqls = len(expressions) 4810 result_sqls = [] 4811 4812 for i, e in enumerate(expressions): 4813 sql = self.sql(e, comment=False) 4814 if not sql: 4815 continue 4816 4817 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4818 4819 if self.pretty: 4820 if self.leading_comma: 4821 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4822 else: 4823 result_sqls.append( 4824 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4825 ) 4826 else: 4827 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4828 4829 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4830 if new_line: 4831 result_sqls.insert(0, "") 4832 result_sqls.append("") 4833 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4834 else: 4835 result_sql = "".join(result_sqls) 4836 4837 return ( 4838 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4839 if indent 4840 else result_sql 4841 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4843 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4844 flat = flat or isinstance(expression.parent, exp.Properties) 4845 expressions_sql = self.expressions(expression, flat=flat) 4846 if flat: 4847 return f"{op} {expressions_sql}" 4848 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4850 def naked_property(self, expression: exp.Property) -> str: 4851 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4852 if not property_name: 4853 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4854 return f"{property_name} {self.sql(expression, 'this')}"
4862 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4863 this = self.sql(expression, "this") 4864 expressions = self.no_identify(self.expressions, expression) 4865 expressions = ( 4866 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4867 ) 4868 return f"{this}{expressions}" if expressions.strip() != "" else this
4887 def when_sql(self, expression: exp.When) -> str: 4888 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4889 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4890 condition = self.sql(expression, "condition") 4891 condition = f" AND {condition}" if condition else "" 4892 4893 then_expression = expression.args.get("then") 4894 if isinstance(then_expression, exp.Insert): 4895 this = self.sql(then_expression, "this") 4896 this = f"INSERT {this}" if this else "INSERT" 4897 then = self.sql(then_expression, "expression") 4898 then = f"{this} VALUES {then}" if then else this 4899 elif isinstance(then_expression, exp.Update): 4900 if isinstance(then_expression.args.get("expressions"), exp.Star): 4901 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4902 else: 4903 expressions_sql = self.expressions(then_expression) 4904 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4905 else: 4906 then = self.sql(then_expression) 4907 4908 if isinstance(then_expression, (exp.Insert, exp.Update)): 4909 where = self.sql(then_expression, "where") 4910 if where and not self.SUPPORTS_MERGE_WHERE: 4911 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4912 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4913 where = "" 4914 then = f"{then}{where}" 4915 return f"WHEN {matched}{source}{condition} THEN {then}"
4920 def merge_sql(self, expression: exp.Merge) -> str: 4921 table = expression.this 4922 table_alias = "" 4923 4924 hints = table.args.get("hints") 4925 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4926 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4927 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4928 4929 this = self.sql(table) 4930 using = f"USING {self.sql(expression, 'using')}" 4931 whens = self.sql(expression, "whens") 4932 4933 on = self.sql(expression, "on") 4934 on = f"ON {on}" if on else "" 4935 4936 if not on: 4937 on = self.expressions(expression, key="using_cond") 4938 on = f"USING ({on})" if on else "" 4939 4940 returning = self.sql(expression, "returning") 4941 if returning: 4942 whens = f"{whens}{returning}" 4943 4944 sep = self.sep() 4945 4946 return self.prepend_ctes( 4947 expression, 4948 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4949 )
@unsupported_args('format')
def
tochar_sql(self, expression: sqlglot.expressions.string.ToChar) -> str:
@unsupported_args('default')
def
tonumber_sql(self, expression: sqlglot.expressions.string.ToNumber) -> str:
4955 @unsupported_args("default") 4956 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4957 if not self.SUPPORTS_TO_NUMBER: 4958 self.unsupported("Unsupported TO_NUMBER function") 4959 return self._tonumber_cast_sql(expression) 4960 4961 fmt = expression.args.get("format") 4962 if not fmt: 4963 self.unsupported("Conversion format is required for TO_NUMBER") 4964 return self._tonumber_cast_sql(expression) 4965 4966 return self.func("TO_NUMBER", expression.this, fmt)
4973 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4974 this = self.sql(expression, "this") 4975 kind = self.sql(expression, "kind") 4976 settings_sql = self.expressions(expression, key="settings", sep=" ") 4977 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4978 return f"{this}({kind}{args})"
def
duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
def
distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
4999 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5000 expressions = self.expressions(expression, flat=True) 5001 expressions = f" {self.wrap(expressions)}" if expressions else "" 5002 buckets = self.sql(expression, "buckets") 5003 kind = self.sql(expression, "kind") 5004 buckets = f" BUCKETS {buckets}" if buckets else "" 5005 order = self.sql(expression, "order") 5006 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
5011 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5012 expressions = self.expressions(expression, key="expressions", flat=True) 5013 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5014 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5015 buckets = self.sql(expression, "buckets") 5016 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5018 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5019 this = self.sql(expression, "this") 5020 having = self.sql(expression, "having") 5021 5022 if having: 5023 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5024 5025 return self.func("ANY_VALUE", this)
5027 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5028 transform = self.func("TRANSFORM", *expression.expressions) 5029 row_format_before = self.sql(expression, "row_format_before") 5030 row_format_before = f" {row_format_before}" if row_format_before else "" 5031 record_writer = self.sql(expression, "record_writer") 5032 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5033 using = f" USING {self.sql(expression, 'command_script')}" 5034 schema = self.sql(expression, "schema") 5035 schema = f" AS {schema}" if schema else "" 5036 row_format_after = self.sql(expression, "row_format_after") 5037 row_format_after = f" {row_format_after}" if row_format_after else "" 5038 record_reader = self.sql(expression, "record_reader") 5039 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5040 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
def
indexconstraintoption_sql( self, expression: sqlglot.expressions.constraints.IndexConstraintOption) -> str:
5042 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5043 key_block_size = self.sql(expression, "key_block_size") 5044 if key_block_size: 5045 return f"KEY_BLOCK_SIZE = {key_block_size}" 5046 5047 using = self.sql(expression, "using") 5048 if using: 5049 return f"USING {using}" 5050 5051 parser = self.sql(expression, "parser") 5052 if parser: 5053 return f"WITH PARSER {parser}" 5054 5055 comment = self.sql(expression, "comment") 5056 if comment: 5057 return f"COMMENT {comment}" 5058 5059 visible = expression.args.get("visible") 5060 if visible is not None: 5061 return "VISIBLE" if visible else "INVISIBLE" 5062 5063 engine_attr = self.sql(expression, "engine_attr") 5064 if engine_attr: 5065 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5066 5067 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5068 if secondary_engine_attr: 5069 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5070 5071 self.unsupported("Unsupported index constraint option.") 5072 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5078 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5079 kind = self.sql(expression, "kind") 5080 kind = f"{kind} INDEX" if kind else "INDEX" 5081 this = self.sql(expression, "this") 5082 this = f" {this}" if this else "" 5083 index_type = self.sql(expression, "index_type") 5084 index_type = f" USING {index_type}" if index_type else "" 5085 expressions = self.expressions(expression, flat=True) 5086 expressions = f" ({expressions})" if expressions else "" 5087 options = self.expressions(expression, key="options", sep=" ") 5088 options = f" {options}" if options else "" 5089 return f"{kind}{this}{index_type}{expressions}{options}"
5091 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5092 if self.NVL2_SUPPORTED: 5093 return self.function_fallback_sql(expression) 5094 5095 case = exp.Case().when( 5096 expression.this.is_(exp.null()).not_(copy=False), 5097 expression.args["true"], 5098 copy=False, 5099 ) 5100 else_cond = expression.args.get("false") 5101 if else_cond: 5102 case.else_(else_cond, copy=False) 5103 5104 return self.sql(case)
5112 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5113 this = self.sql(expression, "this") 5114 expr = self.sql(expression, "expression") 5115 position = self.sql(expression, "position") 5116 position = f", {position}" if position else "" 5117 iterator = self.sql(expression, "iterator") 5118 condition = self.sql(expression, "condition") 5119 condition = f" IF {condition}" if condition else "" 5120 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5170 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5171 this_sql = self.sql(expression, "this") 5172 if isinstance(expression.this, exp.Table): 5173 this_sql = f"TABLE {this_sql}" 5174 5175 return self.func( 5176 "FORECAST", 5177 this_sql, 5178 expression.args.get("data_col"), 5179 expression.args.get("timestamp_col"), 5180 expression.args.get("model"), 5181 expression.args.get("id_cols"), 5182 expression.args.get("horizon"), 5183 expression.args.get("forecast_end_timestamp"), 5184 expression.args.get("confidence_level"), 5185 expression.args.get("output_historical_time_series"), 5186 expression.args.get("context_window"), 5187 )
5189 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5190 this_sql = self.sql(expression, "this") 5191 if isinstance(expression.this, exp.Table): 5192 this_sql = f"TABLE {this_sql}" 5193 5194 return self.func( 5195 "FEATURES_AT_TIME", 5196 this_sql, 5197 expression.args.get("time"), 5198 expression.args.get("num_rows"), 5199 expression.args.get("ignore_feature_nulls"), 5200 )
5202 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5203 this_sql = self.sql(expression, "this") 5204 if isinstance(expression.this, exp.Table): 5205 this_sql = f"TABLE {this_sql}" 5206 5207 query_table = self.sql(expression, "query_table") 5208 if isinstance(expression.args["query_table"], exp.Table): 5209 query_table = f"TABLE {query_table}" 5210 5211 return self.func( 5212 "VECTOR_SEARCH", 5213 this_sql, 5214 expression.args.get("column_to_search"), 5215 query_table, 5216 expression.args.get("query_column_to_search"), 5217 expression.args.get("top_k"), 5218 expression.args.get("distance_type"), 5219 expression.args.get("options"), 5220 )
5232 def toarray_sql(self, expression: exp.ToArray) -> str: 5233 arg = expression.this 5234 if not arg.type: 5235 import sqlglot.optimizer.annotate_types 5236 5237 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5238 5239 if arg.is_type(exp.DType.ARRAY): 5240 return self.sql(arg) 5241 5242 cond_for_null = arg.is_(exp.null()) 5243 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5245 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5246 this = expression.this 5247 time_format = self.format_time(expression) 5248 5249 if time_format: 5250 return self.sql( 5251 exp.cast( 5252 exp.StrToTime(this=this, format=expression.args["format"]), 5253 exp.DType.TIME, 5254 ) 5255 ) 5256 5257 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5258 return self.sql(this) 5259 5260 return self.sql(exp.cast(this, exp.DType.TIME))
5262 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5263 this = expression.this 5264 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5265 return self.sql(this) 5266 5267 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5269 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5270 this = expression.this 5271 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5272 return self.sql(this) 5273 5274 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5276 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5277 this = expression.this 5278 time_format = self.format_time(expression) 5279 safe = expression.args.get("safe") 5280 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5281 return self.sql( 5282 exp.cast( 5283 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5284 exp.DType.DATE, 5285 ) 5286 ) 5287 5288 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5289 return self.sql(this) 5290 5291 if safe: 5292 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5293 5294 return self.sql(exp.cast(this, exp.DType.DATE))
5306 def lastday_sql(self, expression: exp.LastDay) -> str: 5307 if self.LAST_DAY_SUPPORTS_DATE_PART: 5308 return self.function_fallback_sql(expression) 5309 5310 unit = expression.args.get("unit") 5311 if unit and unit.name.upper() != "MONTH": 5312 self.unsupported("Date parts are not supported in LAST_DAY.") 5313 5314 return self.func("LAST_DAY", expression.this)
def
arrayinsert_sql( self, expression: sqlglot.expressions.array.ArrayInsert, index_offset: int = 0) -> str:
5326 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5327 this = expression.this 5328 position = expression.args["position"] 5329 offset = index_offset - (expression.args.get("offset") or 0) 5330 5331 if offset: 5332 if position.is_int: 5333 value = position.to_py() 5334 if value >= 0: 5335 position = exp.Literal.number(value + offset) 5336 elif offset < 0 and value == -1: 5337 # 1-based -1 appends, which a 0-based position can only express as the size 5338 position = exp.ArraySize(this=this.copy()) 5339 else: 5340 # Negative positions count from the end, so they shift in the opposite 5341 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5342 position = exp.Literal.number(value - offset) 5343 else: 5344 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5345 5346 return self.func("ARRAY_INSERT", this, position, expression.expression)
5348 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5349 if self.CAN_IMPLEMENT_ARRAY_ANY: 5350 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5351 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5352 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5353 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5354 5355 import sqlglot.dialects.dialect 5356 5357 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5358 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5359 self.unsupported("ARRAY_ANY is unsupported") 5360 5361 return self.function_fallback_sql(expression)
5363 def struct_sql(self, expression: exp.Struct) -> str: 5364 expression.set( 5365 "expressions", 5366 [ 5367 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5368 if isinstance(e, exp.PropertyEQ) 5369 else e 5370 for e in expression.expressions 5371 ], 5372 ) 5373 5374 return self.function_fallback_sql(expression)
5382 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5383 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5384 tables = f" {self.expressions(expression)}" 5385 5386 exists = " IF EXISTS" if expression.args.get("exists") else "" 5387 5388 on_cluster = self.sql(expression, "cluster") 5389 on_cluster = f" {on_cluster}" if on_cluster else "" 5390 5391 identity = self.sql(expression, "identity") 5392 identity = f" {identity} IDENTITY" if identity else "" 5393 5394 option = self.sql(expression, "option") 5395 option = f" {option}" if option else "" 5396 5397 partition = self.sql(expression, "partition") 5398 partition = f" {partition}" if partition else "" 5399 5400 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5404 def convert_sql(self, expression: exp.Convert) -> str: 5405 to = expression.this 5406 value = expression.expression 5407 style = expression.args.get("style") 5408 safe = expression.args.get("safe") 5409 strict = expression.args.get("strict") 5410 5411 if not to or not value: 5412 return "" 5413 5414 # Retrieve length of datatype and override to default if not specified 5415 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5416 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5417 5418 transformed: exp.Expr | None = None 5419 cast = exp.Cast if strict else exp.TryCast 5420 5421 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5422 if isinstance(style, exp.Literal) and style.is_int: 5423 import sqlglot.dialects.tsql 5424 5425 style_value = style.name 5426 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5427 if not converted_style: 5428 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5429 5430 fmt = exp.Literal.string(converted_style) 5431 5432 if to.this == exp.DType.DATE: 5433 transformed = exp.StrToDate(this=value, format=fmt) 5434 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5435 transformed = exp.StrToTime(this=value, format=fmt) 5436 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5437 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5438 elif to.this == exp.DType.TEXT: 5439 transformed = exp.TimeToStr(this=value, format=fmt) 5440 5441 if not transformed: 5442 transformed = cast(this=value, to=to, safe=safe) 5443 5444 return self.sql(transformed)
5521 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5522 option = self.sql(expression, "this") 5523 5524 if expression.expressions: 5525 upper = option.upper() 5526 5527 # Snowflake FILE_FORMAT options are separated by whitespace 5528 sep = " " if upper == "FILE_FORMAT" else ", " 5529 5530 # Databricks copy/format options do not set their list of values with EQ 5531 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5532 values = self.expressions(expression, flat=True, sep=sep) 5533 return f"{option}{op}({values})" 5534 5535 value = self.sql(expression, "expression") 5536 5537 if not value: 5538 return option 5539 5540 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5541 5542 return f"{option}{op}{value}"
5544 def credentials_sql(self, expression: exp.Credentials) -> str: 5545 cred_expr = expression.args.get("credentials") 5546 if isinstance(cred_expr, exp.Literal): 5547 # Redshift case: CREDENTIALS <string> 5548 credentials = self.sql(expression, "credentials") 5549 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5550 else: 5551 # Snowflake case: CREDENTIALS = (...) 5552 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5553 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5554 5555 storage = self.sql(expression, "storage") 5556 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5557 5558 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5559 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5560 5561 iam_role = self.sql(expression, "iam_role") 5562 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5563 5564 region = self.sql(expression, "region") 5565 region = f" REGION {region}" if region else "" 5566 5567 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5569 def copy_sql(self, expression: exp.Copy) -> str: 5570 this = self.sql(expression, "this") 5571 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5572 5573 credentials = self.sql(expression, "credentials") 5574 credentials = self.seg(credentials) if credentials else "" 5575 files = self.expressions(expression, key="files", flat=True) 5576 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5577 5578 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5579 params = self.expressions( 5580 expression, 5581 key="params", 5582 sep=sep, 5583 new_line=True, 5584 skip_last=True, 5585 skip_first=True, 5586 indent=self.COPY_PARAMS_ARE_WRAPPED, 5587 ) 5588 5589 if params: 5590 if self.COPY_PARAMS_ARE_WRAPPED: 5591 params = f" WITH ({params})" 5592 elif not self.pretty and (files or credentials): 5593 params = f" {params}" 5594 5595 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5600 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5601 on_sql = "ON" if expression.args.get("on") else "OFF" 5602 filter_col: str | None = self.sql(expression, "filter_column") 5603 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5604 retention_period: str | None = self.sql(expression, "retention_period") 5605 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5606 5607 if filter_col or retention_period: 5608 on_sql = self.func("ON", filter_col, retention_period) 5609 5610 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5612 def maskingpolicycolumnconstraint_sql( 5613 self, expression: exp.MaskingPolicyColumnConstraint 5614 ) -> str: 5615 this = self.sql(expression, "this") 5616 expressions = self.expressions(expression, flat=True) 5617 expressions = f" USING ({expressions})" if expressions else "" 5618 return f"MASKING POLICY {this}{expressions}"
5628 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5629 this = self.sql(expression, "this") 5630 expr = expression.expression 5631 5632 if isinstance(expr, exp.Func): 5633 # T-SQL's CLR functions are case sensitive 5634 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5635 else: 5636 expr = self.sql(expression, "expression") 5637 5638 return self.scope_resolution(expr, this)
5646 def rand_sql(self, expression: exp.Rand) -> str: 5647 lower = self.sql(expression, "lower") 5648 upper = self.sql(expression, "upper") 5649 5650 if lower and upper: 5651 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5652 return self.func("RAND", expression.this)
5654 def changes_sql(self, expression: exp.Changes) -> str: 5655 information = self.sql(expression, "information") 5656 information = f"INFORMATION => {information}" 5657 at_before = self.sql(expression, "at_before") 5658 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5659 end = self.sql(expression, "end") 5660 end = f"{self.seg('')}{end}" if end else "" 5661 5662 return f"CHANGES ({information}){at_before}{end}"
5664 def pad_sql(self, expression: exp.Pad) -> str: 5665 prefix = "L" if expression.args.get("is_left") else "R" 5666 5667 fill_pattern = self.sql(expression, "fill_pattern") or None 5668 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5669 fill_pattern = "' '" 5670 5671 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5677 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5678 generate_series = exp.GenerateSeries(**expression.args) 5679 5680 parent = expression.parent 5681 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5682 parent = parent.parent 5683 5684 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5685 return self.sql(exp.Unnest(expressions=[generate_series])) 5686 5687 if isinstance(parent, exp.Select): 5688 self.unsupported("GenerateSeries projection unnesting is not supported.") 5689 5690 return self.sql(generate_series)
5692 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5693 if self.SUPPORTS_CONVERT_TIMEZONE: 5694 return self.function_fallback_sql(expression) 5695 5696 source_tz = expression.args.get("source_tz") 5697 target_tz = expression.args.get("target_tz") 5698 timestamp = expression.args.get("timestamp") 5699 5700 if source_tz and timestamp: 5701 timestamp = exp.AtTimeZone( 5702 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5703 ) 5704 5705 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5706 5707 return self.sql(expr)
5709 def json_sql(self, expression: exp.JSON) -> str: 5710 this = self.sql(expression, "this") 5711 this = f" {this}" if this else "" 5712 5713 _with = expression.args.get("with_") 5714 5715 if _with is None: 5716 with_sql = "" 5717 elif not _with: 5718 with_sql = " WITHOUT" 5719 else: 5720 with_sql = " WITH" 5721 5722 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5723 5724 return f"JSON{this}{with_sql}{unique_sql}"
5726 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5727 path = self.sql(expression, "path") 5728 returning = self.sql(expression, "returning") 5729 returning = f" RETURNING {returning}" if returning else "" 5730 5731 on_condition = self.sql(expression, "on_condition") 5732 on_condition = f" {on_condition}" if on_condition else "" 5733 5734 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5740 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5741 else_ = "ELSE " if expression.args.get("else_") else "" 5742 condition = self.sql(expression, "expression") 5743 condition = f"WHEN {condition} THEN " if condition else else_ 5744 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5745 return f"{condition}{insert}"
5753 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5754 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5755 empty = expression.args.get("empty") 5756 empty = ( 5757 f"DEFAULT {empty} ON EMPTY" 5758 if isinstance(empty, exp.Expr) 5759 else self.sql(expression, "empty") 5760 ) 5761 5762 error = expression.args.get("error") 5763 error = ( 5764 f"DEFAULT {error} ON ERROR" 5765 if isinstance(error, exp.Expr) 5766 else self.sql(expression, "error") 5767 ) 5768 5769 if error and empty: 5770 error = ( 5771 f"{empty} {error}" 5772 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5773 else f"{error} {empty}" 5774 ) 5775 empty = "" 5776 5777 null = self.sql(expression, "null") 5778 5779 return f"{empty}{error}{null}"
5785 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5786 this = self.sql(expression, "this") 5787 path = self.sql(expression, "path") 5788 5789 passing = self.expressions(expression, "passing") 5790 passing = f" PASSING {passing}" if passing else "" 5791 5792 on_condition = self.sql(expression, "on_condition") 5793 on_condition = f" {on_condition}" if on_condition else "" 5794 5795 path = f"{path}{passing}{on_condition}" 5796 5797 return self.func("JSON_EXISTS", this, path)
5839 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5840 array_agg = self.function_fallback_sql(expression) 5841 column_expr = expression.this 5842 if isinstance(column_expr, exp.Order): 5843 column_expr = column_expr.this 5844 5845 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5926 def overlay_sql(self, expression: exp.Overlay) -> str: 5927 this = self.sql(expression, "this") 5928 expr = self.sql(expression, "expression") 5929 from_sql = self.sql(expression, "from_") 5930 for_sql = self.sql(expression, "for_") 5931 for_sql = f" FOR {for_sql}" if for_sql else "" 5932 5933 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5940 def string_sql(self, expression: exp.String) -> str: 5941 this = expression.this 5942 zone = expression.args.get("zone") 5943 5944 if zone: 5945 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5946 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5947 # set for source_tz to transpile the time conversion before the STRING cast 5948 this = exp.ConvertTimezone( 5949 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5950 ) 5951 5952 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5962 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5963 filler = self.sql(expression, "this") 5964 filler = f" {filler}" if filler else "" 5965 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5966 return f"TRUNCATE{filler} {with_count}"
5968 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5969 if self.SUPPORTS_UNIX_SECONDS: 5970 return self.function_fallback_sql(expression) 5971 5972 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5973 5974 return self.sql( 5975 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5976 )
5978 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5979 dim = expression.expression 5980 5981 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5982 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5983 if not (dim.is_int and dim.name == "1"): 5984 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5985 dim = None 5986 5987 # If dimension is required but not specified, default initialize it 5988 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5989 dim = exp.Literal.number(1) 5990 5991 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5993 def attach_sql(self, expression: exp.Attach) -> str: 5994 this = self.sql(expression, "this") 5995 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5996 expressions = self.expressions(expression) 5997 expressions = f" ({expressions})" if expressions else "" 5998 5999 return f"ATTACH{exists_sql} {this}{expressions}"
6001 def detach_sql(self, expression: exp.Detach) -> str: 6002 kind = self.sql(expression, "kind") 6003 kind = f" {kind}" if kind else "" 6004 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6005 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6006 exists = " IF EXISTS" if expression.args.get("exists") else "" 6007 if exists: 6008 kind = kind or " DATABASE" 6009 6010 this = self.sql(expression, "this") 6011 this = f" {this}" if this else "" 6012 cluster = self.sql(expression, "cluster") 6013 cluster = f" {cluster}" if cluster else "" 6014 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6015 sync = " SYNC" if expression.args.get("sync") else "" 6016 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
6029 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6030 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6031 encode = f"{encode} {self.sql(expression, 'this')}" 6032 6033 properties = expression.args.get("properties") 6034 if properties: 6035 encode = f"{encode} {self.properties(properties)}" 6036 6037 return encode
6039 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6040 this = self.sql(expression, "this") 6041 include = f"INCLUDE {this}" 6042 6043 column_def = self.sql(expression, "column_def") 6044 if column_def: 6045 include = f"{include} {column_def}" 6046 6047 alias = self.sql(expression, "alias") 6048 if alias: 6049 include = f"{include} AS {alias}" 6050 6051 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6064 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6065 partitions = self.expressions(expression, "partition_expressions") 6066 create = self.expressions(expression, "create_expressions") 6067 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6069 def partitionbyrangepropertydynamic_sql( 6070 self, expression: exp.PartitionByRangePropertyDynamic 6071 ) -> str: 6072 start = self.sql(expression, "start") 6073 end = self.sql(expression, "end") 6074 6075 every = expression.args["every"] 6076 if isinstance(every, exp.Interval) and every.this.is_string: 6077 every.this.replace(exp.Literal.number(every.name)) 6078 6079 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6092 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6093 kind = self.sql(expression, "kind") 6094 option = self.sql(expression, "option") 6095 option = f" {option}" if option else "" 6096 this = self.sql(expression, "this") 6097 this = f" {this}" if this else "" 6098 columns = self.expressions(expression) 6099 columns = f" {columns}" if columns else "" 6100 return f"{kind}{option} STATISTICS{this}{columns}"
6102 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6103 this = self.sql(expression, "this") 6104 columns = self.expressions(expression) 6105 inner_expression = self.sql(expression, "expression") 6106 inner_expression = f" {inner_expression}" if inner_expression else "" 6107 update_options = self.sql(expression, "update_options") 6108 update_options = f" {update_options} UPDATE" if update_options else "" 6109 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6120 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6121 kind = self.sql(expression, "kind") 6122 this = self.sql(expression, "this") 6123 this = f" {this}" if this else "" 6124 inner_expression = self.sql(expression, "expression") 6125 return f"VALIDATE {kind}{this}{inner_expression}"
6127 def analyze_sql(self, expression: exp.Analyze) -> str: 6128 options = self.expressions(expression, key="options", sep=" ") 6129 options = f" {options}" if options else "" 6130 kind = self.sql(expression, "kind") 6131 kind = f" {kind}" if kind else "" 6132 tables = self.expressions(expression, key="tables", flat=True) 6133 tables = f" {tables}" if tables else "" 6134 mode = self.sql(expression, "mode") 6135 mode = f" {mode}" if mode else "" 6136 properties = self.sql(expression, "properties") 6137 properties = f" {properties}" if properties else "" 6138 partition = self.sql(expression, "partition") 6139 partition = f" {partition}" if partition else "" 6140 inner_expression = self.sql(expression, "expression") 6141 inner_expression = f" {inner_expression}" if inner_expression else "" 6142 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6144 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6145 this = self.sql(expression, "this") 6146 namespaces = self.expressions(expression, key="namespaces") 6147 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6148 passing = self.expressions(expression, key="passing") 6149 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6150 columns = self.expressions(expression, key="columns") 6151 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6152 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6153 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6159 def export_sql(self, expression: exp.Export) -> str: 6160 this = self.sql(expression, "this") 6161 connection = self.sql(expression, "connection") 6162 connection = f"WITH CONNECTION {connection} " if connection else "" 6163 options = self.sql(expression, "options") 6164 return f"EXPORT DATA {connection}{options} AS {this}"
6170 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6171 variables = self.expressions(expression, "this") 6172 default = self.sql(expression, "default") 6173 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6174 6175 kind = self.sql(expression, "kind") 6176 if isinstance(expression.args.get("kind"), exp.Schema): 6177 kind = f"TABLE {kind}" 6178 6179 kind = f" {kind}" if kind else "" 6180 6181 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6183 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6184 kind = self.sql(expression, "kind") 6185 this = self.sql(expression, "this") 6186 set = self.sql(expression, "expression") 6187 using = self.sql(expression, "using") 6188 using = f" USING {using}" if using else "" 6189 6190 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6191 6192 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql( self, expression: sqlglot.expressions.core.CombinedParameterizedAgg) -> str:
def
get_put_sql( self, expression: sqlglot.expressions.query.Put | sqlglot.expressions.query.Get) -> str:
6215 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6216 # Snowflake GET/PUT statements: 6217 # PUT <file> <internalStage> <properties> 6218 # GET <internalStage> <file> <properties> 6219 props = expression.args.get("properties") 6220 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6221 this = self.sql(expression, "this") 6222 target = self.sql(expression, "target") 6223 6224 if isinstance(expression, exp.Put): 6225 return f"PUT {this} {target}{props_sql}" 6226 else: 6227 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6229 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6230 this = self.sql(expression, "this") 6231 expr = self.sql(expression, "expression") 6232 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6233 return f"TRANSLATE({this} USING {expr}{with_error})"
6235 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6236 if self.SUPPORTS_DECODE_CASE: 6237 return self.func("DECODE", *expression.expressions) 6238 6239 decode_expr, *expressions = expression.expressions 6240 6241 ifs = [] 6242 for search, result in zip(expressions[::2], expressions[1::2]): 6243 if isinstance(search, exp.Literal): 6244 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6245 elif isinstance(search, exp.Null): 6246 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6247 else: 6248 if isinstance(search, exp.Binary): 6249 search = exp.paren(search) 6250 6251 cond = exp.or_( 6252 decode_expr.eq(search), 6253 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6254 copy=False, 6255 ) 6256 ifs.append(exp.If(this=cond, true=result)) 6257 6258 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6259 return self.sql(case)
6261 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6262 this = self.sql(expression, "this") 6263 this = self.seg(this, sep="") 6264 dimensions = self.expressions( 6265 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6266 ) 6267 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6268 metrics = self.expressions( 6269 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6270 ) 6271 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6272 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6273 facts = self.seg(f"FACTS {facts}") if facts else "" 6274 where = self.sql(expression, "where") 6275 where = self.seg(f"WHERE {where}") if where else "" 6276 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6277 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6279 def getextract_sql(self, expression: exp.GetExtract) -> str: 6280 this = expression.this 6281 expr = expression.expression 6282 6283 if not this.type or not expression.type: 6284 import sqlglot.optimizer.annotate_types 6285 6286 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6287 6288 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6289 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6290 6291 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6308 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6309 method = self.sql(expression, "method") 6310 kind = expression.args.get("kind") 6311 if not kind: 6312 return f"REFRESH {method}" 6313 6314 every = self.sql(expression, "every") 6315 unit = self.sql(expression, "unit") 6316 every = f" EVERY {every} {unit}" if every else "" 6317 starts = self.sql(expression, "starts") 6318 starts = f" STARTS {starts}" if starts else "" 6319 6320 return f"REFRESH {method} ON {kind}{every}{starts}"
6329 def uuid_sql(self, expression: exp.Uuid) -> str: 6330 is_string = expression.args.get("is_string", False) 6331 uuid_func_sql = self.func("UUID") 6332 6333 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6334 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6335 6336 return uuid_func_sql
6338 def initcap_sql(self, expression: exp.Initcap) -> str: 6339 delimiters = expression.expression 6340 6341 if delimiters: 6342 # do not generate delimiters arg if we are round-tripping from default delimiters 6343 if ( 6344 delimiters.is_string 6345 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6346 ): 6347 delimiters = None 6348 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6349 self.unsupported("INITCAP does not support custom delimiters") 6350 delimiters = None 6351 6352 return self.func("INITCAP", expression.this, delimiters)
6362 def weekstart_name(self, expression: exp.WeekStart) -> str: 6363 import sqlglot.dialects.dialect 6364 6365 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6366 this = expression.this.name.upper() 6367 6368 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6369 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6370 6371 if dow_from_week_start_day != dow_from_week_offset: 6372 self.unsupported( 6373 f"WEEK({this}) is not supported; falling back to the default week start day" 6374 ) 6375 6376 return "WEEK"
6378 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6379 name = self.weekstart_name(expression) 6380 6381 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6382 if isinstance(expression.parent, exp.DateTrunc): 6383 return self.sql(exp.Literal.string(name)) 6384 6385 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: