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 alias = self.sql(expression, "alias") 2963 alias = f" AS {alias}" if alias else "" 2964 2965 ordinality = expression.args.get("ordinality") or "" 2966 if ordinality: 2967 ordinality = f" WITH ORDINALITY{alias}" 2968 alias = "" 2969 2970 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2971 2972 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2973 this = self.sql(expression, "this") 2974 2975 args = [ 2976 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2977 for e in (expression.args.get(k) for k in ("offset", "expression")) 2978 if e 2979 ] 2980 2981 args_sql = ", ".join(self.sql(e) for e in args) 2982 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2983 expressions = self.expressions(expression, flat=True) 2984 limit_options = self.sql(expression, "limit_options") 2985 expressions = f" BY {expressions}" if expressions else "" 2986 2987 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2988 2989 def offset_sql(self, expression: exp.Offset) -> str: 2990 this = self.sql(expression, "this") 2991 value = expression.expression 2992 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2993 expressions = self.expressions(expression, flat=True) 2994 expressions = f" BY {expressions}" if expressions else "" 2995 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2996 2997 def setitem_sql(self, expression: exp.SetItem) -> str: 2998 kind = self.sql(expression, "kind") 2999 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3000 kind = "" 3001 else: 3002 kind = f"{kind} " if kind else "" 3003 this = self.sql(expression, "this") 3004 expressions = self.expressions(expression) 3005 collate = self.sql(expression, "collate") 3006 collate = f" COLLATE {collate}" if collate else "" 3007 global_ = "GLOBAL " if expression.args.get("global_") else "" 3008 return f"{global_}{kind}{this}{expressions}{collate}" 3009 3010 def set_sql(self, expression: exp.Set) -> str: 3011 expressions = f" {self.expressions(expression, flat=True)}" 3012 tag = " TAG" if expression.args.get("tag") else "" 3013 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3014 3015 def queryband_sql(self, expression: exp.QueryBand) -> str: 3016 this = self.sql(expression, "this") 3017 update = " UPDATE" if expression.args.get("update") else "" 3018 scope = self.sql(expression, "scope") 3019 scope = f" FOR {scope}" if scope else "" 3020 3021 return f"QUERY_BAND = {this}{update}{scope}" 3022 3023 def pragma_sql(self, expression: exp.Pragma) -> str: 3024 return f"PRAGMA {self.sql(expression, 'this')}" 3025 3026 def lock_sql(self, expression: exp.Lock) -> str: 3027 if not self.LOCKING_READS_SUPPORTED: 3028 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3029 return "" 3030 3031 update = expression.args["update"] 3032 key = expression.args.get("key") 3033 if update: 3034 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3035 else: 3036 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3037 expressions = self.expressions(expression, flat=True) 3038 expressions = f" OF {expressions}" if expressions else "" 3039 wait = expression.args.get("wait") 3040 3041 if wait is not None: 3042 if isinstance(wait, exp.Literal): 3043 wait = f" WAIT {self.sql(wait)}" 3044 else: 3045 wait = " NOWAIT" if wait else " SKIP LOCKED" 3046 3047 return f"{lock_type}{expressions}{wait or ''}" 3048 3049 def literal_sql(self, expression: exp.Literal) -> str: 3050 text = expression.this or "" 3051 if expression.is_string: 3052 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3053 return text 3054 3055 def escape_str( 3056 self, 3057 text: str, 3058 escape_backslash: bool = True, 3059 delimiter: str | None = None, 3060 escaped_delimiter: str | None = None, 3061 is_byte_string: bool = False, 3062 ) -> str: 3063 if is_byte_string: 3064 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3065 else: 3066 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3067 3068 if supports_escape_sequences: 3069 text = "".join( 3070 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3071 for ch in text 3072 ) 3073 3074 delimiter = delimiter or self.dialect.QUOTE_END 3075 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3076 3077 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3078 3079 def loaddata_sql(self, expression: exp.LoadData) -> str: 3080 is_overwrite = expression.args.get("overwrite") 3081 overwrite = " OVERWRITE" if is_overwrite else "" 3082 this = self.sql(expression, "this") 3083 3084 files = expression.args.get("files") 3085 if files: 3086 files_sql = self.expressions(files, flat=True) 3087 files_sql = f"FILES{self.wrap(files_sql)}" 3088 if is_overwrite: 3089 this = f" {this}" 3090 elif expression.args.get("temp"): 3091 this = f" INTO TEMP TABLE {this}" 3092 else: 3093 this = f" INTO TABLE {this}" 3094 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3095 3096 local = " LOCAL" if expression.args.get("local") else "" 3097 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3098 this = f" INTO TABLE {this}" 3099 partition = self.sql(expression, "partition") 3100 partition = f" {partition}" if partition else "" 3101 input_format = self.sql(expression, "input_format") 3102 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3103 serde = self.sql(expression, "serde") 3104 serde = f" SERDE {serde}" if serde else "" 3105 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3106 3107 def null_sql(self, *_) -> str: 3108 return "NULL" 3109 3110 def boolean_sql(self, expression: exp.Boolean) -> str: 3111 return "TRUE" if expression.this else "FALSE" 3112 3113 def booland_sql(self, expression: exp.Booland) -> str: 3114 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3115 3116 def boolor_sql(self, expression: exp.Boolor) -> str: 3117 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3118 3119 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3120 this = self.sql(expression, "this") 3121 this = f"{this} " if this else this 3122 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3123 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3124 3125 def withfill_sql(self, expression: exp.WithFill) -> str: 3126 from_sql = self.sql(expression, "from_") 3127 from_sql = f" FROM {from_sql}" if from_sql else "" 3128 to_sql = self.sql(expression, "to") 3129 to_sql = f" TO {to_sql}" if to_sql else "" 3130 step_sql = self.sql(expression, "step") 3131 step_sql = f" STEP {step_sql}" if step_sql else "" 3132 interpolated_values = [ 3133 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3134 if isinstance(e, exp.Alias) 3135 else self.sql(e, "this") 3136 for e in expression.args.get("interpolate") or [] 3137 ] 3138 interpolate = ( 3139 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3140 ) 3141 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3142 3143 def cluster_sql(self, expression: exp.Cluster) -> str: 3144 return self.op_expressions("CLUSTER BY", expression) 3145 3146 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3147 if expression.this: 3148 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3149 return "" 3150 expressions = self.expressions(expression, flat=True) 3151 return f"CLUSTER BY ({expressions})" 3152 3153 def distribute_sql(self, expression: exp.Distribute) -> str: 3154 return self.op_expressions("DISTRIBUTE BY", expression) 3155 3156 def sort_sql(self, expression: exp.Sort) -> str: 3157 return self.op_expressions("SORT BY", expression) 3158 3159 def _resolve_ordered_for_null_ordering_simulation( 3160 self, expression: exp.Ordered 3161 ) -> exp.Expr | None: 3162 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3163 3164 Returns the underlying expression of the uniquely-matching projection 3165 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3166 simulation, since the CASE is evaluated in FROM-clause scope rather 3167 than alias scope (MySQL error 1052). Returns None if no safe 3168 substitution applies, leaving the original behaviour unchanged. 3169 """ 3170 this = expression.this 3171 if not (isinstance(this, exp.Column) and not this.table): 3172 return None 3173 3174 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3175 if not isinstance(ancestor, exp.Select): 3176 return None 3177 3178 column_name = this.name 3179 matched: list[exp.Expr] = [ 3180 p.this if isinstance(p, exp.Alias) else p 3181 for p in ancestor.selects 3182 if p.output_name == column_name 3183 ] 3184 match = matched[0] if len(matched) == 1 else None 3185 3186 # Skip the substitution when it would be identical to the existing 3187 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3188 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3189 return None 3190 3191 return match 3192 3193 def ordered_sql(self, expression: exp.Ordered) -> str: 3194 desc = expression.args.get("desc") 3195 asc = not desc 3196 3197 nulls_first = expression.args.get("nulls_first") 3198 nulls_last = not nulls_first 3199 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3200 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3201 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3202 3203 this = self.sql(expression, "this") 3204 3205 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3206 nulls_sort_change = "" 3207 if nulls_first and ( 3208 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3209 ): 3210 nulls_sort_change = " NULLS FIRST" 3211 elif ( 3212 nulls_last 3213 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3214 and not nulls_are_last 3215 ): 3216 nulls_sort_change = " NULLS LAST" 3217 3218 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3219 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3220 window = expression.find_ancestor(exp.Window, exp.Select) 3221 3222 if isinstance(window, exp.Window): 3223 window_this = window.this 3224 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3225 window_this = window_this.this 3226 spec = window.args.get("spec") 3227 else: 3228 window_this = None 3229 spec = None 3230 3231 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3232 # without a spec or with a ROWS spec, but not with RANGE 3233 if not ( 3234 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3235 and (not spec or spec.text("kind").upper() == "ROWS") 3236 ): 3237 if window_this and spec: 3238 self.unsupported( 3239 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3240 ) 3241 nulls_sort_change = "" 3242 elif self.NULL_ORDERING_SUPPORTED is False and ( 3243 (asc and nulls_sort_change == " NULLS LAST") 3244 or (desc and nulls_sort_change == " NULLS FIRST") 3245 ): 3246 # BigQuery does not allow these ordering/nulls combinations when used under 3247 # an aggregation func or under a window containing one 3248 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3249 3250 if isinstance(ancestor, exp.Window): 3251 ancestor = ancestor.this 3252 if isinstance(ancestor, exp.AggFunc): 3253 self.unsupported( 3254 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3255 ) 3256 nulls_sort_change = "" 3257 elif self.NULL_ORDERING_SUPPORTED is None: 3258 if expression.this.is_int: 3259 self.unsupported( 3260 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3261 ) 3262 elif not isinstance(expression.this, exp.Rand): 3263 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3264 target = self.sql(resolved) if resolved is not None else this 3265 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3266 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3267 nulls_sort_change = "" 3268 3269 with_fill = self.sql(expression, "with_fill") 3270 with_fill = f" {with_fill}" if with_fill else "" 3271 3272 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3273 3274 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3275 window_frame = self.sql(expression, "window_frame") 3276 window_frame = f"{window_frame} " if window_frame else "" 3277 3278 this = self.sql(expression, "this") 3279 3280 return f"{window_frame}{this}" 3281 3282 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3283 partition = self.partition_by_sql(expression) 3284 order = self.sql(expression, "order") 3285 measures = self.expressions(expression, key="measures") 3286 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3287 rows = self.sql(expression, "rows") 3288 rows = self.seg(rows) if rows else "" 3289 after = self.sql(expression, "after") 3290 after = self.seg(after) if after else "" 3291 pattern = self.sql(expression, "pattern") 3292 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3293 definition_sqls = [ 3294 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3295 for definition in expression.args.get("define", []) 3296 ] 3297 definitions = self.expressions(sqls=definition_sqls) 3298 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3299 body = "".join( 3300 ( 3301 partition, 3302 order, 3303 measures, 3304 rows, 3305 after, 3306 pattern, 3307 define, 3308 ) 3309 ) 3310 alias = self.sql(expression, "alias") 3311 alias = f" {alias}" if alias else "" 3312 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3313 3314 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3315 limit = expression.args.get("limit") 3316 3317 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3318 count = limit.args.get("count") 3319 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3320 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3321 limit = exp.Limit( 3322 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3323 ) 3324 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3325 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3326 3327 return csv( 3328 *sqls, 3329 *[self.sql(join) for join in expression.args.get("joins") or []], 3330 self.sql(expression, "match"), 3331 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3332 self.sql(expression, "prewhere"), 3333 self.sql(expression, "where"), 3334 self.sql(expression, "connect"), 3335 self.sql(expression, "group"), 3336 self.sql(expression, "having"), 3337 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3338 self.sql(expression, "order"), 3339 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3340 *self.after_limit_modifiers(expression), 3341 self.sql(expression, "for_"), 3342 self.options_modifier(expression), 3343 sep="", 3344 ) 3345 3346 def options_modifier(self, expression: exp.Expr) -> str: 3347 options = self.expressions(expression, key="options") 3348 return f" {options}" if options else "" 3349 3350 def forclause_sql(self, expression: exp.ForClause) -> str: 3351 kind = expression.args["kind"] 3352 if kind == "BROWSE": 3353 return f"{self.sep()}FOR BROWSE" 3354 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3355 # the target dialect doesn't support QueryOption, so we drop the clause. 3356 options = self.expressions(expression, key="expressions") 3357 if not options: 3358 return "" 3359 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3360 3361 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3362 self.unsupported("Unsupported query option.") 3363 return "" 3364 3365 def offset_limit_modifiers( 3366 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3367 ) -> list[str]: 3368 return [ 3369 self.sql(expression, "offset") if fetch else self.sql(limit), 3370 self.sql(limit) if fetch else self.sql(expression, "offset"), 3371 ] 3372 3373 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3374 locks = self.expressions(expression, key="locks", sep=" ") 3375 locks = f" {locks}" if locks else "" 3376 return [locks, self.sql(expression, "sample")] 3377 3378 def select_sql(self, expression: exp.Select) -> str: 3379 into = expression.args.get("into") 3380 if not self.SUPPORTS_SELECT_INTO and into: 3381 into.pop() 3382 3383 hint = self.sql(expression, "hint") 3384 distinct = self.sql(expression, "distinct") 3385 distinct = f" {distinct}" if distinct else "" 3386 kind = self.sql(expression, "kind") 3387 3388 limit = expression.args.get("limit") 3389 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3390 top = self.limit_sql(limit, top=True) 3391 limit.pop() 3392 else: 3393 top = "" 3394 3395 expressions = self.expressions(expression) 3396 3397 if kind: 3398 if kind in self.SELECT_KINDS: 3399 kind = f" AS {kind}" 3400 else: 3401 if kind == "STRUCT": 3402 expressions = self.expressions( 3403 sqls=[ 3404 self.sql( 3405 exp.Struct( 3406 expressions=[ 3407 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3408 if isinstance(e, exp.Alias) 3409 else e 3410 for e in expression.expressions 3411 ] 3412 ) 3413 ) 3414 ] 3415 ) 3416 kind = "" 3417 3418 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3419 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3420 3421 exclude = expression.args.get("exclude") 3422 3423 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3424 exclude_sql = self.expressions(sqls=exclude, flat=True) 3425 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3426 3427 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3428 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3429 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3430 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3431 sql = self.query_modifiers( 3432 expression, 3433 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3434 self.sql(expression, "into", comment=False), 3435 self.sql(expression, "from_", comment=False), 3436 ) 3437 3438 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3439 if expression.args.get("with_"): 3440 sql = self.maybe_comment(sql, expression) 3441 expression.pop_comments() 3442 3443 sql = self.prepend_ctes(expression, sql) 3444 3445 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3446 expression.set("exclude", None) 3447 subquery = expression.subquery(copy=False) 3448 star = exp.Star(except_=exclude) 3449 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3450 3451 if not self.SUPPORTS_SELECT_INTO and into: 3452 if into.args.get("temporary"): 3453 table_kind = " TEMPORARY" 3454 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3455 table_kind = " UNLOGGED" 3456 else: 3457 table_kind = "" 3458 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3459 3460 return sql 3461 3462 def schema_sql(self, expression: exp.Schema) -> str: 3463 this = self.sql(expression, "this") 3464 sql = self.schema_columns_sql(expression) 3465 return f"{this} {sql}" if this and sql else this or sql 3466 3467 def schema_columns_sql(self, expression: exp.Expr) -> str: 3468 if expression.expressions: 3469 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3470 return "" 3471 3472 def star_sql(self, expression: exp.Star) -> str: 3473 except_ = self.expressions(expression, key="except_", flat=True) 3474 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3475 replace = self.expressions(expression, key="replace", flat=True) 3476 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3477 rename = self.expressions(expression, key="rename", flat=True) 3478 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3479 ilike = self.sql(expression, "ilike") 3480 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3481 return f"*{ilike}{except_}{replace}{rename}" 3482 3483 def parameter_sql(self, expression: exp.Parameter) -> str: 3484 this = self.sql(expression, "this") 3485 return f"{self.PARAMETER_TOKEN}{this}" 3486 3487 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3488 this = self.sql(expression, "this") 3489 kind = expression.text("kind") 3490 if kind: 3491 kind = f"{kind}." 3492 return f"@@{kind}{this}" 3493 3494 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3495 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3496 3497 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3498 alias = self.sql(expression, "alias") 3499 alias = f"{sep}{alias}" if alias else "" 3500 sample = self.sql(expression, "sample") 3501 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3502 alias = f"{sample}{alias}" 3503 3504 # Set to None so it's not generated again by self.query_modifiers() 3505 expression.set("sample", None) 3506 3507 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3508 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3509 return self.prepend_ctes(expression, sql) 3510 3511 def qualify_sql(self, expression: exp.Qualify) -> str: 3512 this = self.indent(self.sql(expression, "this")) 3513 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3514 3515 def unnest_sql(self, expression: exp.Unnest) -> str: 3516 args = self.expressions(expression, flat=True) 3517 3518 alias = expression.args.get("alias") 3519 offset = expression.args.get("offset") 3520 3521 if self.UNNEST_WITH_ORDINALITY: 3522 if alias and isinstance(offset, exp.Expr): 3523 alias.append("columns", offset) 3524 expression.set("offset", None) 3525 3526 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3527 columns = alias.columns 3528 alias = self.sql(columns[0]) if columns else "" 3529 else: 3530 alias = self.sql(alias) 3531 3532 alias = f" AS {alias}" if alias else alias 3533 if self.UNNEST_WITH_ORDINALITY: 3534 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3535 else: 3536 if isinstance(offset, exp.Expr): 3537 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3538 elif offset: 3539 suffix = f"{alias} WITH OFFSET" 3540 else: 3541 suffix = alias 3542 3543 return f"UNNEST({args}){suffix}" 3544 3545 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3546 return "" 3547 3548 def where_sql(self, expression: exp.Where) -> str: 3549 this = self.indent(self.sql(expression, "this")) 3550 return f"{self.seg('WHERE')}{self.sep()}{this}" 3551 3552 def window_sql(self, expression: exp.Window) -> str: 3553 this = self.sql(expression, "this") 3554 partition = self.partition_by_sql(expression) 3555 order = expression.args.get("order") 3556 order = self.order_sql(order, flat=True) if order else "" 3557 spec = self.sql(expression, "spec") 3558 alias = self.sql(expression, "alias") 3559 over = self.sql(expression, "over") or "OVER" 3560 3561 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3562 3563 first = expression.args.get("first") 3564 if first is None: 3565 first = "" 3566 else: 3567 first = "FIRST" if first else "LAST" 3568 3569 if not partition and not order and not spec and alias: 3570 return f"{this} {alias}" 3571 3572 args = self.format_args( 3573 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3574 ) 3575 return f"{this} ({args})" 3576 3577 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3578 partition = self.expressions(expression, key="partition_by", flat=True) 3579 return f"PARTITION BY {partition}" if partition else "" 3580 3581 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3582 kind = self.sql(expression, "kind") 3583 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3584 end = ( 3585 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3586 or "CURRENT ROW" 3587 ) 3588 3589 window_spec = f"{kind} BETWEEN {start} AND {end}" 3590 3591 exclude = self.sql(expression, "exclude") 3592 if exclude: 3593 if self.SUPPORTS_WINDOW_EXCLUDE: 3594 window_spec += f" EXCLUDE {exclude}" 3595 else: 3596 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3597 3598 return window_spec 3599 3600 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3601 this = self.sql(expression, "this") 3602 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3603 return f"{this} WITHIN GROUP ({expression_sql})" 3604 3605 def between_sql(self, expression: exp.Between) -> str: 3606 this = self.sql(expression, "this") 3607 low = self.sql(expression, "low") 3608 high = self.sql(expression, "high") 3609 symmetric = expression.args.get("symmetric") 3610 3611 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3612 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3613 3614 flag = ( 3615 " SYMMETRIC" 3616 if symmetric 3617 else " ASYMMETRIC" 3618 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3619 else "" # silently drop ASYMMETRIC – semantics identical 3620 ) 3621 return f"{this} BETWEEN{flag} {low} AND {high}" 3622 3623 def bracket_offset_expressions( 3624 self, expression: exp.Bracket, index_offset: int | None = None 3625 ) -> list[exp.Expr]: 3626 if expression.args.get("json_access"): 3627 return expression.expressions 3628 3629 return apply_index_offset( 3630 expression.this, 3631 expression.expressions, 3632 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3633 dialect=self.dialect, 3634 ) 3635 3636 def bracket_sql(self, expression: exp.Bracket) -> str: 3637 expressions = self.bracket_offset_expressions(expression) 3638 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3639 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3640 3641 def all_sql(self, expression: exp.All) -> str: 3642 this = self.sql(expression, "this") 3643 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3644 this = self.wrap(this) 3645 return f"ALL {this}" 3646 3647 def any_sql(self, expression: exp.Any) -> str: 3648 this = self.sql(expression, "this") 3649 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3650 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3651 this = self.wrap(this) 3652 return f"ANY{this}" 3653 return f"ANY {this}" 3654 3655 def exists_sql(self, expression: exp.Exists) -> str: 3656 return f"EXISTS{self.wrap(expression)}" 3657 3658 def case_sql(self, expression: exp.Case) -> str: 3659 this = self.sql(expression, "this") 3660 statements = [f"CASE {this}" if this else "CASE"] 3661 3662 for e in expression.args["ifs"]: 3663 statements.append(f"WHEN {self.sql(e, 'this')}") 3664 statements.append(f"THEN {self.sql(e, 'true')}") 3665 3666 default = self.sql(expression, "default") 3667 3668 if default: 3669 statements.append(f"ELSE {default}") 3670 3671 statements.append("END") 3672 3673 if self.pretty and self.too_wide(statements): 3674 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3675 3676 return " ".join(statements) 3677 3678 def constraint_sql(self, expression: exp.Constraint) -> str: 3679 this = self.sql(expression, "this") 3680 expressions = self.expressions(expression, flat=True) 3681 return f"CONSTRAINT {this} {expressions}" 3682 3683 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3684 order = expression.args.get("order") 3685 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3686 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3687 3688 def extract_sql(self, expression: exp.Extract) -> str: 3689 import sqlglot.dialects.dialect 3690 3691 this = ( 3692 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3693 if self.NORMALIZE_EXTRACT_DATE_PARTS 3694 else expression.this 3695 ) 3696 if self.EXTRACT_ALLOWS_QUOTES: 3697 this_sql = self.sql(this) 3698 elif isinstance(this, exp.WeekStart): 3699 this_sql = self.weekstart_name(this) 3700 else: 3701 this_sql = this.name 3702 expression_sql = self.sql(expression, "expression") 3703 3704 return f"EXTRACT({this_sql} FROM {expression_sql})" 3705 3706 def trim_sql(self, expression: exp.Trim) -> str: 3707 trim_type = self.sql(expression, "position") 3708 3709 if trim_type == "LEADING": 3710 func_name = "LTRIM" 3711 elif trim_type == "TRAILING": 3712 func_name = "RTRIM" 3713 else: 3714 func_name = "TRIM" 3715 3716 return self.func(func_name, expression.this, expression.expression) 3717 3718 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3719 args = expression.expressions 3720 if isinstance(expression, exp.ConcatWs): 3721 args = args[1:] # Skip the delimiter 3722 3723 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3724 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3725 3726 concat_coalesce = ( 3727 self.dialect.CONCAT_WS_COALESCE 3728 if isinstance(expression, exp.ConcatWs) 3729 else self.dialect.CONCAT_COALESCE 3730 ) 3731 3732 if not concat_coalesce and expression.args.get("coalesce"): 3733 3734 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3735 if not e.type: 3736 import sqlglot.optimizer.annotate_types 3737 3738 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3739 3740 if e.is_string or e.is_type(exp.DType.ARRAY): 3741 return e 3742 3743 return exp.func("coalesce", e, exp.Literal.string("")) 3744 3745 args = [_wrap_with_coalesce(e) for e in args] 3746 3747 return args 3748 3749 def concat_sql(self, expression: exp.Concat) -> str: 3750 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3751 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3752 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3753 # instead of coalescing them to empty string. 3754 import sqlglot.dialects.dialect 3755 3756 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3757 3758 expressions = self.convert_concat_args(expression) 3759 3760 # Some dialects don't allow a single-argument CONCAT call 3761 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3762 return self.sql(expressions[0]) 3763 3764 return self.func("CONCAT", *expressions) 3765 3766 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3767 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3768 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3769 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3770 all_args = expression.expressions 3771 expression.set("coalesce", True) 3772 return self.sql( 3773 exp.case() 3774 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3775 .else_(expression) 3776 ) 3777 3778 return self.func( 3779 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3780 ) 3781 3782 def check_sql(self, expression: exp.Check) -> str: 3783 this = self.sql(expression, key="this") 3784 return f"CHECK ({this})" 3785 3786 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3787 expressions = self.expressions(expression, flat=True) 3788 expressions = f" ({expressions})" if expressions else "" 3789 reference = self.sql(expression, "reference") 3790 reference = f" {reference}" if reference else "" 3791 delete = self.sql(expression, "delete") 3792 delete = f" ON DELETE {delete}" if delete else "" 3793 update = self.sql(expression, "update") 3794 update = f" ON UPDATE {update}" if update else "" 3795 options = self.expressions(expression, key="options", flat=True, sep=" ") 3796 options = f" {options}" if options else "" 3797 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3798 3799 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3800 this = self.sql(expression, "this") 3801 this = f" {this}" if this else "" 3802 expressions = self.expressions(expression, flat=True) 3803 include = self.sql(expression, "include") 3804 options = self.expressions(expression, key="options", flat=True, sep=" ") 3805 options = f" {options}" if options else "" 3806 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3807 3808 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3809 self.unsupported("TIMESERIES primary key columns are not supported") 3810 return self.sql(expression, "this") 3811 3812 def if_sql(self, expression: exp.If) -> str: 3813 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3814 3815 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3816 if self.MATCH_AGAINST_TABLE_PREFIX: 3817 expressions = [] 3818 for expr in expression.expressions: 3819 if isinstance(expr, exp.Table): 3820 expressions.append(f"TABLE {self.sql(expr)}") 3821 else: 3822 expressions.append(expr) 3823 else: 3824 expressions = expression.expressions 3825 3826 modifier = expression.args.get("modifier") 3827 modifier = f" {modifier}" if modifier else "" 3828 return ( 3829 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3830 ) 3831 3832 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3833 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3834 3835 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3836 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3837 3838 if self.QUOTE_JSON_PATH: 3839 path = self.escape_str(path) 3840 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3841 3842 return path 3843 3844 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3845 if isinstance(expression, exp.JSONPathPart): 3846 transform = self.TRANSFORMS.get(expression.__class__) 3847 if not callable(transform): 3848 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3849 return "" 3850 3851 return transform(self, expression) 3852 3853 if isinstance(expression, int): 3854 return str(expression) 3855 3856 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3857 escaped = expression.replace("'", "\\'") 3858 escaped = f"'{escaped}'" 3859 else: 3860 escaped = expression.replace('"', '\\"') 3861 escaped = f'"{escaped}"' 3862 3863 return escaped 3864 3865 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3866 return f"{self.sql(expression, 'this')} FORMAT JSON" 3867 3868 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3869 # Output the Teradata column FORMAT override. 3870 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3871 this = self.sql(expression, "this") 3872 fmt = self.sql(expression, "format") 3873 return f"{this} (FORMAT {fmt})" 3874 3875 def _jsonobject_sql( 3876 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3877 ) -> str: 3878 null_handling = expression.args.get("null_handling") 3879 null_handling = f" {null_handling}" if null_handling else "" 3880 3881 unique_keys = expression.args.get("unique_keys") 3882 if unique_keys is not None: 3883 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3884 else: 3885 unique_keys = "" 3886 3887 return_type = self.sql(expression, "return_type") 3888 return_type = f" RETURNING {return_type}" if return_type else "" 3889 encoding = self.sql(expression, "encoding") 3890 encoding = f" ENCODING {encoding}" if encoding else "" 3891 3892 if not name: 3893 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3894 3895 return self.func( 3896 name, 3897 *expression.expressions, 3898 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3899 ) 3900 3901 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3902 null_handling = expression.args.get("null_handling") 3903 null_handling = f" {null_handling}" if null_handling else "" 3904 return_type = self.sql(expression, "return_type") 3905 return_type = f" RETURNING {return_type}" if return_type else "" 3906 strict = " STRICT" if expression.args.get("strict") else "" 3907 return self.func( 3908 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3909 ) 3910 3911 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3912 this = self.sql(expression, "this") 3913 order = self.sql(expression, "order") 3914 null_handling = expression.args.get("null_handling") 3915 null_handling = f" {null_handling}" if null_handling else "" 3916 return_type = self.sql(expression, "return_type") 3917 return_type = f" RETURNING {return_type}" if return_type else "" 3918 strict = " STRICT" if expression.args.get("strict") else "" 3919 return self.func( 3920 "JSON_ARRAYAGG", 3921 this, 3922 suffix=f"{order}{null_handling}{return_type}{strict})", 3923 ) 3924 3925 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3926 path = self.sql(expression, "path") 3927 path = f" PATH {path}" if path else "" 3928 nested_schema = self.sql(expression, "nested_schema") 3929 3930 if nested_schema: 3931 return f"NESTED{path} {nested_schema}" 3932 3933 this = self.sql(expression, "this") 3934 kind = self.sql(expression, "kind") 3935 kind = f" {kind}" if kind else "" 3936 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3937 3938 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3939 return f"{this}{kind}{format_json}{path}{ordinality}" 3940 3941 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3942 return self.func("COLUMNS", *expression.expressions) 3943 3944 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3945 this = self.sql(expression, "this") 3946 path = self.sql(expression, "path") 3947 path = f", {path}" if path else "" 3948 error_handling = expression.args.get("error_handling") 3949 error_handling = f" {error_handling}" if error_handling else "" 3950 empty_handling = expression.args.get("empty_handling") 3951 empty_handling = f" {empty_handling}" if empty_handling else "" 3952 schema = self.sql(expression, "schema") 3953 return self.func( 3954 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3955 ) 3956 3957 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3958 this = self.sql(expression, "this") 3959 kind = self.sql(expression, "kind") 3960 path = self.sql(expression, "path") 3961 path = f" {path}" if path else "" 3962 as_json = " AS JSON" if expression.args.get("as_json") else "" 3963 return f"{this} {kind}{path}{as_json}" 3964 3965 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3966 this = self.sql(expression, "this") 3967 path = self.sql(expression, "path") 3968 path = f", {path}" if path else "" 3969 expressions = self.expressions(expression) 3970 with_ = ( 3971 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3972 if expressions 3973 else "" 3974 ) 3975 return f"OPENJSON({this}{path}){with_}" 3976 3977 def in_sql(self, expression: exp.In) -> str: 3978 query = expression.args.get("query") 3979 unnest = expression.args.get("unnest") 3980 field = expression.args.get("field") 3981 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3982 3983 if query: 3984 in_sql = self.sql(query) 3985 elif unnest: 3986 in_sql = self.in_unnest_op(unnest) 3987 elif field: 3988 in_sql = self.sql(field) 3989 else: 3990 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3991 3992 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3993 3994 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3995 return f"(SELECT {self.sql(unnest)})" 3996 3997 def interval_sql(self, expression: exp.Interval) -> str: 3998 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3999 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4000 exp.AutoRefreshProperty, 4001 ) 4002 interval_keyword = "INTERVAL" if include_keyword else "" 4003 unit_expression = expression.args.get("unit") 4004 unit = self.sql(unit_expression) if unit_expression else "" 4005 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4006 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4007 unit = f" {unit}" if unit else "" 4008 4009 if self.SINGLE_STRING_INTERVAL: 4010 this = expression.this.name if expression.this else "" 4011 if this: 4012 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4013 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4014 return f"{interval_keyword}'{this}'{unit}" 4015 return f"{interval_keyword}'{this}{unit}'" 4016 return f"{interval_keyword}{unit}" 4017 4018 this = self.sql(expression, "this") 4019 if this: 4020 if not include_keyword and expression.this.is_string: 4021 this = expression.this.name 4022 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4023 this = f"({this})" 4024 if include_keyword: 4025 this = f" {this}" 4026 4027 return f"{interval_keyword}{this}{unit}" 4028 4029 def return_sql(self, expression: exp.Return) -> str: 4030 return f"RETURN {self.sql(expression, 'this')}" 4031 4032 def reference_sql(self, expression: exp.Reference) -> str: 4033 this = self.sql(expression, "this") 4034 expressions = self.expressions(expression, flat=True) 4035 expressions = f"({expressions})" if expressions else "" 4036 options = self.expressions(expression, key="options", flat=True, sep=" ") 4037 options = f" {options}" if options else "" 4038 return f"REFERENCES {this}{expressions}{options}" 4039 4040 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4041 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4042 parent = expression.parent 4043 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4044 4045 return self.func( 4046 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4047 ) 4048 4049 def paren_sql(self, expression: exp.Paren) -> str: 4050 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4051 return f"({sql}{self.seg(')', sep='')}" 4052 4053 def neg_sql(self, expression: exp.Neg) -> str: 4054 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4055 this_sql = self.sql(expression, "this") 4056 sep = " " if this_sql[0] == "-" else "" 4057 return f"-{sep}{this_sql}" 4058 4059 def not_sql(self, expression: exp.Not) -> str: 4060 return f"NOT {self.sql(expression, 'this')}" 4061 4062 def alias_sql(self, expression: exp.Alias) -> str: 4063 alias = self.sql(expression, "alias") 4064 alias = f" AS {alias}" if alias else "" 4065 return f"{self.sql(expression, 'this')}{alias}" 4066 4067 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4068 alias = expression.args["alias"] 4069 4070 parent = expression.parent 4071 pivot = parent and parent.parent 4072 4073 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4074 identifier_alias = isinstance(alias, exp.Identifier) 4075 literal_alias = isinstance(alias, exp.Literal) 4076 4077 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4078 alias.replace(exp.Literal.string(alias.output_name)) 4079 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4080 alias.replace(exp.to_identifier(alias.output_name)) 4081 4082 return self.alias_sql(expression) 4083 4084 def aliases_sql(self, expression: exp.Aliases) -> str: 4085 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4086 4087 def atindex_sql(self, expression: exp.AtIndex) -> str: 4088 this = self.sql(expression, "this") 4089 index = self.sql(expression, "expression") 4090 return f"{this} AT {index}" 4091 4092 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4093 this = self.sql(expression, "this") 4094 zone = self.sql(expression, "zone") 4095 return f"{this} AT TIME ZONE {zone}" 4096 4097 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4098 this = self.sql(expression, "this") 4099 zone = self.sql(expression, "zone") 4100 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4101 4102 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4103 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4104 4105 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4106 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4107 4108 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4109 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4110 4111 def add_sql(self, expression: exp.Add) -> str: 4112 return self.binary(expression, "+") 4113 4114 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4115 return self.connector_sql(expression, "AND", stack) 4116 4117 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4118 return self.connector_sql(expression, "OR", stack) 4119 4120 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4121 return self.connector_sql(expression, "XOR", stack) 4122 4123 def connector_sql( 4124 self, 4125 expression: exp.Connector, 4126 op: str, 4127 stack: list[str | exp.Expr] | None = None, 4128 ) -> str: 4129 if stack is not None: 4130 stack.append(expression.right) 4131 if expression.comments and self.comments: 4132 op = self.maybe_comment(op, comments=expression.comments) 4133 4134 stack.extend((op, expression.left)) 4135 return op 4136 4137 stack = [expression] 4138 sqls: list[str] = [] 4139 ops = set() 4140 4141 while stack: 4142 node = stack.pop() 4143 if isinstance(node, exp.Connector): 4144 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4145 else: 4146 sql = self.sql(node) 4147 if sqls and sqls[-1] in ops: 4148 sqls[-1] += f" {sql}" 4149 else: 4150 sqls.append(sql) 4151 4152 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4153 return sep.join(sqls) 4154 4155 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4156 return self.binary(expression, "&") 4157 4158 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4159 return self.binary(expression, "<<") 4160 4161 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4162 return f"~{self.sql(expression, 'this')}" 4163 4164 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4165 return self.binary(expression, "|") 4166 4167 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4168 return self.binary(expression, ">>") 4169 4170 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4171 return self.binary(expression, "^") 4172 4173 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4174 format_sql = self.sql(expression, "format") 4175 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4176 to_sql = self.sql(expression, "to") 4177 to_sql = f" {to_sql}" if to_sql else "" 4178 action = self.sql(expression, "action") 4179 action = f" {action}" if action else "" 4180 default = self.sql(expression, "default") 4181 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4182 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4183 4184 # Base implementation that excludes safe, zone, and target_type metadata args 4185 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4186 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4187 4188 # Base implementation that excludes the safe and default_year metadata args 4189 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4190 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4191 4192 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4193 return self.func( 4194 "PARSE_DATETIME", 4195 expression.this, 4196 expression.args.get("format"), 4197 expression.args.get("zone"), 4198 ) 4199 4200 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4201 zone = self.sql(expression, "this") 4202 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4203 4204 def collate_sql(self, expression: exp.Collate) -> str: 4205 if self.COLLATE_IS_FUNC: 4206 return self.function_fallback_sql(expression) 4207 return self.binary(expression, "COLLATE") 4208 4209 def command_sql(self, expression: exp.Command) -> str: 4210 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4211 4212 def comment_sql(self, expression: exp.Comment) -> str: 4213 this = self.sql(expression, "this") 4214 kind = expression.args["kind"] 4215 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4216 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4217 expression_sql = self.sql(expression, "expression") 4218 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4219 4220 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4221 this = self.sql(expression, "this") 4222 delete = " DELETE" if expression.args.get("delete") else "" 4223 recompress = self.sql(expression, "recompress") 4224 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4225 to_disk = self.sql(expression, "to_disk") 4226 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4227 to_volume = self.sql(expression, "to_volume") 4228 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4229 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4230 4231 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4232 where = self.sql(expression, "where") 4233 group = self.sql(expression, "group") 4234 aggregates = self.expressions(expression, key="aggregates") 4235 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4236 4237 if not (where or group or aggregates) and len(expression.expressions) == 1: 4238 return f"TTL {self.expressions(expression, flat=True)}" 4239 4240 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4241 4242 def transaction_sql(self, expression: exp.Transaction) -> str: 4243 modes = self.expressions(expression, key="modes") 4244 modes = f" {modes}" if modes else "" 4245 return f"BEGIN{modes}" 4246 4247 def commit_sql(self, expression: exp.Commit) -> str: 4248 chain = expression.args.get("chain") 4249 if chain is not None: 4250 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4251 4252 return f"COMMIT{chain or ''}" 4253 4254 def rollback_sql(self, expression: exp.Rollback) -> str: 4255 savepoint = expression.args.get("savepoint") 4256 savepoint = f" TO {savepoint}" if savepoint else "" 4257 return f"ROLLBACK{savepoint}" 4258 4259 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4260 this = self.sql(expression, "this") 4261 4262 exists = "" 4263 if expression.args.get("exists"): 4264 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4265 exists = " IF EXISTS" 4266 else: 4267 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4268 4269 dtype = self.sql(expression, "dtype") 4270 if dtype: 4271 collate = self.sql(expression, "collate") 4272 collate = f" COLLATE {collate}" if collate else "" 4273 using = self.sql(expression, "using") 4274 using = f" USING {using}" if using else "" 4275 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4276 null_constraint = self._alter_column_null_constraint_sql(expression) 4277 4278 return ( 4279 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4280 f"{collate}{using}{null_constraint}" 4281 ) 4282 4283 default = self.sql(expression, "default") 4284 if default: 4285 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4286 4287 comment = self.sql(expression, "comment") 4288 if comment: 4289 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4290 4291 visible = expression.args.get("visible") 4292 if visible: 4293 return f"ALTER COLUMN{exists} {this} SET {visible}" 4294 4295 allow_null = expression.args.get("allow_null") 4296 drop = expression.args.get("drop") 4297 4298 if not drop and not allow_null: 4299 self.unsupported("Unsupported ALTER COLUMN syntax") 4300 4301 if allow_null is not None: 4302 keyword = "DROP" if drop else "SET" 4303 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4304 4305 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4306 4307 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4308 allow_null = expression.args.get("allow_null") 4309 if allow_null is None: 4310 return "" 4311 4312 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4313 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4314 return "" 4315 4316 return " NULL" if allow_null else " NOT NULL" 4317 4318 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4319 this = self.sql(expression, "this") 4320 rename_from = self.sql(expression, "rename_from") 4321 if rename_from: 4322 if not self.SUPPORTS_CHANGE_COLUMN: 4323 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4324 return f"CHANGE COLUMN {rename_from} {this}" 4325 if not self.SUPPORTS_MODIFY_COLUMN: 4326 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4327 return f"MODIFY COLUMN {this}" 4328 4329 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4330 this = self.sql(expression, "this") 4331 4332 visible = expression.args.get("visible") 4333 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4334 4335 return f"ALTER INDEX {this} {visible_sql}" 4336 4337 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4338 this = self.sql(expression, "this") 4339 if not isinstance(expression.this, exp.Var): 4340 this = f"KEY DISTKEY {this}" 4341 return f"ALTER DISTSTYLE {this}" 4342 4343 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4344 compound = " COMPOUND" if expression.args.get("compound") else "" 4345 this = self.sql(expression, "this") 4346 expressions = self.expressions(expression, flat=True) 4347 expressions = f"({expressions})" if expressions else "" 4348 return f"ALTER{compound} SORTKEY {this or expressions}" 4349 4350 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4351 if not self.RENAME_TABLE_WITH_DB: 4352 # Remove db from tables 4353 expression = expression.transform( 4354 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4355 ).assert_is(exp.AlterRename) 4356 this = self.sql(expression, "this") 4357 to_kw = " TO" if include_to else "" 4358 return f"RENAME{to_kw} {this}" 4359 4360 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4361 exists = " IF EXISTS" if expression.args.get("exists") else "" 4362 old_column = self.sql(expression, "this") 4363 new_column = self.sql(expression, "to") 4364 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4365 4366 def alterset_sql(self, expression: exp.AlterSet) -> str: 4367 exprs = self.expressions(expression, flat=True) 4368 if self.ALTER_SET_WRAPPED: 4369 exprs = f"({exprs})" 4370 4371 return f"SET {exprs}" 4372 4373 def alter_sql(self, expression: exp.Alter) -> str: 4374 actions = expression.args["actions"] 4375 4376 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4377 actions[0], exp.ColumnDef 4378 ): 4379 actions_sql = self.expressions(expression, key="actions", flat=True) 4380 actions_sql = f"ADD {actions_sql}" 4381 else: 4382 actions_list = [] 4383 for action in actions: 4384 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4385 action_sql = self.add_column_sql(action) 4386 else: 4387 action_sql = self.sql(action) 4388 if isinstance(action, exp.Query): 4389 action_sql = f"AS {action_sql}" 4390 4391 actions_list.append(action_sql) 4392 4393 actions_sql = self.format_args(*actions_list).lstrip("\n") 4394 4395 iceberg = ( 4396 "ICEBERG " 4397 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4398 else "" 4399 ) 4400 exists = " IF EXISTS" if expression.args.get("exists") else "" 4401 on_cluster = self.sql(expression, "cluster") 4402 on_cluster = f" {on_cluster}" if on_cluster else "" 4403 only = " ONLY" if expression.args.get("only") else "" 4404 options = self.expressions(expression, key="options") 4405 options = f", {options}" if options else "" 4406 kind = self.sql(expression, "kind") 4407 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4408 check = " WITH CHECK" if expression.args.get("check") else "" 4409 cascade = ( 4410 " CASCADE" 4411 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4412 else "" 4413 ) 4414 this = self.sql(expression, "this") 4415 this = f" {this}" if this else "" 4416 4417 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4418 4419 def altersession_sql(self, expression: exp.AlterSession) -> str: 4420 items_sql = self.expressions(expression, flat=True) 4421 keyword = "UNSET" if expression.args.get("unset") else "SET" 4422 return f"{keyword} {items_sql}" 4423 4424 def add_column_sql(self, expression: exp.Expr) -> str: 4425 sql = self.sql(expression) 4426 if isinstance(expression, exp.Schema): 4427 column_text = " COLUMNS" 4428 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4429 column_text = " COLUMN" 4430 else: 4431 column_text = "" 4432 4433 return f"ADD{column_text} {sql}" 4434 4435 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4436 expressions = self.expressions(expression) 4437 exists = " IF EXISTS " if expression.args.get("exists") else " " 4438 return f"DROP{exists}{expressions}" 4439 4440 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4441 return "DROP PRIMARY KEY" 4442 4443 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4444 return f"ADD {self.expressions(expression, indent=False)}" 4445 4446 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4447 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4448 location = self.sql(expression, "location") 4449 location = f" {location}" if location else "" 4450 return f"ADD {exists}{self.sql(expression.this)}{location}" 4451 4452 def distinct_sql(self, expression: exp.Distinct) -> str: 4453 this = self.expressions(expression, flat=True) 4454 4455 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4456 case = exp.case() 4457 for arg in expression.expressions: 4458 case = case.when(arg.is_(exp.null()), exp.null()) 4459 this = self.sql(case.else_(f"({this})")) 4460 4461 this = f" {this}" if this else "" 4462 4463 on = self.sql(expression, "on") 4464 on = f" ON {on}" if on else "" 4465 return f"DISTINCT{this}{on}" 4466 4467 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4468 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4469 4470 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4471 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4472 4473 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4474 this_sql = self.sql(expression, "this") 4475 expression_sql = self.sql(expression, "expression") 4476 kind = "MAX" if expression.args.get("max") else "MIN" 4477 return f"{this_sql} HAVING {kind} {expression_sql}" 4478 4479 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4480 return self.sql( 4481 exp.Cast( 4482 this=exp.Div(this=expression.this, expression=expression.expression), 4483 to=exp.DataType(this=exp.DType.INT), 4484 ) 4485 ) 4486 4487 def dpipe_sql(self, expression: exp.DPipe) -> str: 4488 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4489 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4490 return self.binary(expression, "||") 4491 4492 def div_sql(self, expression: exp.Div) -> str: 4493 l, r = expression.left, expression.right 4494 4495 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4496 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4497 4498 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4499 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4500 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4501 4502 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4503 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4504 return self.sql( 4505 exp.cast( 4506 l / r, 4507 to=exp.DType.BIGINT, 4508 ) 4509 ) 4510 4511 return self.binary(expression, "/") 4512 4513 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4514 n = exp._wrap(expression.this, exp.Binary) 4515 d = exp._wrap(expression.expression, exp.Binary) 4516 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4517 4518 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4519 return self.binary(expression, "OVERLAPS") 4520 4521 def distance_sql(self, expression: exp.Distance) -> str: 4522 return self.binary(expression, "<->") 4523 4524 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4525 return self.binary(expression, "<<->>") 4526 4527 def dot_sql(self, expression: exp.Dot) -> str: 4528 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4529 4530 def eq_sql(self, expression: exp.EQ) -> str: 4531 return self.binary(expression, "=") 4532 4533 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4534 return self.binary(expression, ":=") 4535 4536 def escape_sql(self, expression: exp.Escape) -> str: 4537 this = expression.this 4538 if ( 4539 isinstance(this, (exp.Like, exp.ILike)) 4540 and isinstance(this.expression, (exp.All, exp.Any)) 4541 and not self.SUPPORTS_LIKE_QUANTIFIERS 4542 ): 4543 return self._like_sql(this, escape=expression) 4544 return self.binary(expression, "ESCAPE") 4545 4546 def glob_sql(self, expression: exp.Glob) -> str: 4547 return self.binary(expression, "GLOB") 4548 4549 def gt_sql(self, expression: exp.GT) -> str: 4550 return self.binary(expression, ">") 4551 4552 def gte_sql(self, expression: exp.GTE) -> str: 4553 return self.binary(expression, ">=") 4554 4555 def is_sql(self, expression: exp.Is) -> str: 4556 negate = expression.args.get("negate") 4557 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4558 positive = bool(expression.expression.this) != bool(negate) 4559 return self.sql(expression.this if positive else exp.not_(expression.this)) 4560 return self.binary(expression, "IS NOT" if negate else "IS") 4561 4562 def _like_sql( 4563 self, 4564 expression: exp.Like | exp.ILike, 4565 escape: exp.Escape | None = None, 4566 ) -> str: 4567 this = expression.this 4568 rhs = expression.expression 4569 4570 if isinstance(expression, exp.Like): 4571 exp_class: type[exp.Like | exp.ILike] = exp.Like 4572 op = "LIKE" 4573 else: 4574 exp_class = exp.ILike 4575 op = "ILIKE" 4576 4577 if expression.args.get("negate"): 4578 op = f"NOT {op}" 4579 4580 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4581 exprs = rhs.this.unnest() 4582 4583 if isinstance(exprs, exp.Tuple): 4584 exprs = exprs.expressions 4585 else: 4586 exprs = [exprs] 4587 4588 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4589 4590 def _make_like(expr: exp.Expression) -> exp.Expression: 4591 like: exp.Expression = exp_class( 4592 this=this, expression=expr, negate=expression.args.get("negate") 4593 ) 4594 if escape: 4595 like = exp.Escape(this=like, expression=escape.expression.copy()) 4596 return like 4597 4598 like_expr: exp.Expr = _make_like(exprs[0]) 4599 for expr in exprs[1:]: 4600 like_expr = connective(like_expr, _make_like(expr), copy=False) 4601 4602 parent = escape.parent if escape else expression.parent 4603 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4604 parent, exp.Condition 4605 ): 4606 like_expr = exp.paren(like_expr, copy=False) 4607 4608 return self.sql(like_expr) 4609 4610 return self.binary(expression, op) 4611 4612 def like_sql(self, expression: exp.Like) -> str: 4613 return self._like_sql(expression) 4614 4615 def ilike_sql(self, expression: exp.ILike) -> str: 4616 return self._like_sql(expression) 4617 4618 def match_sql(self, expression: exp.Match) -> str: 4619 return self.binary(expression, "MATCH") 4620 4621 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4622 return self.binary(expression, "SIMILAR TO") 4623 4624 def lt_sql(self, expression: exp.LT) -> str: 4625 return self.binary(expression, "<") 4626 4627 def lte_sql(self, expression: exp.LTE) -> str: 4628 return self.binary(expression, "<=") 4629 4630 def mod_sql(self, expression: exp.Mod) -> str: 4631 this = self.sql(expression, "this") 4632 expr = self.sql(expression, "expression") 4633 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4634 4635 parent = expression.parent 4636 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4637 return f"({sql})" 4638 4639 return sql 4640 4641 def mul_sql(self, expression: exp.Mul) -> str: 4642 return self.binary(expression, "*") 4643 4644 def neq_sql(self, expression: exp.NEQ) -> str: 4645 return self.binary(expression, "<>") 4646 4647 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4648 return self.binary(expression, "IS NOT DISTINCT FROM") 4649 4650 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4651 return self.binary(expression, "IS DISTINCT FROM") 4652 4653 def sub_sql(self, expression: exp.Sub) -> str: 4654 return self.binary(expression, "-") 4655 4656 def trycast_sql(self, expression: exp.TryCast) -> str: 4657 return self.cast_sql(expression, safe_prefix="TRY_") 4658 4659 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4660 return self.cast_sql(expression) 4661 4662 def try_sql(self, expression: exp.Try) -> str: 4663 if not self.TRY_SUPPORTED: 4664 self.unsupported("Unsupported TRY function") 4665 return self.sql(expression, "this") 4666 4667 return self.func("TRY", expression.this) 4668 4669 def log_sql(self, expression: exp.Log) -> str: 4670 this = expression.this 4671 expr = expression.expression 4672 4673 if self.dialect.LOG_BASE_FIRST is False: 4674 this, expr = expr, this 4675 elif self.dialect.LOG_BASE_FIRST is None and expr: 4676 if this.name in ("2", "10"): 4677 return self.func(f"LOG{this.name}", expr) 4678 4679 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4680 4681 return self.func("LOG", this, expr) 4682 4683 def use_sql(self, expression: exp.Use) -> str: 4684 kind = self.sql(expression, "kind") 4685 kind = f" {kind}" if kind else "" 4686 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4687 this = f" {this}" if this else "" 4688 return f"USE{kind}{this}" 4689 4690 def binary(self, expression: exp.Binary, op: str) -> str: 4691 sqls: list[str] = [] 4692 stack: list[None | str | exp.Expr] = [expression] 4693 binary_type = type(expression) 4694 4695 while stack: 4696 node = stack.pop() 4697 4698 if type(node) is binary_type: 4699 op_func = node.args.get("operator") 4700 if op_func: 4701 op = f"OPERATOR({self.sql(op_func)})" 4702 4703 stack.append(node.args.get("expression")) 4704 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4705 stack.append(node.args.get("this")) 4706 else: 4707 sqls.append(self.sql(node)) 4708 4709 return "".join(sqls) 4710 4711 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4712 to_clause = self.sql(expression, "to") 4713 if to_clause: 4714 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4715 4716 return self.function_fallback_sql(expression) 4717 4718 def function_fallback_sql(self, expression: exp.Func) -> str: 4719 args = [] 4720 4721 for key in expression.arg_types: 4722 arg_value = expression.args.get(key) 4723 4724 if isinstance(arg_value, list): 4725 for value in arg_value: 4726 args.append(value) 4727 elif arg_value is not None: 4728 args.append(arg_value) 4729 4730 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4731 name = expression.meta_get("name") or expression.sql_name() 4732 else: 4733 name = expression.sql_name() 4734 4735 return self.func(name, *args) 4736 4737 def func( 4738 self, 4739 name: str, 4740 *args: t.Any, 4741 prefix: str = "(", 4742 suffix: str = ")", 4743 normalize: bool = True, 4744 ) -> str: 4745 name = self.normalize_func(name) if normalize else name 4746 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4747 4748 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4749 arg_sqls = tuple( 4750 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4751 ) 4752 if self.pretty and self.too_wide(arg_sqls): 4753 return self.indent( 4754 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4755 ) 4756 return sep.join(arg_sqls) 4757 4758 def too_wide(self, args: t.Iterable) -> bool: 4759 return sum(len(arg) for arg in args) > self.max_text_width 4760 4761 def format_time( 4762 self, 4763 expression: exp.Expr, 4764 inverse_time_mapping: dict[str, str] | None = None, 4765 inverse_time_trie: dict | None = None, 4766 ) -> str | None: 4767 return format_time( 4768 self.sql(expression, "format"), 4769 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4770 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4771 ) 4772 4773 def expressions( 4774 self, 4775 expression: exp.Expr | None = None, 4776 key: str | None = None, 4777 sqls: t.Collection[str | exp.Expr] | None = None, 4778 flat: bool = False, 4779 indent: bool = True, 4780 skip_first: bool = False, 4781 skip_last: bool = False, 4782 sep: str = ", ", 4783 prefix: str = "", 4784 dynamic: bool = False, 4785 new_line: bool = False, 4786 ) -> str: 4787 expressions = expression.args.get(key or "expressions") if expression else sqls 4788 4789 if not expressions: 4790 return "" 4791 4792 if flat: 4793 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4794 4795 num_sqls = len(expressions) 4796 result_sqls = [] 4797 4798 for i, e in enumerate(expressions): 4799 sql = self.sql(e, comment=False) 4800 if not sql: 4801 continue 4802 4803 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4804 4805 if self.pretty: 4806 if self.leading_comma: 4807 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4808 else: 4809 result_sqls.append( 4810 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4811 ) 4812 else: 4813 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4814 4815 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4816 if new_line: 4817 result_sqls.insert(0, "") 4818 result_sqls.append("") 4819 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4820 else: 4821 result_sql = "".join(result_sqls) 4822 4823 return ( 4824 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4825 if indent 4826 else result_sql 4827 ) 4828 4829 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4830 flat = flat or isinstance(expression.parent, exp.Properties) 4831 expressions_sql = self.expressions(expression, flat=flat) 4832 if flat: 4833 return f"{op} {expressions_sql}" 4834 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4835 4836 def naked_property(self, expression: exp.Property) -> str: 4837 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4838 if not property_name: 4839 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4840 return f"{property_name} {self.sql(expression, 'this')}" 4841 4842 def tag_sql(self, expression: exp.Tag) -> str: 4843 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4844 4845 def token_sql(self, token_type: TokenType) -> str: 4846 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4847 4848 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4849 this = self.sql(expression, "this") 4850 expressions = self.no_identify(self.expressions, expression) 4851 expressions = ( 4852 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4853 ) 4854 return f"{this}{expressions}" if expressions.strip() != "" else this 4855 4856 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4857 return self.expressions(expression, flat=True) 4858 4859 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4860 params = self.no_identify(self.expressions, expression, flat=True) 4861 body = self.sql(expression, "this") 4862 prefix = "TABLE " if expression.args.get("is_table") else "" 4863 return f"({params}) AS {prefix}{body}" 4864 4865 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4866 this = self.sql(expression, "this") 4867 expressions = self.expressions(expression, flat=True) 4868 return f"{this}({expressions})" 4869 4870 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4871 return self.binary(expression, "=>") 4872 4873 def when_sql(self, expression: exp.When) -> str: 4874 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4875 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4876 condition = self.sql(expression, "condition") 4877 condition = f" AND {condition}" if condition else "" 4878 4879 then_expression = expression.args.get("then") 4880 if isinstance(then_expression, exp.Insert): 4881 this = self.sql(then_expression, "this") 4882 this = f"INSERT {this}" if this else "INSERT" 4883 then = self.sql(then_expression, "expression") 4884 then = f"{this} VALUES {then}" if then else this 4885 elif isinstance(then_expression, exp.Update): 4886 if isinstance(then_expression.args.get("expressions"), exp.Star): 4887 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4888 else: 4889 expressions_sql = self.expressions(then_expression) 4890 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4891 else: 4892 then = self.sql(then_expression) 4893 4894 if isinstance(then_expression, (exp.Insert, exp.Update)): 4895 where = self.sql(then_expression, "where") 4896 if where and not self.SUPPORTS_MERGE_WHERE: 4897 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4898 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4899 where = "" 4900 then = f"{then}{where}" 4901 return f"WHEN {matched}{source}{condition} THEN {then}" 4902 4903 def whens_sql(self, expression: exp.Whens) -> str: 4904 return self.expressions(expression, sep=" ", indent=False) 4905 4906 def merge_sql(self, expression: exp.Merge) -> str: 4907 table = expression.this 4908 table_alias = "" 4909 4910 hints = table.args.get("hints") 4911 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4912 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4913 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4914 4915 this = self.sql(table) 4916 using = f"USING {self.sql(expression, 'using')}" 4917 whens = self.sql(expression, "whens") 4918 4919 on = self.sql(expression, "on") 4920 on = f"ON {on}" if on else "" 4921 4922 if not on: 4923 on = self.expressions(expression, key="using_cond") 4924 on = f"USING ({on})" if on else "" 4925 4926 returning = self.sql(expression, "returning") 4927 if returning: 4928 whens = f"{whens}{returning}" 4929 4930 sep = self.sep() 4931 4932 return self.prepend_ctes( 4933 expression, 4934 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4935 ) 4936 4937 @unsupported_args("format") 4938 def tochar_sql(self, expression: exp.ToChar) -> str: 4939 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4940 4941 @unsupported_args("default") 4942 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4943 if not self.SUPPORTS_TO_NUMBER: 4944 self.unsupported("Unsupported TO_NUMBER function") 4945 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4946 4947 fmt = expression.args.get("format") 4948 if not fmt: 4949 self.unsupported("Conversion format is required for TO_NUMBER") 4950 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4951 4952 return self.func("TO_NUMBER", expression.this, fmt) 4953 4954 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4955 this = self.sql(expression, "this") 4956 kind = self.sql(expression, "kind") 4957 settings_sql = self.expressions(expression, key="settings", sep=" ") 4958 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4959 return f"{this}({kind}{args})" 4960 4961 def dictrange_sql(self, expression: exp.DictRange) -> str: 4962 this = self.sql(expression, "this") 4963 max = self.sql(expression, "max") 4964 min = self.sql(expression, "min") 4965 return f"{this}(MIN {min} MAX {max})" 4966 4967 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4968 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4969 4970 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4971 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4972 4973 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4974 def uniquekeyproperty_sql( 4975 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4976 ) -> str: 4977 return f"{prefix} ({self.expressions(expression, flat=True)})" 4978 4979 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4980 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4981 expressions = self.expressions(expression, flat=True) 4982 expressions = f" {self.wrap(expressions)}" if expressions else "" 4983 buckets = self.sql(expression, "buckets") 4984 kind = self.sql(expression, "kind") 4985 buckets = f" BUCKETS {buckets}" if buckets else "" 4986 order = self.sql(expression, "order") 4987 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4988 4989 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4990 return "" 4991 4992 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4993 expressions = self.expressions(expression, key="expressions", flat=True) 4994 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4995 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4996 buckets = self.sql(expression, "buckets") 4997 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4998 4999 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5000 this = self.sql(expression, "this") 5001 having = self.sql(expression, "having") 5002 5003 if having: 5004 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5005 5006 return self.func("ANY_VALUE", this) 5007 5008 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5009 transform = self.func("TRANSFORM", *expression.expressions) 5010 row_format_before = self.sql(expression, "row_format_before") 5011 row_format_before = f" {row_format_before}" if row_format_before else "" 5012 record_writer = self.sql(expression, "record_writer") 5013 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5014 using = f" USING {self.sql(expression, 'command_script')}" 5015 schema = self.sql(expression, "schema") 5016 schema = f" AS {schema}" if schema else "" 5017 row_format_after = self.sql(expression, "row_format_after") 5018 row_format_after = f" {row_format_after}" if row_format_after else "" 5019 record_reader = self.sql(expression, "record_reader") 5020 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5021 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5022 5023 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5024 key_block_size = self.sql(expression, "key_block_size") 5025 if key_block_size: 5026 return f"KEY_BLOCK_SIZE = {key_block_size}" 5027 5028 using = self.sql(expression, "using") 5029 if using: 5030 return f"USING {using}" 5031 5032 parser = self.sql(expression, "parser") 5033 if parser: 5034 return f"WITH PARSER {parser}" 5035 5036 comment = self.sql(expression, "comment") 5037 if comment: 5038 return f"COMMENT {comment}" 5039 5040 visible = expression.args.get("visible") 5041 if visible is not None: 5042 return "VISIBLE" if visible else "INVISIBLE" 5043 5044 engine_attr = self.sql(expression, "engine_attr") 5045 if engine_attr: 5046 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5047 5048 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5049 if secondary_engine_attr: 5050 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5051 5052 self.unsupported("Unsupported index constraint option.") 5053 return "" 5054 5055 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5056 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5057 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5058 5059 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5060 kind = self.sql(expression, "kind") 5061 kind = f"{kind} INDEX" if kind else "INDEX" 5062 this = self.sql(expression, "this") 5063 this = f" {this}" if this else "" 5064 index_type = self.sql(expression, "index_type") 5065 index_type = f" USING {index_type}" if index_type else "" 5066 expressions = self.expressions(expression, flat=True) 5067 expressions = f" ({expressions})" if expressions else "" 5068 options = self.expressions(expression, key="options", sep=" ") 5069 options = f" {options}" if options else "" 5070 return f"{kind}{this}{index_type}{expressions}{options}" 5071 5072 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5073 if self.NVL2_SUPPORTED: 5074 return self.function_fallback_sql(expression) 5075 5076 case = exp.Case().when( 5077 expression.this.is_(exp.null()).not_(copy=False), 5078 expression.args["true"], 5079 copy=False, 5080 ) 5081 else_cond = expression.args.get("false") 5082 if else_cond: 5083 case.else_(else_cond, copy=False) 5084 5085 return self.sql(case) 5086 5087 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5088 if expression.args.get("from_first") is False: 5089 self.unsupported("NTH_VALUE FROM LAST is not supported") 5090 5091 return self.function_fallback_sql(expression) 5092 5093 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5094 this = self.sql(expression, "this") 5095 expr = self.sql(expression, "expression") 5096 position = self.sql(expression, "position") 5097 position = f", {position}" if position else "" 5098 iterator = self.sql(expression, "iterator") 5099 condition = self.sql(expression, "condition") 5100 condition = f" IF {condition}" if condition else "" 5101 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5102 5103 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5104 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5105 5106 def opclass_sql(self, expression: exp.Opclass) -> str: 5107 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5108 5109 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5110 model = self.sql(expression, "this") 5111 model = f"MODEL {model}" 5112 expr = expression.expression 5113 if expr: 5114 expr_sql = self.sql(expression, "expression") 5115 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5116 else: 5117 expr_sql = None 5118 5119 parameters = self.sql(expression, "params_struct") or None 5120 5121 return self.func(name, model, expr_sql, parameters) 5122 5123 def predict_sql(self, expression: exp.Predict) -> str: 5124 return self._ml_sql(expression, "PREDICT") 5125 5126 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5127 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5128 return self._ml_sql(expression, name) 5129 5130 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5131 return self._ml_sql(expression, "GENERATE_TEXT") 5132 5133 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5134 return self._ml_sql(expression, "GENERATE_TABLE") 5135 5136 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5137 return self._ml_sql(expression, "GENERATE_BOOL") 5138 5139 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5140 return self._ml_sql(expression, "GENERATE_INT") 5141 5142 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5143 return self._ml_sql(expression, "GENERATE_DOUBLE") 5144 5145 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5146 return self._ml_sql(expression, "TRANSLATE") 5147 5148 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5149 return self._ml_sql(expression, "FORECAST") 5150 5151 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5152 this_sql = self.sql(expression, "this") 5153 if isinstance(expression.this, exp.Table): 5154 this_sql = f"TABLE {this_sql}" 5155 5156 return self.func( 5157 "FORECAST", 5158 this_sql, 5159 expression.args.get("data_col"), 5160 expression.args.get("timestamp_col"), 5161 expression.args.get("model"), 5162 expression.args.get("id_cols"), 5163 expression.args.get("horizon"), 5164 expression.args.get("forecast_end_timestamp"), 5165 expression.args.get("confidence_level"), 5166 expression.args.get("output_historical_time_series"), 5167 expression.args.get("context_window"), 5168 ) 5169 5170 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> 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 "FEATURES_AT_TIME", 5177 this_sql, 5178 expression.args.get("time"), 5179 expression.args.get("num_rows"), 5180 expression.args.get("ignore_feature_nulls"), 5181 ) 5182 5183 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5184 this_sql = self.sql(expression, "this") 5185 if isinstance(expression.this, exp.Table): 5186 this_sql = f"TABLE {this_sql}" 5187 5188 query_table = self.sql(expression, "query_table") 5189 if isinstance(expression.args["query_table"], exp.Table): 5190 query_table = f"TABLE {query_table}" 5191 5192 return self.func( 5193 "VECTOR_SEARCH", 5194 this_sql, 5195 expression.args.get("column_to_search"), 5196 query_table, 5197 expression.args.get("query_column_to_search"), 5198 expression.args.get("top_k"), 5199 expression.args.get("distance_type"), 5200 expression.args.get("options"), 5201 ) 5202 5203 def forin_sql(self, expression: exp.ForIn) -> str: 5204 this = self.sql(expression, "this") 5205 expression_sql = self.sql(expression, "expression") 5206 return f"FOR {this} DO {expression_sql}" 5207 5208 def refresh_sql(self, expression: exp.Refresh) -> str: 5209 this = self.sql(expression, "this") 5210 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5211 return f"REFRESH {kind}{this}" 5212 5213 def toarray_sql(self, expression: exp.ToArray) -> str: 5214 arg = expression.this 5215 if not arg.type: 5216 import sqlglot.optimizer.annotate_types 5217 5218 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5219 5220 if arg.is_type(exp.DType.ARRAY): 5221 return self.sql(arg) 5222 5223 cond_for_null = arg.is_(exp.null()) 5224 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5225 5226 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5227 this = expression.this 5228 time_format = self.format_time(expression) 5229 5230 if time_format: 5231 return self.sql( 5232 exp.cast( 5233 exp.StrToTime(this=this, format=expression.args["format"]), 5234 exp.DType.TIME, 5235 ) 5236 ) 5237 5238 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5239 return self.sql(this) 5240 5241 return self.sql(exp.cast(this, exp.DType.TIME)) 5242 5243 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5244 this = expression.this 5245 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5246 return self.sql(this) 5247 5248 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5249 5250 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5251 this = expression.this 5252 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5253 return self.sql(this) 5254 5255 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5256 5257 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5258 this = expression.this 5259 time_format = self.format_time(expression) 5260 safe = expression.args.get("safe") 5261 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5262 return self.sql( 5263 exp.cast( 5264 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5265 exp.DType.DATE, 5266 ) 5267 ) 5268 5269 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5270 return self.sql(this) 5271 5272 if safe: 5273 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5274 5275 return self.sql(exp.cast(this, exp.DType.DATE)) 5276 5277 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5278 return self.sql( 5279 exp.func( 5280 "DATEDIFF", 5281 expression.this, 5282 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5283 "day", 5284 ) 5285 ) 5286 5287 def lastday_sql(self, expression: exp.LastDay) -> str: 5288 if self.LAST_DAY_SUPPORTS_DATE_PART: 5289 return self.function_fallback_sql(expression) 5290 5291 unit = expression.args.get("unit") 5292 if unit and unit.name.upper() != "MONTH": 5293 self.unsupported("Date parts are not supported in LAST_DAY.") 5294 5295 return self.func("LAST_DAY", expression.this) 5296 5297 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5298 import sqlglot.dialects.dialect 5299 5300 return self.func( 5301 "DATE_ADD", 5302 expression.this, 5303 expression.expression, 5304 sqlglot.dialects.dialect.unit_to_str(expression), 5305 ) 5306 5307 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5308 if self.CAN_IMPLEMENT_ARRAY_ANY: 5309 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5310 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5311 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5312 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5313 5314 import sqlglot.dialects.dialect 5315 5316 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5317 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5318 self.unsupported("ARRAY_ANY is unsupported") 5319 5320 return self.function_fallback_sql(expression) 5321 5322 def struct_sql(self, expression: exp.Struct) -> str: 5323 expression.set( 5324 "expressions", 5325 [ 5326 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5327 if isinstance(e, exp.PropertyEQ) 5328 else e 5329 for e in expression.expressions 5330 ], 5331 ) 5332 5333 return self.function_fallback_sql(expression) 5334 5335 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5336 low = self.sql(expression, "this") 5337 high = self.sql(expression, "expression") 5338 5339 return f"{low} TO {high}" 5340 5341 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5342 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5343 tables = f" {self.expressions(expression)}" 5344 5345 exists = " IF EXISTS" if expression.args.get("exists") else "" 5346 5347 on_cluster = self.sql(expression, "cluster") 5348 on_cluster = f" {on_cluster}" if on_cluster else "" 5349 5350 identity = self.sql(expression, "identity") 5351 identity = f" {identity} IDENTITY" if identity else "" 5352 5353 option = self.sql(expression, "option") 5354 option = f" {option}" if option else "" 5355 5356 partition = self.sql(expression, "partition") 5357 partition = f" {partition}" if partition else "" 5358 5359 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5360 5361 # This transpiles T-SQL's CONVERT function 5362 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5363 def convert_sql(self, expression: exp.Convert) -> str: 5364 to = expression.this 5365 value = expression.expression 5366 style = expression.args.get("style") 5367 safe = expression.args.get("safe") 5368 strict = expression.args.get("strict") 5369 5370 if not to or not value: 5371 return "" 5372 5373 # Retrieve length of datatype and override to default if not specified 5374 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5375 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5376 5377 transformed: exp.Expr | None = None 5378 cast = exp.Cast if strict else exp.TryCast 5379 5380 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5381 if isinstance(style, exp.Literal) and style.is_int: 5382 import sqlglot.dialects.tsql 5383 5384 style_value = style.name 5385 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5386 if not converted_style: 5387 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5388 5389 fmt = exp.Literal.string(converted_style) 5390 5391 if to.this == exp.DType.DATE: 5392 transformed = exp.StrToDate(this=value, format=fmt) 5393 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5394 transformed = exp.StrToTime(this=value, format=fmt) 5395 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5396 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5397 elif to.this == exp.DType.TEXT: 5398 transformed = exp.TimeToStr(this=value, format=fmt) 5399 5400 if not transformed: 5401 transformed = cast(this=value, to=to, safe=safe) 5402 5403 return self.sql(transformed) 5404 5405 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5406 this = expression.this 5407 if isinstance(this, exp.JSONPathWildcard): 5408 this = self.json_path_part(this) 5409 return f".{this}" if this else "" 5410 5411 quoted = expression.args.get("quoted") 5412 if not ( 5413 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5414 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5415 return f".{this}" 5416 5417 this = self.json_path_part(this) 5418 5419 return ( 5420 f"[{this}]" 5421 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5422 else f".{this}" 5423 ) 5424 5425 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5426 this = self.json_path_part(expression.this) 5427 return f"[{this}]" if this else "" 5428 5429 def _simplify_unless_literal(self, expression: E) -> E: 5430 if not isinstance(expression, exp.Literal): 5431 import sqlglot.optimizer.simplify 5432 5433 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5434 5435 return expression 5436 5437 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5438 this = expression.this 5439 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5440 self.unsupported( 5441 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5442 ) 5443 return self.sql(this) 5444 5445 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5446 if self.IGNORE_NULLS_BEFORE_ORDER: 5447 from sqlglot.optimizer.scope import find_all_in_scope 5448 5449 # The first modifier here will be the one closest to the AggFunc's arg 5450 mods = sorted( 5451 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5452 key=lambda x: ( 5453 0 5454 if isinstance(x, exp.HavingMax) 5455 else (1 if isinstance(x, exp.Order) else 2) 5456 ), 5457 ) 5458 5459 if mods: 5460 mod = mods[0] 5461 this = expression.__class__(this=mod.this.copy()) 5462 this.meta["inline"] = True 5463 mod.this.replace(this) 5464 return self.sql(expression.this) 5465 5466 agg_func = expression.find(exp.AggFunc) 5467 5468 if agg_func: 5469 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5470 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5471 5472 return f"{self.sql(expression, 'this')} {text}" 5473 5474 def _replace_line_breaks(self, string: str) -> str: 5475 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5476 if self.pretty: 5477 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5478 return string 5479 5480 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5481 option = self.sql(expression, "this") 5482 5483 if expression.expressions: 5484 upper = option.upper() 5485 5486 # Snowflake FILE_FORMAT options are separated by whitespace 5487 sep = " " if upper == "FILE_FORMAT" else ", " 5488 5489 # Databricks copy/format options do not set their list of values with EQ 5490 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5491 values = self.expressions(expression, flat=True, sep=sep) 5492 return f"{option}{op}({values})" 5493 5494 value = self.sql(expression, "expression") 5495 5496 if not value: 5497 return option 5498 5499 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5500 5501 return f"{option}{op}{value}" 5502 5503 def credentials_sql(self, expression: exp.Credentials) -> str: 5504 cred_expr = expression.args.get("credentials") 5505 if isinstance(cred_expr, exp.Literal): 5506 # Redshift case: CREDENTIALS <string> 5507 credentials = self.sql(expression, "credentials") 5508 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5509 else: 5510 # Snowflake case: CREDENTIALS = (...) 5511 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5512 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5513 5514 storage = self.sql(expression, "storage") 5515 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5516 5517 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5518 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5519 5520 iam_role = self.sql(expression, "iam_role") 5521 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5522 5523 region = self.sql(expression, "region") 5524 region = f" REGION {region}" if region else "" 5525 5526 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5527 5528 def copy_sql(self, expression: exp.Copy) -> str: 5529 this = self.sql(expression, "this") 5530 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5531 5532 credentials = self.sql(expression, "credentials") 5533 credentials = self.seg(credentials) if credentials else "" 5534 files = self.expressions(expression, key="files", flat=True) 5535 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5536 5537 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5538 params = self.expressions( 5539 expression, 5540 key="params", 5541 sep=sep, 5542 new_line=True, 5543 skip_last=True, 5544 skip_first=True, 5545 indent=self.COPY_PARAMS_ARE_WRAPPED, 5546 ) 5547 5548 if params: 5549 if self.COPY_PARAMS_ARE_WRAPPED: 5550 params = f" WITH ({params})" 5551 elif not self.pretty and (files or credentials): 5552 params = f" {params}" 5553 5554 return f"COPY{this}{kind} {files}{credentials}{params}" 5555 5556 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5557 return "" 5558 5559 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5560 on_sql = "ON" if expression.args.get("on") else "OFF" 5561 filter_col: str | None = self.sql(expression, "filter_column") 5562 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5563 retention_period: str | None = self.sql(expression, "retention_period") 5564 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5565 5566 if filter_col or retention_period: 5567 on_sql = self.func("ON", filter_col, retention_period) 5568 5569 return f"DATA_DELETION={on_sql}" 5570 5571 def maskingpolicycolumnconstraint_sql( 5572 self, expression: exp.MaskingPolicyColumnConstraint 5573 ) -> str: 5574 this = self.sql(expression, "this") 5575 expressions = self.expressions(expression, flat=True) 5576 expressions = f" USING ({expressions})" if expressions else "" 5577 return f"MASKING POLICY {this}{expressions}" 5578 5579 def gapfill_sql(self, expression: exp.GapFill) -> str: 5580 this = self.sql(expression, "this") 5581 this = f"TABLE {this}" 5582 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5583 5584 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5585 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5586 5587 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5588 this = self.sql(expression, "this") 5589 expr = expression.expression 5590 5591 if isinstance(expr, exp.Func): 5592 # T-SQL's CLR functions are case sensitive 5593 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5594 else: 5595 expr = self.sql(expression, "expression") 5596 5597 return self.scope_resolution(expr, this) 5598 5599 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5600 if self.PARSE_JSON_NAME is None: 5601 return self.sql(expression.this) 5602 5603 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5604 5605 def rand_sql(self, expression: exp.Rand) -> str: 5606 lower = self.sql(expression, "lower") 5607 upper = self.sql(expression, "upper") 5608 5609 if lower and upper: 5610 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5611 return self.func("RAND", expression.this) 5612 5613 def changes_sql(self, expression: exp.Changes) -> str: 5614 information = self.sql(expression, "information") 5615 information = f"INFORMATION => {information}" 5616 at_before = self.sql(expression, "at_before") 5617 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5618 end = self.sql(expression, "end") 5619 end = f"{self.seg('')}{end}" if end else "" 5620 5621 return f"CHANGES ({information}){at_before}{end}" 5622 5623 def pad_sql(self, expression: exp.Pad) -> str: 5624 prefix = "L" if expression.args.get("is_left") else "R" 5625 5626 fill_pattern = self.sql(expression, "fill_pattern") or None 5627 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5628 fill_pattern = "' '" 5629 5630 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5631 5632 def summarize_sql(self, expression: exp.Summarize) -> str: 5633 table = " TABLE" if expression.args.get("table") else "" 5634 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5635 5636 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5637 generate_series = exp.GenerateSeries(**expression.args) 5638 5639 parent = expression.parent 5640 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5641 parent = parent.parent 5642 5643 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5644 return self.sql(exp.Unnest(expressions=[generate_series])) 5645 5646 if isinstance(parent, exp.Select): 5647 self.unsupported("GenerateSeries projection unnesting is not supported.") 5648 5649 return self.sql(generate_series) 5650 5651 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5652 if self.SUPPORTS_CONVERT_TIMEZONE: 5653 return self.function_fallback_sql(expression) 5654 5655 source_tz = expression.args.get("source_tz") 5656 target_tz = expression.args.get("target_tz") 5657 timestamp = expression.args.get("timestamp") 5658 5659 if source_tz and timestamp: 5660 timestamp = exp.AtTimeZone( 5661 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5662 ) 5663 5664 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5665 5666 return self.sql(expr) 5667 5668 def json_sql(self, expression: exp.JSON) -> str: 5669 this = self.sql(expression, "this") 5670 this = f" {this}" if this else "" 5671 5672 _with = expression.args.get("with_") 5673 5674 if _with is None: 5675 with_sql = "" 5676 elif not _with: 5677 with_sql = " WITHOUT" 5678 else: 5679 with_sql = " WITH" 5680 5681 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5682 5683 return f"JSON{this}{with_sql}{unique_sql}" 5684 5685 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5686 path = self.sql(expression, "path") 5687 returning = self.sql(expression, "returning") 5688 returning = f" RETURNING {returning}" if returning else "" 5689 5690 on_condition = self.sql(expression, "on_condition") 5691 on_condition = f" {on_condition}" if on_condition else "" 5692 5693 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5694 5695 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5696 regexp = " REGEXP" if expression.args.get("regexp") else "" 5697 return f"SKIP{regexp} {self.sql(expression.expression)}" 5698 5699 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5700 else_ = "ELSE " if expression.args.get("else_") else "" 5701 condition = self.sql(expression, "expression") 5702 condition = f"WHEN {condition} THEN " if condition else else_ 5703 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5704 return f"{condition}{insert}" 5705 5706 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5707 kind = self.sql(expression, "kind") 5708 expressions = self.seg(self.expressions(expression, sep=" ")) 5709 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5710 return res 5711 5712 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5713 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5714 empty = expression.args.get("empty") 5715 empty = ( 5716 f"DEFAULT {empty} ON EMPTY" 5717 if isinstance(empty, exp.Expr) 5718 else self.sql(expression, "empty") 5719 ) 5720 5721 error = expression.args.get("error") 5722 error = ( 5723 f"DEFAULT {error} ON ERROR" 5724 if isinstance(error, exp.Expr) 5725 else self.sql(expression, "error") 5726 ) 5727 5728 if error and empty: 5729 error = ( 5730 f"{empty} {error}" 5731 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5732 else f"{error} {empty}" 5733 ) 5734 empty = "" 5735 5736 null = self.sql(expression, "null") 5737 5738 return f"{empty}{error}{null}" 5739 5740 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5741 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5742 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5743 5744 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5745 this = self.sql(expression, "this") 5746 path = self.sql(expression, "path") 5747 5748 passing = self.expressions(expression, "passing") 5749 passing = f" PASSING {passing}" if passing else "" 5750 5751 on_condition = self.sql(expression, "on_condition") 5752 on_condition = f" {on_condition}" if on_condition else "" 5753 5754 path = f"{path}{passing}{on_condition}" 5755 5756 return self.func("JSON_EXISTS", this, path) 5757 5758 def _add_arrayagg_null_filter( 5759 self, 5760 array_agg_sql: str, 5761 array_agg_expr: exp.ArrayAgg, 5762 column_expr: exp.Expr, 5763 ) -> str: 5764 """ 5765 Add NULL filter to ARRAY_AGG if dialect requires it. 5766 5767 Args: 5768 array_agg_sql: The generated ARRAY_AGG SQL string 5769 array_agg_expr: The ArrayAgg expression node 5770 column_expr: The column/expression to filter (before ORDER BY wrapping) 5771 5772 Returns: 5773 SQL string with FILTER clause added if needed 5774 """ 5775 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5776 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5777 if not ( 5778 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5779 ): 5780 return array_agg_sql 5781 5782 parent = array_agg_expr.parent 5783 if isinstance(parent, exp.Filter): 5784 parent_cond = parent.expression.this 5785 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5786 elif column_expr.find(exp.Column): 5787 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5788 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5789 this_sql = ( 5790 self.expressions(column_expr) 5791 if isinstance(column_expr, exp.Distinct) 5792 else self.sql(column_expr) 5793 ) 5794 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5795 5796 return array_agg_sql 5797 5798 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5799 array_agg = self.function_fallback_sql(expression) 5800 column_expr = expression.this 5801 if isinstance(column_expr, exp.Order): 5802 column_expr = column_expr.this 5803 5804 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5805 5806 def slice_sql(self, expression: exp.Slice) -> str: 5807 step = self.sql(expression, "step") 5808 end = self.sql(expression.expression) 5809 begin = self.sql(expression.this) 5810 5811 sql = f"{end}:{step}" if step else end 5812 return f"{begin}:{sql}" if sql else f"{begin}:" 5813 5814 def apply_sql(self, expression: exp.Apply) -> str: 5815 this = self.sql(expression, "this") 5816 expr = self.sql(expression, "expression") 5817 5818 return f"{this} APPLY({expr})" 5819 5820 def _grant_or_revoke_sql( 5821 self, 5822 expression: exp.Grant | exp.Revoke, 5823 keyword: str, 5824 preposition: str, 5825 grant_option_prefix: str = "", 5826 grant_option_suffix: str = "", 5827 ) -> str: 5828 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5829 5830 kind = self.sql(expression, "kind") 5831 kind = f" {kind}" if kind else "" 5832 5833 securable = self.sql(expression, "securable") 5834 securable = f" {securable}" if securable else "" 5835 5836 principals = self.expressions(expression, key="principals", flat=True) 5837 5838 if not expression.args.get("grant_option"): 5839 grant_option_prefix = grant_option_suffix = "" 5840 5841 # cascade for revoke only 5842 cascade = self.sql(expression, "cascade") 5843 cascade = f" {cascade}" if cascade else "" 5844 5845 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5846 5847 def grant_sql(self, expression: exp.Grant) -> str: 5848 return self._grant_or_revoke_sql( 5849 expression, 5850 keyword="GRANT", 5851 preposition="TO", 5852 grant_option_suffix=" WITH GRANT OPTION", 5853 ) 5854 5855 def revoke_sql(self, expression: exp.Revoke) -> str: 5856 return self._grant_or_revoke_sql( 5857 expression, 5858 keyword="REVOKE", 5859 preposition="FROM", 5860 grant_option_prefix="GRANT OPTION FOR ", 5861 ) 5862 5863 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5864 this = self.sql(expression, "this") 5865 columns = self.expressions(expression, flat=True) 5866 columns = f"({columns})" if columns else "" 5867 5868 return f"{this}{columns}" 5869 5870 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5871 this = self.sql(expression, "this") 5872 5873 kind = self.sql(expression, "kind") 5874 kind = f"{kind} " if kind else "" 5875 5876 return f"{kind}{this}" 5877 5878 def columns_sql(self, expression: exp.Columns) -> str: 5879 func = self.function_fallback_sql(expression) 5880 if expression.args.get("unpack"): 5881 func = f"*{func}" 5882 5883 return func 5884 5885 def overlay_sql(self, expression: exp.Overlay) -> str: 5886 this = self.sql(expression, "this") 5887 expr = self.sql(expression, "expression") 5888 from_sql = self.sql(expression, "from_") 5889 for_sql = self.sql(expression, "for_") 5890 for_sql = f" FOR {for_sql}" if for_sql else "" 5891 5892 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5893 5894 @unsupported_args("format") 5895 def todouble_sql(self, expression: exp.ToDouble) -> str: 5896 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5897 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5898 5899 def string_sql(self, expression: exp.String) -> str: 5900 this = expression.this 5901 zone = expression.args.get("zone") 5902 5903 if zone: 5904 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5905 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5906 # set for source_tz to transpile the time conversion before the STRING cast 5907 this = exp.ConvertTimezone( 5908 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5909 ) 5910 5911 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5912 5913 def median_sql(self, expression: exp.Median) -> str: 5914 if not self.SUPPORTS_MEDIAN: 5915 return self.sql( 5916 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5917 ) 5918 5919 return self.function_fallback_sql(expression) 5920 5921 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5922 filler = self.sql(expression, "this") 5923 filler = f" {filler}" if filler else "" 5924 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5925 return f"TRUNCATE{filler} {with_count}" 5926 5927 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5928 if self.SUPPORTS_UNIX_SECONDS: 5929 return self.function_fallback_sql(expression) 5930 5931 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5932 5933 return self.sql( 5934 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5935 ) 5936 5937 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5938 dim = expression.expression 5939 5940 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5941 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5942 if not (dim.is_int and dim.name == "1"): 5943 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5944 dim = None 5945 5946 # If dimension is required but not specified, default initialize it 5947 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5948 dim = exp.Literal.number(1) 5949 5950 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5951 5952 def attach_sql(self, expression: exp.Attach) -> str: 5953 this = self.sql(expression, "this") 5954 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5955 expressions = self.expressions(expression) 5956 expressions = f" ({expressions})" if expressions else "" 5957 5958 return f"ATTACH{exists_sql} {this}{expressions}" 5959 5960 def detach_sql(self, expression: exp.Detach) -> str: 5961 kind = self.sql(expression, "kind") 5962 kind = f" {kind}" if kind else "" 5963 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5964 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5965 exists = " IF EXISTS" if expression.args.get("exists") else "" 5966 if exists: 5967 kind = kind or " DATABASE" 5968 5969 this = self.sql(expression, "this") 5970 this = f" {this}" if this else "" 5971 cluster = self.sql(expression, "cluster") 5972 cluster = f" {cluster}" if cluster else "" 5973 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5974 sync = " SYNC" if expression.args.get("sync") else "" 5975 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5976 5977 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5978 this = self.sql(expression, "this") 5979 value = self.sql(expression, "expression") 5980 value = f" {value}" if value else "" 5981 return f"{this}{value}" 5982 5983 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5984 return ( 5985 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5986 ) 5987 5988 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5989 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5990 encode = f"{encode} {self.sql(expression, 'this')}" 5991 5992 properties = expression.args.get("properties") 5993 if properties: 5994 encode = f"{encode} {self.properties(properties)}" 5995 5996 return encode 5997 5998 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5999 this = self.sql(expression, "this") 6000 include = f"INCLUDE {this}" 6001 6002 column_def = self.sql(expression, "column_def") 6003 if column_def: 6004 include = f"{include} {column_def}" 6005 6006 alias = self.sql(expression, "alias") 6007 if alias: 6008 include = f"{include} AS {alias}" 6009 6010 return include 6011 6012 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6013 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6014 name = f"{prefix} {self.sql(expression, 'this')}" 6015 return self.func("XMLELEMENT", name, *expression.expressions) 6016 6017 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6018 this = self.sql(expression, "this") 6019 expr = self.sql(expression, "expression") 6020 expr = f"({expr})" if expr else "" 6021 return f"{this}{expr}" 6022 6023 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6024 partitions = self.expressions(expression, "partition_expressions") 6025 create = self.expressions(expression, "create_expressions") 6026 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6027 6028 def partitionbyrangepropertydynamic_sql( 6029 self, expression: exp.PartitionByRangePropertyDynamic 6030 ) -> str: 6031 start = self.sql(expression, "start") 6032 end = self.sql(expression, "end") 6033 6034 every = expression.args["every"] 6035 if isinstance(every, exp.Interval) and every.this.is_string: 6036 every.this.replace(exp.Literal.number(every.name)) 6037 6038 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6039 6040 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6041 name = self.sql(expression, "this") 6042 values = self.expressions(expression, flat=True) 6043 6044 return f"NAME {name} VALUE {values}" 6045 6046 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6047 kind = self.sql(expression, "kind") 6048 sample = self.sql(expression, "sample") 6049 return f"SAMPLE {sample} {kind}" 6050 6051 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6052 kind = self.sql(expression, "kind") 6053 option = self.sql(expression, "option") 6054 option = f" {option}" if option else "" 6055 this = self.sql(expression, "this") 6056 this = f" {this}" if this else "" 6057 columns = self.expressions(expression) 6058 columns = f" {columns}" if columns else "" 6059 return f"{kind}{option} STATISTICS{this}{columns}" 6060 6061 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6062 this = self.sql(expression, "this") 6063 columns = self.expressions(expression) 6064 inner_expression = self.sql(expression, "expression") 6065 inner_expression = f" {inner_expression}" if inner_expression else "" 6066 update_options = self.sql(expression, "update_options") 6067 update_options = f" {update_options} UPDATE" if update_options else "" 6068 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6069 6070 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6071 kind = self.sql(expression, "kind") 6072 kind = f" {kind}" if kind else "" 6073 return f"DELETE{kind} STATISTICS" 6074 6075 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6076 inner_expression = self.sql(expression, "expression") 6077 return f"LIST CHAINED ROWS{inner_expression}" 6078 6079 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6080 kind = self.sql(expression, "kind") 6081 this = self.sql(expression, "this") 6082 this = f" {this}" if this else "" 6083 inner_expression = self.sql(expression, "expression") 6084 return f"VALIDATE {kind}{this}{inner_expression}" 6085 6086 def analyze_sql(self, expression: exp.Analyze) -> str: 6087 options = self.expressions(expression, key="options", sep=" ") 6088 options = f" {options}" if options else "" 6089 kind = self.sql(expression, "kind") 6090 kind = f" {kind}" if kind else "" 6091 tables = self.expressions(expression, key="tables", flat=True) 6092 tables = f" {tables}" if tables else "" 6093 mode = self.sql(expression, "mode") 6094 mode = f" {mode}" if mode else "" 6095 properties = self.sql(expression, "properties") 6096 properties = f" {properties}" if properties else "" 6097 partition = self.sql(expression, "partition") 6098 partition = f" {partition}" if partition else "" 6099 inner_expression = self.sql(expression, "expression") 6100 inner_expression = f" {inner_expression}" if inner_expression else "" 6101 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6102 6103 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6104 this = self.sql(expression, "this") 6105 namespaces = self.expressions(expression, key="namespaces") 6106 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6107 passing = self.expressions(expression, key="passing") 6108 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6109 columns = self.expressions(expression, key="columns") 6110 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6111 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6112 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6113 6114 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6115 this = self.sql(expression, "this") 6116 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6117 6118 def export_sql(self, expression: exp.Export) -> str: 6119 this = self.sql(expression, "this") 6120 connection = self.sql(expression, "connection") 6121 connection = f"WITH CONNECTION {connection} " if connection else "" 6122 options = self.sql(expression, "options") 6123 return f"EXPORT DATA {connection}{options} AS {this}" 6124 6125 def declare_sql(self, expression: exp.Declare) -> str: 6126 replace = "OR REPLACE " if expression.args.get("replace") else "" 6127 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6128 6129 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6130 variables = self.expressions(expression, "this") 6131 default = self.sql(expression, "default") 6132 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6133 6134 kind = self.sql(expression, "kind") 6135 if isinstance(expression.args.get("kind"), exp.Schema): 6136 kind = f"TABLE {kind}" 6137 6138 kind = f" {kind}" if kind else "" 6139 6140 return f"{variables}{kind}{default}" 6141 6142 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6143 kind = self.sql(expression, "kind") 6144 this = self.sql(expression, "this") 6145 set = self.sql(expression, "expression") 6146 using = self.sql(expression, "using") 6147 using = f" USING {using}" if using else "" 6148 6149 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6150 6151 return f"{kind_sql} {this} SET {set}{using}" 6152 6153 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6154 params = self.expressions(expression, key="params", flat=True) 6155 return self.func(expression.name, *expression.expressions) + f"({params})" 6156 6157 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6158 return self.func(expression.name, *expression.expressions) 6159 6160 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6161 return self.anonymousaggfunc_sql(expression) 6162 6163 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6164 return self.parameterizedagg_sql(expression) 6165 6166 def show_sql(self, expression: exp.Show) -> str: 6167 self.unsupported("Unsupported SHOW statement") 6168 return "" 6169 6170 def install_sql(self, expression: exp.Install) -> str: 6171 self.unsupported("Unsupported INSTALL statement") 6172 return "" 6173 6174 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6175 # Snowflake GET/PUT statements: 6176 # PUT <file> <internalStage> <properties> 6177 # GET <internalStage> <file> <properties> 6178 props = expression.args.get("properties") 6179 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6180 this = self.sql(expression, "this") 6181 target = self.sql(expression, "target") 6182 6183 if isinstance(expression, exp.Put): 6184 return f"PUT {this} {target}{props_sql}" 6185 else: 6186 return f"GET {target} {this}{props_sql}" 6187 6188 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6189 this = self.sql(expression, "this") 6190 expr = self.sql(expression, "expression") 6191 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6192 return f"TRANSLATE({this} USING {expr}{with_error})" 6193 6194 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6195 if self.SUPPORTS_DECODE_CASE: 6196 return self.func("DECODE", *expression.expressions) 6197 6198 decode_expr, *expressions = expression.expressions 6199 6200 ifs = [] 6201 for search, result in zip(expressions[::2], expressions[1::2]): 6202 if isinstance(search, exp.Literal): 6203 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6204 elif isinstance(search, exp.Null): 6205 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6206 else: 6207 if isinstance(search, exp.Binary): 6208 search = exp.paren(search) 6209 6210 cond = exp.or_( 6211 decode_expr.eq(search), 6212 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6213 copy=False, 6214 ) 6215 ifs.append(exp.If(this=cond, true=result)) 6216 6217 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6218 return self.sql(case) 6219 6220 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6221 this = self.sql(expression, "this") 6222 this = self.seg(this, sep="") 6223 dimensions = self.expressions( 6224 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6225 ) 6226 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6227 metrics = self.expressions( 6228 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6229 ) 6230 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6231 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6232 facts = self.seg(f"FACTS {facts}") if facts else "" 6233 where = self.sql(expression, "where") 6234 where = self.seg(f"WHERE {where}") if where else "" 6235 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6236 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6237 6238 def getextract_sql(self, expression: exp.GetExtract) -> str: 6239 this = expression.this 6240 expr = expression.expression 6241 6242 if not this.type or not expression.type: 6243 import sqlglot.optimizer.annotate_types 6244 6245 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6246 6247 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6248 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6249 6250 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6251 6252 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6253 return self.sql( 6254 exp.DateAdd( 6255 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6256 expression=expression.this, 6257 unit=exp.var("DAY"), 6258 ) 6259 ) 6260 6261 def space_sql(self: Generator, expression: exp.Space) -> str: 6262 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6263 6264 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6265 return f"BUILD {self.sql(expression, 'this')}" 6266 6267 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6268 method = self.sql(expression, "method") 6269 kind = expression.args.get("kind") 6270 if not kind: 6271 return f"REFRESH {method}" 6272 6273 every = self.sql(expression, "every") 6274 unit = self.sql(expression, "unit") 6275 every = f" EVERY {every} {unit}" if every else "" 6276 starts = self.sql(expression, "starts") 6277 starts = f" STARTS {starts}" if starts else "" 6278 6279 return f"REFRESH {method} ON {kind}{every}{starts}" 6280 6281 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6282 self.unsupported("The model!attribute syntax is not supported") 6283 return "" 6284 6285 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6286 return self.func("DIRECTORY", expression.this) 6287 6288 def uuid_sql(self, expression: exp.Uuid) -> str: 6289 is_string = expression.args.get("is_string", False) 6290 uuid_func_sql = self.func("UUID") 6291 6292 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6293 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6294 6295 return uuid_func_sql 6296 6297 def initcap_sql(self, expression: exp.Initcap) -> str: 6298 delimiters = expression.expression 6299 6300 if delimiters: 6301 # do not generate delimiters arg if we are round-tripping from default delimiters 6302 if ( 6303 delimiters.is_string 6304 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6305 ): 6306 delimiters = None 6307 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6308 self.unsupported("INITCAP does not support custom delimiters") 6309 delimiters = None 6310 6311 return self.func("INITCAP", expression.this, delimiters) 6312 6313 def localtime_sql(self, expression: exp.Localtime) -> str: 6314 this = expression.this 6315 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6316 6317 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6318 this = expression.this 6319 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6320 6321 def weekstart_name(self, expression: exp.WeekStart) -> str: 6322 import sqlglot.dialects.dialect 6323 6324 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6325 this = expression.this.name.upper() 6326 6327 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6328 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6329 6330 if dow_from_week_start_day != dow_from_week_offset: 6331 self.unsupported( 6332 f"WEEK({this}) is not supported; falling back to the default week start day" 6333 ) 6334 6335 return "WEEK" 6336 6337 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6338 name = self.weekstart_name(expression) 6339 6340 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6341 if isinstance(expression.parent, exp.DateTrunc): 6342 return self.sql(exp.Literal.string(name)) 6343 6344 return name 6345 6346 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6347 this = self.expressions(expression) 6348 charset = self.sql(expression, "charset") 6349 using = f" USING {charset}" if charset else "" 6350 return self.func(name, this + using) 6351 6352 def block_sql(self, expression: exp.Block) -> str: 6353 expressions = self.expressions(expression, sep="; ", flat=True) 6354 begin = "BEGIN " if expression.args.get("begin") else "" 6355 return f"{begin}{expressions}" if expressions else "" 6356 6357 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6358 self.unsupported("Unsupported Inline UDFs syntax") 6359 return "" 6360 6361 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6362 self.unsupported("Unsupported Stored Procedure syntax") 6363 return "" 6364 6365 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6366 self.unsupported("Unsupported If block syntax") 6367 return "" 6368 6369 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6370 self.unsupported("Unsupported Case statement syntax") 6371 return "" 6372 6373 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6374 self.unsupported("Unsupported While block syntax") 6375 return "" 6376 6377 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6378 self.unsupported("Unsupported Loop block syntax") 6379 return "" 6380 6381 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6382 self.unsupported("Unsupported Repeat block syntax") 6383 return "" 6384 6385 def leave_sql(self, expression: exp.Leave) -> str: 6386 self.unsupported("Unsupported Leave syntax") 6387 return "" 6388 6389 def iterate_sql(self, expression: exp.Iterate) -> str: 6390 self.unsupported("Unsupported Iterate syntax") 6391 return "" 6392 6393 def execute_sql(self, expression: exp.Execute) -> str: 6394 self.unsupported("Unsupported Execute syntax") 6395 return "" 6396 6397 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6398 self.unsupported("Unsupported Execute syntax") 6399 return "" 6400 6401 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6402 props = self.expressions(expression, sep=" ") 6403 return f"MODIFY {props}" 6404 6405 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6406 kind = expression.args.get("kind") 6407 return f"USING {kind} {self.sql(expression, 'this')}" 6408 6409 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6410 this = self.sql(expression, "this") 6411 to = self.sql(expression, "to") 6412 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 alias = self.sql(expression, "alias") 2964 alias = f" AS {alias}" if alias else "" 2965 2966 ordinality = expression.args.get("ordinality") or "" 2967 if ordinality: 2968 ordinality = f" WITH ORDINALITY{alias}" 2969 alias = "" 2970 2971 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2972 2973 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2974 this = self.sql(expression, "this") 2975 2976 args = [ 2977 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2978 for e in (expression.args.get(k) for k in ("offset", "expression")) 2979 if e 2980 ] 2981 2982 args_sql = ", ".join(self.sql(e) for e in args) 2983 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2984 expressions = self.expressions(expression, flat=True) 2985 limit_options = self.sql(expression, "limit_options") 2986 expressions = f" BY {expressions}" if expressions else "" 2987 2988 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2989 2990 def offset_sql(self, expression: exp.Offset) -> str: 2991 this = self.sql(expression, "this") 2992 value = expression.expression 2993 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2994 expressions = self.expressions(expression, flat=True) 2995 expressions = f" BY {expressions}" if expressions else "" 2996 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2997 2998 def setitem_sql(self, expression: exp.SetItem) -> str: 2999 kind = self.sql(expression, "kind") 3000 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3001 kind = "" 3002 else: 3003 kind = f"{kind} " if kind else "" 3004 this = self.sql(expression, "this") 3005 expressions = self.expressions(expression) 3006 collate = self.sql(expression, "collate") 3007 collate = f" COLLATE {collate}" if collate else "" 3008 global_ = "GLOBAL " if expression.args.get("global_") else "" 3009 return f"{global_}{kind}{this}{expressions}{collate}" 3010 3011 def set_sql(self, expression: exp.Set) -> str: 3012 expressions = f" {self.expressions(expression, flat=True)}" 3013 tag = " TAG" if expression.args.get("tag") else "" 3014 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3015 3016 def queryband_sql(self, expression: exp.QueryBand) -> str: 3017 this = self.sql(expression, "this") 3018 update = " UPDATE" if expression.args.get("update") else "" 3019 scope = self.sql(expression, "scope") 3020 scope = f" FOR {scope}" if scope else "" 3021 3022 return f"QUERY_BAND = {this}{update}{scope}" 3023 3024 def pragma_sql(self, expression: exp.Pragma) -> str: 3025 return f"PRAGMA {self.sql(expression, 'this')}" 3026 3027 def lock_sql(self, expression: exp.Lock) -> str: 3028 if not self.LOCKING_READS_SUPPORTED: 3029 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3030 return "" 3031 3032 update = expression.args["update"] 3033 key = expression.args.get("key") 3034 if update: 3035 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3036 else: 3037 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3038 expressions = self.expressions(expression, flat=True) 3039 expressions = f" OF {expressions}" if expressions else "" 3040 wait = expression.args.get("wait") 3041 3042 if wait is not None: 3043 if isinstance(wait, exp.Literal): 3044 wait = f" WAIT {self.sql(wait)}" 3045 else: 3046 wait = " NOWAIT" if wait else " SKIP LOCKED" 3047 3048 return f"{lock_type}{expressions}{wait or ''}" 3049 3050 def literal_sql(self, expression: exp.Literal) -> str: 3051 text = expression.this or "" 3052 if expression.is_string: 3053 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3054 return text 3055 3056 def escape_str( 3057 self, 3058 text: str, 3059 escape_backslash: bool = True, 3060 delimiter: str | None = None, 3061 escaped_delimiter: str | None = None, 3062 is_byte_string: bool = False, 3063 ) -> str: 3064 if is_byte_string: 3065 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3066 else: 3067 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3068 3069 if supports_escape_sequences: 3070 text = "".join( 3071 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3072 for ch in text 3073 ) 3074 3075 delimiter = delimiter or self.dialect.QUOTE_END 3076 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3077 3078 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3079 3080 def loaddata_sql(self, expression: exp.LoadData) -> str: 3081 is_overwrite = expression.args.get("overwrite") 3082 overwrite = " OVERWRITE" if is_overwrite else "" 3083 this = self.sql(expression, "this") 3084 3085 files = expression.args.get("files") 3086 if files: 3087 files_sql = self.expressions(files, flat=True) 3088 files_sql = f"FILES{self.wrap(files_sql)}" 3089 if is_overwrite: 3090 this = f" {this}" 3091 elif expression.args.get("temp"): 3092 this = f" INTO TEMP TABLE {this}" 3093 else: 3094 this = f" INTO TABLE {this}" 3095 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3096 3097 local = " LOCAL" if expression.args.get("local") else "" 3098 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3099 this = f" INTO TABLE {this}" 3100 partition = self.sql(expression, "partition") 3101 partition = f" {partition}" if partition else "" 3102 input_format = self.sql(expression, "input_format") 3103 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3104 serde = self.sql(expression, "serde") 3105 serde = f" SERDE {serde}" if serde else "" 3106 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3107 3108 def null_sql(self, *_) -> str: 3109 return "NULL" 3110 3111 def boolean_sql(self, expression: exp.Boolean) -> str: 3112 return "TRUE" if expression.this else "FALSE" 3113 3114 def booland_sql(self, expression: exp.Booland) -> str: 3115 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3116 3117 def boolor_sql(self, expression: exp.Boolor) -> str: 3118 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3119 3120 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3121 this = self.sql(expression, "this") 3122 this = f"{this} " if this else this 3123 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3124 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3125 3126 def withfill_sql(self, expression: exp.WithFill) -> str: 3127 from_sql = self.sql(expression, "from_") 3128 from_sql = f" FROM {from_sql}" if from_sql else "" 3129 to_sql = self.sql(expression, "to") 3130 to_sql = f" TO {to_sql}" if to_sql else "" 3131 step_sql = self.sql(expression, "step") 3132 step_sql = f" STEP {step_sql}" if step_sql else "" 3133 interpolated_values = [ 3134 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3135 if isinstance(e, exp.Alias) 3136 else self.sql(e, "this") 3137 for e in expression.args.get("interpolate") or [] 3138 ] 3139 interpolate = ( 3140 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3141 ) 3142 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3143 3144 def cluster_sql(self, expression: exp.Cluster) -> str: 3145 return self.op_expressions("CLUSTER BY", expression) 3146 3147 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3148 if expression.this: 3149 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3150 return "" 3151 expressions = self.expressions(expression, flat=True) 3152 return f"CLUSTER BY ({expressions})" 3153 3154 def distribute_sql(self, expression: exp.Distribute) -> str: 3155 return self.op_expressions("DISTRIBUTE BY", expression) 3156 3157 def sort_sql(self, expression: exp.Sort) -> str: 3158 return self.op_expressions("SORT BY", expression) 3159 3160 def _resolve_ordered_for_null_ordering_simulation( 3161 self, expression: exp.Ordered 3162 ) -> exp.Expr | None: 3163 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3164 3165 Returns the underlying expression of the uniquely-matching projection 3166 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3167 simulation, since the CASE is evaluated in FROM-clause scope rather 3168 than alias scope (MySQL error 1052). Returns None if no safe 3169 substitution applies, leaving the original behaviour unchanged. 3170 """ 3171 this = expression.this 3172 if not (isinstance(this, exp.Column) and not this.table): 3173 return None 3174 3175 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3176 if not isinstance(ancestor, exp.Select): 3177 return None 3178 3179 column_name = this.name 3180 matched: list[exp.Expr] = [ 3181 p.this if isinstance(p, exp.Alias) else p 3182 for p in ancestor.selects 3183 if p.output_name == column_name 3184 ] 3185 match = matched[0] if len(matched) == 1 else None 3186 3187 # Skip the substitution when it would be identical to the existing 3188 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3189 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3190 return None 3191 3192 return match 3193 3194 def ordered_sql(self, expression: exp.Ordered) -> str: 3195 desc = expression.args.get("desc") 3196 asc = not desc 3197 3198 nulls_first = expression.args.get("nulls_first") 3199 nulls_last = not nulls_first 3200 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3201 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3202 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3203 3204 this = self.sql(expression, "this") 3205 3206 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3207 nulls_sort_change = "" 3208 if nulls_first and ( 3209 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3210 ): 3211 nulls_sort_change = " NULLS FIRST" 3212 elif ( 3213 nulls_last 3214 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3215 and not nulls_are_last 3216 ): 3217 nulls_sort_change = " NULLS LAST" 3218 3219 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3220 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3221 window = expression.find_ancestor(exp.Window, exp.Select) 3222 3223 if isinstance(window, exp.Window): 3224 window_this = window.this 3225 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3226 window_this = window_this.this 3227 spec = window.args.get("spec") 3228 else: 3229 window_this = None 3230 spec = None 3231 3232 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3233 # without a spec or with a ROWS spec, but not with RANGE 3234 if not ( 3235 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3236 and (not spec or spec.text("kind").upper() == "ROWS") 3237 ): 3238 if window_this and spec: 3239 self.unsupported( 3240 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3241 ) 3242 nulls_sort_change = "" 3243 elif self.NULL_ORDERING_SUPPORTED is False and ( 3244 (asc and nulls_sort_change == " NULLS LAST") 3245 or (desc and nulls_sort_change == " NULLS FIRST") 3246 ): 3247 # BigQuery does not allow these ordering/nulls combinations when used under 3248 # an aggregation func or under a window containing one 3249 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3250 3251 if isinstance(ancestor, exp.Window): 3252 ancestor = ancestor.this 3253 if isinstance(ancestor, exp.AggFunc): 3254 self.unsupported( 3255 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3256 ) 3257 nulls_sort_change = "" 3258 elif self.NULL_ORDERING_SUPPORTED is None: 3259 if expression.this.is_int: 3260 self.unsupported( 3261 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3262 ) 3263 elif not isinstance(expression.this, exp.Rand): 3264 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3265 target = self.sql(resolved) if resolved is not None else this 3266 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3267 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3268 nulls_sort_change = "" 3269 3270 with_fill = self.sql(expression, "with_fill") 3271 with_fill = f" {with_fill}" if with_fill else "" 3272 3273 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3274 3275 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3276 window_frame = self.sql(expression, "window_frame") 3277 window_frame = f"{window_frame} " if window_frame else "" 3278 3279 this = self.sql(expression, "this") 3280 3281 return f"{window_frame}{this}" 3282 3283 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3284 partition = self.partition_by_sql(expression) 3285 order = self.sql(expression, "order") 3286 measures = self.expressions(expression, key="measures") 3287 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3288 rows = self.sql(expression, "rows") 3289 rows = self.seg(rows) if rows else "" 3290 after = self.sql(expression, "after") 3291 after = self.seg(after) if after else "" 3292 pattern = self.sql(expression, "pattern") 3293 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3294 definition_sqls = [ 3295 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3296 for definition in expression.args.get("define", []) 3297 ] 3298 definitions = self.expressions(sqls=definition_sqls) 3299 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3300 body = "".join( 3301 ( 3302 partition, 3303 order, 3304 measures, 3305 rows, 3306 after, 3307 pattern, 3308 define, 3309 ) 3310 ) 3311 alias = self.sql(expression, "alias") 3312 alias = f" {alias}" if alias else "" 3313 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3314 3315 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3316 limit = expression.args.get("limit") 3317 3318 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3319 count = limit.args.get("count") 3320 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3321 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3322 limit = exp.Limit( 3323 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3324 ) 3325 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3326 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3327 3328 return csv( 3329 *sqls, 3330 *[self.sql(join) for join in expression.args.get("joins") or []], 3331 self.sql(expression, "match"), 3332 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3333 self.sql(expression, "prewhere"), 3334 self.sql(expression, "where"), 3335 self.sql(expression, "connect"), 3336 self.sql(expression, "group"), 3337 self.sql(expression, "having"), 3338 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3339 self.sql(expression, "order"), 3340 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3341 *self.after_limit_modifiers(expression), 3342 self.sql(expression, "for_"), 3343 self.options_modifier(expression), 3344 sep="", 3345 ) 3346 3347 def options_modifier(self, expression: exp.Expr) -> str: 3348 options = self.expressions(expression, key="options") 3349 return f" {options}" if options else "" 3350 3351 def forclause_sql(self, expression: exp.ForClause) -> str: 3352 kind = expression.args["kind"] 3353 if kind == "BROWSE": 3354 return f"{self.sep()}FOR BROWSE" 3355 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3356 # the target dialect doesn't support QueryOption, so we drop the clause. 3357 options = self.expressions(expression, key="expressions") 3358 if not options: 3359 return "" 3360 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3361 3362 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3363 self.unsupported("Unsupported query option.") 3364 return "" 3365 3366 def offset_limit_modifiers( 3367 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3368 ) -> list[str]: 3369 return [ 3370 self.sql(expression, "offset") if fetch else self.sql(limit), 3371 self.sql(limit) if fetch else self.sql(expression, "offset"), 3372 ] 3373 3374 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3375 locks = self.expressions(expression, key="locks", sep=" ") 3376 locks = f" {locks}" if locks else "" 3377 return [locks, self.sql(expression, "sample")] 3378 3379 def select_sql(self, expression: exp.Select) -> str: 3380 into = expression.args.get("into") 3381 if not self.SUPPORTS_SELECT_INTO and into: 3382 into.pop() 3383 3384 hint = self.sql(expression, "hint") 3385 distinct = self.sql(expression, "distinct") 3386 distinct = f" {distinct}" if distinct else "" 3387 kind = self.sql(expression, "kind") 3388 3389 limit = expression.args.get("limit") 3390 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3391 top = self.limit_sql(limit, top=True) 3392 limit.pop() 3393 else: 3394 top = "" 3395 3396 expressions = self.expressions(expression) 3397 3398 if kind: 3399 if kind in self.SELECT_KINDS: 3400 kind = f" AS {kind}" 3401 else: 3402 if kind == "STRUCT": 3403 expressions = self.expressions( 3404 sqls=[ 3405 self.sql( 3406 exp.Struct( 3407 expressions=[ 3408 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3409 if isinstance(e, exp.Alias) 3410 else e 3411 for e in expression.expressions 3412 ] 3413 ) 3414 ) 3415 ] 3416 ) 3417 kind = "" 3418 3419 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3420 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3421 3422 exclude = expression.args.get("exclude") 3423 3424 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3425 exclude_sql = self.expressions(sqls=exclude, flat=True) 3426 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3427 3428 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3429 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3430 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3431 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3432 sql = self.query_modifiers( 3433 expression, 3434 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3435 self.sql(expression, "into", comment=False), 3436 self.sql(expression, "from_", comment=False), 3437 ) 3438 3439 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3440 if expression.args.get("with_"): 3441 sql = self.maybe_comment(sql, expression) 3442 expression.pop_comments() 3443 3444 sql = self.prepend_ctes(expression, sql) 3445 3446 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3447 expression.set("exclude", None) 3448 subquery = expression.subquery(copy=False) 3449 star = exp.Star(except_=exclude) 3450 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3451 3452 if not self.SUPPORTS_SELECT_INTO and into: 3453 if into.args.get("temporary"): 3454 table_kind = " TEMPORARY" 3455 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3456 table_kind = " UNLOGGED" 3457 else: 3458 table_kind = "" 3459 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3460 3461 return sql 3462 3463 def schema_sql(self, expression: exp.Schema) -> str: 3464 this = self.sql(expression, "this") 3465 sql = self.schema_columns_sql(expression) 3466 return f"{this} {sql}" if this and sql else this or sql 3467 3468 def schema_columns_sql(self, expression: exp.Expr) -> str: 3469 if expression.expressions: 3470 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3471 return "" 3472 3473 def star_sql(self, expression: exp.Star) -> str: 3474 except_ = self.expressions(expression, key="except_", flat=True) 3475 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3476 replace = self.expressions(expression, key="replace", flat=True) 3477 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3478 rename = self.expressions(expression, key="rename", flat=True) 3479 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3480 ilike = self.sql(expression, "ilike") 3481 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3482 return f"*{ilike}{except_}{replace}{rename}" 3483 3484 def parameter_sql(self, expression: exp.Parameter) -> str: 3485 this = self.sql(expression, "this") 3486 return f"{self.PARAMETER_TOKEN}{this}" 3487 3488 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3489 this = self.sql(expression, "this") 3490 kind = expression.text("kind") 3491 if kind: 3492 kind = f"{kind}." 3493 return f"@@{kind}{this}" 3494 3495 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3496 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3497 3498 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3499 alias = self.sql(expression, "alias") 3500 alias = f"{sep}{alias}" if alias else "" 3501 sample = self.sql(expression, "sample") 3502 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3503 alias = f"{sample}{alias}" 3504 3505 # Set to None so it's not generated again by self.query_modifiers() 3506 expression.set("sample", None) 3507 3508 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3509 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3510 return self.prepend_ctes(expression, sql) 3511 3512 def qualify_sql(self, expression: exp.Qualify) -> str: 3513 this = self.indent(self.sql(expression, "this")) 3514 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3515 3516 def unnest_sql(self, expression: exp.Unnest) -> str: 3517 args = self.expressions(expression, flat=True) 3518 3519 alias = expression.args.get("alias") 3520 offset = expression.args.get("offset") 3521 3522 if self.UNNEST_WITH_ORDINALITY: 3523 if alias and isinstance(offset, exp.Expr): 3524 alias.append("columns", offset) 3525 expression.set("offset", None) 3526 3527 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3528 columns = alias.columns 3529 alias = self.sql(columns[0]) if columns else "" 3530 else: 3531 alias = self.sql(alias) 3532 3533 alias = f" AS {alias}" if alias else alias 3534 if self.UNNEST_WITH_ORDINALITY: 3535 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3536 else: 3537 if isinstance(offset, exp.Expr): 3538 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3539 elif offset: 3540 suffix = f"{alias} WITH OFFSET" 3541 else: 3542 suffix = alias 3543 3544 return f"UNNEST({args}){suffix}" 3545 3546 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3547 return "" 3548 3549 def where_sql(self, expression: exp.Where) -> str: 3550 this = self.indent(self.sql(expression, "this")) 3551 return f"{self.seg('WHERE')}{self.sep()}{this}" 3552 3553 def window_sql(self, expression: exp.Window) -> str: 3554 this = self.sql(expression, "this") 3555 partition = self.partition_by_sql(expression) 3556 order = expression.args.get("order") 3557 order = self.order_sql(order, flat=True) if order else "" 3558 spec = self.sql(expression, "spec") 3559 alias = self.sql(expression, "alias") 3560 over = self.sql(expression, "over") or "OVER" 3561 3562 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3563 3564 first = expression.args.get("first") 3565 if first is None: 3566 first = "" 3567 else: 3568 first = "FIRST" if first else "LAST" 3569 3570 if not partition and not order and not spec and alias: 3571 return f"{this} {alias}" 3572 3573 args = self.format_args( 3574 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3575 ) 3576 return f"{this} ({args})" 3577 3578 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3579 partition = self.expressions(expression, key="partition_by", flat=True) 3580 return f"PARTITION BY {partition}" if partition else "" 3581 3582 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3583 kind = self.sql(expression, "kind") 3584 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3585 end = ( 3586 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3587 or "CURRENT ROW" 3588 ) 3589 3590 window_spec = f"{kind} BETWEEN {start} AND {end}" 3591 3592 exclude = self.sql(expression, "exclude") 3593 if exclude: 3594 if self.SUPPORTS_WINDOW_EXCLUDE: 3595 window_spec += f" EXCLUDE {exclude}" 3596 else: 3597 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3598 3599 return window_spec 3600 3601 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3602 this = self.sql(expression, "this") 3603 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3604 return f"{this} WITHIN GROUP ({expression_sql})" 3605 3606 def between_sql(self, expression: exp.Between) -> str: 3607 this = self.sql(expression, "this") 3608 low = self.sql(expression, "low") 3609 high = self.sql(expression, "high") 3610 symmetric = expression.args.get("symmetric") 3611 3612 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3613 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3614 3615 flag = ( 3616 " SYMMETRIC" 3617 if symmetric 3618 else " ASYMMETRIC" 3619 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3620 else "" # silently drop ASYMMETRIC – semantics identical 3621 ) 3622 return f"{this} BETWEEN{flag} {low} AND {high}" 3623 3624 def bracket_offset_expressions( 3625 self, expression: exp.Bracket, index_offset: int | None = None 3626 ) -> list[exp.Expr]: 3627 if expression.args.get("json_access"): 3628 return expression.expressions 3629 3630 return apply_index_offset( 3631 expression.this, 3632 expression.expressions, 3633 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3634 dialect=self.dialect, 3635 ) 3636 3637 def bracket_sql(self, expression: exp.Bracket) -> str: 3638 expressions = self.bracket_offset_expressions(expression) 3639 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3640 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3641 3642 def all_sql(self, expression: exp.All) -> str: 3643 this = self.sql(expression, "this") 3644 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3645 this = self.wrap(this) 3646 return f"ALL {this}" 3647 3648 def any_sql(self, expression: exp.Any) -> str: 3649 this = self.sql(expression, "this") 3650 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3651 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3652 this = self.wrap(this) 3653 return f"ANY{this}" 3654 return f"ANY {this}" 3655 3656 def exists_sql(self, expression: exp.Exists) -> str: 3657 return f"EXISTS{self.wrap(expression)}" 3658 3659 def case_sql(self, expression: exp.Case) -> str: 3660 this = self.sql(expression, "this") 3661 statements = [f"CASE {this}" if this else "CASE"] 3662 3663 for e in expression.args["ifs"]: 3664 statements.append(f"WHEN {self.sql(e, 'this')}") 3665 statements.append(f"THEN {self.sql(e, 'true')}") 3666 3667 default = self.sql(expression, "default") 3668 3669 if default: 3670 statements.append(f"ELSE {default}") 3671 3672 statements.append("END") 3673 3674 if self.pretty and self.too_wide(statements): 3675 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3676 3677 return " ".join(statements) 3678 3679 def constraint_sql(self, expression: exp.Constraint) -> str: 3680 this = self.sql(expression, "this") 3681 expressions = self.expressions(expression, flat=True) 3682 return f"CONSTRAINT {this} {expressions}" 3683 3684 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3685 order = expression.args.get("order") 3686 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3687 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3688 3689 def extract_sql(self, expression: exp.Extract) -> str: 3690 import sqlglot.dialects.dialect 3691 3692 this = ( 3693 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3694 if self.NORMALIZE_EXTRACT_DATE_PARTS 3695 else expression.this 3696 ) 3697 if self.EXTRACT_ALLOWS_QUOTES: 3698 this_sql = self.sql(this) 3699 elif isinstance(this, exp.WeekStart): 3700 this_sql = self.weekstart_name(this) 3701 else: 3702 this_sql = this.name 3703 expression_sql = self.sql(expression, "expression") 3704 3705 return f"EXTRACT({this_sql} FROM {expression_sql})" 3706 3707 def trim_sql(self, expression: exp.Trim) -> str: 3708 trim_type = self.sql(expression, "position") 3709 3710 if trim_type == "LEADING": 3711 func_name = "LTRIM" 3712 elif trim_type == "TRAILING": 3713 func_name = "RTRIM" 3714 else: 3715 func_name = "TRIM" 3716 3717 return self.func(func_name, expression.this, expression.expression) 3718 3719 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3720 args = expression.expressions 3721 if isinstance(expression, exp.ConcatWs): 3722 args = args[1:] # Skip the delimiter 3723 3724 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3725 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3726 3727 concat_coalesce = ( 3728 self.dialect.CONCAT_WS_COALESCE 3729 if isinstance(expression, exp.ConcatWs) 3730 else self.dialect.CONCAT_COALESCE 3731 ) 3732 3733 if not concat_coalesce and expression.args.get("coalesce"): 3734 3735 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3736 if not e.type: 3737 import sqlglot.optimizer.annotate_types 3738 3739 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3740 3741 if e.is_string or e.is_type(exp.DType.ARRAY): 3742 return e 3743 3744 return exp.func("coalesce", e, exp.Literal.string("")) 3745 3746 args = [_wrap_with_coalesce(e) for e in args] 3747 3748 return args 3749 3750 def concat_sql(self, expression: exp.Concat) -> str: 3751 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3752 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3753 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3754 # instead of coalescing them to empty string. 3755 import sqlglot.dialects.dialect 3756 3757 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3758 3759 expressions = self.convert_concat_args(expression) 3760 3761 # Some dialects don't allow a single-argument CONCAT call 3762 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3763 return self.sql(expressions[0]) 3764 3765 return self.func("CONCAT", *expressions) 3766 3767 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3768 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3769 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3770 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3771 all_args = expression.expressions 3772 expression.set("coalesce", True) 3773 return self.sql( 3774 exp.case() 3775 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3776 .else_(expression) 3777 ) 3778 3779 return self.func( 3780 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3781 ) 3782 3783 def check_sql(self, expression: exp.Check) -> str: 3784 this = self.sql(expression, key="this") 3785 return f"CHECK ({this})" 3786 3787 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3788 expressions = self.expressions(expression, flat=True) 3789 expressions = f" ({expressions})" if expressions else "" 3790 reference = self.sql(expression, "reference") 3791 reference = f" {reference}" if reference else "" 3792 delete = self.sql(expression, "delete") 3793 delete = f" ON DELETE {delete}" if delete else "" 3794 update = self.sql(expression, "update") 3795 update = f" ON UPDATE {update}" if update else "" 3796 options = self.expressions(expression, key="options", flat=True, sep=" ") 3797 options = f" {options}" if options else "" 3798 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3799 3800 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3801 this = self.sql(expression, "this") 3802 this = f" {this}" if this else "" 3803 expressions = self.expressions(expression, flat=True) 3804 include = self.sql(expression, "include") 3805 options = self.expressions(expression, key="options", flat=True, sep=" ") 3806 options = f" {options}" if options else "" 3807 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3808 3809 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3810 self.unsupported("TIMESERIES primary key columns are not supported") 3811 return self.sql(expression, "this") 3812 3813 def if_sql(self, expression: exp.If) -> str: 3814 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3815 3816 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3817 if self.MATCH_AGAINST_TABLE_PREFIX: 3818 expressions = [] 3819 for expr in expression.expressions: 3820 if isinstance(expr, exp.Table): 3821 expressions.append(f"TABLE {self.sql(expr)}") 3822 else: 3823 expressions.append(expr) 3824 else: 3825 expressions = expression.expressions 3826 3827 modifier = expression.args.get("modifier") 3828 modifier = f" {modifier}" if modifier else "" 3829 return ( 3830 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3831 ) 3832 3833 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3834 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3835 3836 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3837 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3838 3839 if self.QUOTE_JSON_PATH: 3840 path = self.escape_str(path) 3841 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3842 3843 return path 3844 3845 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3846 if isinstance(expression, exp.JSONPathPart): 3847 transform = self.TRANSFORMS.get(expression.__class__) 3848 if not callable(transform): 3849 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3850 return "" 3851 3852 return transform(self, expression) 3853 3854 if isinstance(expression, int): 3855 return str(expression) 3856 3857 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3858 escaped = expression.replace("'", "\\'") 3859 escaped = f"'{escaped}'" 3860 else: 3861 escaped = expression.replace('"', '\\"') 3862 escaped = f'"{escaped}"' 3863 3864 return escaped 3865 3866 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3867 return f"{self.sql(expression, 'this')} FORMAT JSON" 3868 3869 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3870 # Output the Teradata column FORMAT override. 3871 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3872 this = self.sql(expression, "this") 3873 fmt = self.sql(expression, "format") 3874 return f"{this} (FORMAT {fmt})" 3875 3876 def _jsonobject_sql( 3877 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3878 ) -> str: 3879 null_handling = expression.args.get("null_handling") 3880 null_handling = f" {null_handling}" if null_handling else "" 3881 3882 unique_keys = expression.args.get("unique_keys") 3883 if unique_keys is not None: 3884 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3885 else: 3886 unique_keys = "" 3887 3888 return_type = self.sql(expression, "return_type") 3889 return_type = f" RETURNING {return_type}" if return_type else "" 3890 encoding = self.sql(expression, "encoding") 3891 encoding = f" ENCODING {encoding}" if encoding else "" 3892 3893 if not name: 3894 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3895 3896 return self.func( 3897 name, 3898 *expression.expressions, 3899 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3900 ) 3901 3902 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3903 null_handling = expression.args.get("null_handling") 3904 null_handling = f" {null_handling}" if null_handling else "" 3905 return_type = self.sql(expression, "return_type") 3906 return_type = f" RETURNING {return_type}" if return_type else "" 3907 strict = " STRICT" if expression.args.get("strict") else "" 3908 return self.func( 3909 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3910 ) 3911 3912 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3913 this = self.sql(expression, "this") 3914 order = self.sql(expression, "order") 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_ARRAYAGG", 3922 this, 3923 suffix=f"{order}{null_handling}{return_type}{strict})", 3924 ) 3925 3926 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3927 path = self.sql(expression, "path") 3928 path = f" PATH {path}" if path else "" 3929 nested_schema = self.sql(expression, "nested_schema") 3930 3931 if nested_schema: 3932 return f"NESTED{path} {nested_schema}" 3933 3934 this = self.sql(expression, "this") 3935 kind = self.sql(expression, "kind") 3936 kind = f" {kind}" if kind else "" 3937 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3938 3939 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3940 return f"{this}{kind}{format_json}{path}{ordinality}" 3941 3942 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3943 return self.func("COLUMNS", *expression.expressions) 3944 3945 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3946 this = self.sql(expression, "this") 3947 path = self.sql(expression, "path") 3948 path = f", {path}" if path else "" 3949 error_handling = expression.args.get("error_handling") 3950 error_handling = f" {error_handling}" if error_handling else "" 3951 empty_handling = expression.args.get("empty_handling") 3952 empty_handling = f" {empty_handling}" if empty_handling else "" 3953 schema = self.sql(expression, "schema") 3954 return self.func( 3955 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3956 ) 3957 3958 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3959 this = self.sql(expression, "this") 3960 kind = self.sql(expression, "kind") 3961 path = self.sql(expression, "path") 3962 path = f" {path}" if path else "" 3963 as_json = " AS JSON" if expression.args.get("as_json") else "" 3964 return f"{this} {kind}{path}{as_json}" 3965 3966 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3967 this = self.sql(expression, "this") 3968 path = self.sql(expression, "path") 3969 path = f", {path}" if path else "" 3970 expressions = self.expressions(expression) 3971 with_ = ( 3972 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3973 if expressions 3974 else "" 3975 ) 3976 return f"OPENJSON({this}{path}){with_}" 3977 3978 def in_sql(self, expression: exp.In) -> str: 3979 query = expression.args.get("query") 3980 unnest = expression.args.get("unnest") 3981 field = expression.args.get("field") 3982 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3983 3984 if query: 3985 in_sql = self.sql(query) 3986 elif unnest: 3987 in_sql = self.in_unnest_op(unnest) 3988 elif field: 3989 in_sql = self.sql(field) 3990 else: 3991 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3992 3993 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3994 3995 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3996 return f"(SELECT {self.sql(unnest)})" 3997 3998 def interval_sql(self, expression: exp.Interval) -> str: 3999 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4000 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4001 exp.AutoRefreshProperty, 4002 ) 4003 interval_keyword = "INTERVAL" if include_keyword else "" 4004 unit_expression = expression.args.get("unit") 4005 unit = self.sql(unit_expression) if unit_expression else "" 4006 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4007 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4008 unit = f" {unit}" if unit else "" 4009 4010 if self.SINGLE_STRING_INTERVAL: 4011 this = expression.this.name if expression.this else "" 4012 if this: 4013 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4014 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4015 return f"{interval_keyword}'{this}'{unit}" 4016 return f"{interval_keyword}'{this}{unit}'" 4017 return f"{interval_keyword}{unit}" 4018 4019 this = self.sql(expression, "this") 4020 if this: 4021 if not include_keyword and expression.this.is_string: 4022 this = expression.this.name 4023 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4024 this = f"({this})" 4025 if include_keyword: 4026 this = f" {this}" 4027 4028 return f"{interval_keyword}{this}{unit}" 4029 4030 def return_sql(self, expression: exp.Return) -> str: 4031 return f"RETURN {self.sql(expression, 'this')}" 4032 4033 def reference_sql(self, expression: exp.Reference) -> str: 4034 this = self.sql(expression, "this") 4035 expressions = self.expressions(expression, flat=True) 4036 expressions = f"({expressions})" if expressions else "" 4037 options = self.expressions(expression, key="options", flat=True, sep=" ") 4038 options = f" {options}" if options else "" 4039 return f"REFERENCES {this}{expressions}{options}" 4040 4041 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4042 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4043 parent = expression.parent 4044 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4045 4046 return self.func( 4047 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4048 ) 4049 4050 def paren_sql(self, expression: exp.Paren) -> str: 4051 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4052 return f"({sql}{self.seg(')', sep='')}" 4053 4054 def neg_sql(self, expression: exp.Neg) -> str: 4055 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4056 this_sql = self.sql(expression, "this") 4057 sep = " " if this_sql[0] == "-" else "" 4058 return f"-{sep}{this_sql}" 4059 4060 def not_sql(self, expression: exp.Not) -> str: 4061 return f"NOT {self.sql(expression, 'this')}" 4062 4063 def alias_sql(self, expression: exp.Alias) -> str: 4064 alias = self.sql(expression, "alias") 4065 alias = f" AS {alias}" if alias else "" 4066 return f"{self.sql(expression, 'this')}{alias}" 4067 4068 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4069 alias = expression.args["alias"] 4070 4071 parent = expression.parent 4072 pivot = parent and parent.parent 4073 4074 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4075 identifier_alias = isinstance(alias, exp.Identifier) 4076 literal_alias = isinstance(alias, exp.Literal) 4077 4078 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4079 alias.replace(exp.Literal.string(alias.output_name)) 4080 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4081 alias.replace(exp.to_identifier(alias.output_name)) 4082 4083 return self.alias_sql(expression) 4084 4085 def aliases_sql(self, expression: exp.Aliases) -> str: 4086 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4087 4088 def atindex_sql(self, expression: exp.AtIndex) -> str: 4089 this = self.sql(expression, "this") 4090 index = self.sql(expression, "expression") 4091 return f"{this} AT {index}" 4092 4093 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4094 this = self.sql(expression, "this") 4095 zone = self.sql(expression, "zone") 4096 return f"{this} AT TIME ZONE {zone}" 4097 4098 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4099 this = self.sql(expression, "this") 4100 zone = self.sql(expression, "zone") 4101 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4102 4103 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4104 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4105 4106 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4107 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4108 4109 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4110 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4111 4112 def add_sql(self, expression: exp.Add) -> str: 4113 return self.binary(expression, "+") 4114 4115 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4116 return self.connector_sql(expression, "AND", stack) 4117 4118 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4119 return self.connector_sql(expression, "OR", stack) 4120 4121 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4122 return self.connector_sql(expression, "XOR", stack) 4123 4124 def connector_sql( 4125 self, 4126 expression: exp.Connector, 4127 op: str, 4128 stack: list[str | exp.Expr] | None = None, 4129 ) -> str: 4130 if stack is not None: 4131 stack.append(expression.right) 4132 if expression.comments and self.comments: 4133 op = self.maybe_comment(op, comments=expression.comments) 4134 4135 stack.extend((op, expression.left)) 4136 return op 4137 4138 stack = [expression] 4139 sqls: list[str] = [] 4140 ops = set() 4141 4142 while stack: 4143 node = stack.pop() 4144 if isinstance(node, exp.Connector): 4145 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4146 else: 4147 sql = self.sql(node) 4148 if sqls and sqls[-1] in ops: 4149 sqls[-1] += f" {sql}" 4150 else: 4151 sqls.append(sql) 4152 4153 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4154 return sep.join(sqls) 4155 4156 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4157 return self.binary(expression, "&") 4158 4159 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4160 return self.binary(expression, "<<") 4161 4162 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4163 return f"~{self.sql(expression, 'this')}" 4164 4165 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4166 return self.binary(expression, "|") 4167 4168 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4169 return self.binary(expression, ">>") 4170 4171 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4172 return self.binary(expression, "^") 4173 4174 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4175 format_sql = self.sql(expression, "format") 4176 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4177 to_sql = self.sql(expression, "to") 4178 to_sql = f" {to_sql}" if to_sql else "" 4179 action = self.sql(expression, "action") 4180 action = f" {action}" if action else "" 4181 default = self.sql(expression, "default") 4182 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4183 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4184 4185 # Base implementation that excludes safe, zone, and target_type metadata args 4186 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4187 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4188 4189 # Base implementation that excludes the safe and default_year metadata args 4190 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4191 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4192 4193 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4194 return self.func( 4195 "PARSE_DATETIME", 4196 expression.this, 4197 expression.args.get("format"), 4198 expression.args.get("zone"), 4199 ) 4200 4201 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4202 zone = self.sql(expression, "this") 4203 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4204 4205 def collate_sql(self, expression: exp.Collate) -> str: 4206 if self.COLLATE_IS_FUNC: 4207 return self.function_fallback_sql(expression) 4208 return self.binary(expression, "COLLATE") 4209 4210 def command_sql(self, expression: exp.Command) -> str: 4211 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4212 4213 def comment_sql(self, expression: exp.Comment) -> str: 4214 this = self.sql(expression, "this") 4215 kind = expression.args["kind"] 4216 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4217 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4218 expression_sql = self.sql(expression, "expression") 4219 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4220 4221 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4222 this = self.sql(expression, "this") 4223 delete = " DELETE" if expression.args.get("delete") else "" 4224 recompress = self.sql(expression, "recompress") 4225 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4226 to_disk = self.sql(expression, "to_disk") 4227 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4228 to_volume = self.sql(expression, "to_volume") 4229 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4230 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4231 4232 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4233 where = self.sql(expression, "where") 4234 group = self.sql(expression, "group") 4235 aggregates = self.expressions(expression, key="aggregates") 4236 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4237 4238 if not (where or group or aggregates) and len(expression.expressions) == 1: 4239 return f"TTL {self.expressions(expression, flat=True)}" 4240 4241 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4242 4243 def transaction_sql(self, expression: exp.Transaction) -> str: 4244 modes = self.expressions(expression, key="modes") 4245 modes = f" {modes}" if modes else "" 4246 return f"BEGIN{modes}" 4247 4248 def commit_sql(self, expression: exp.Commit) -> str: 4249 chain = expression.args.get("chain") 4250 if chain is not None: 4251 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4252 4253 return f"COMMIT{chain or ''}" 4254 4255 def rollback_sql(self, expression: exp.Rollback) -> str: 4256 savepoint = expression.args.get("savepoint") 4257 savepoint = f" TO {savepoint}" if savepoint else "" 4258 return f"ROLLBACK{savepoint}" 4259 4260 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4261 this = self.sql(expression, "this") 4262 4263 exists = "" 4264 if expression.args.get("exists"): 4265 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4266 exists = " IF EXISTS" 4267 else: 4268 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4269 4270 dtype = self.sql(expression, "dtype") 4271 if dtype: 4272 collate = self.sql(expression, "collate") 4273 collate = f" COLLATE {collate}" if collate else "" 4274 using = self.sql(expression, "using") 4275 using = f" USING {using}" if using else "" 4276 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4277 null_constraint = self._alter_column_null_constraint_sql(expression) 4278 4279 return ( 4280 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4281 f"{collate}{using}{null_constraint}" 4282 ) 4283 4284 default = self.sql(expression, "default") 4285 if default: 4286 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4287 4288 comment = self.sql(expression, "comment") 4289 if comment: 4290 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4291 4292 visible = expression.args.get("visible") 4293 if visible: 4294 return f"ALTER COLUMN{exists} {this} SET {visible}" 4295 4296 allow_null = expression.args.get("allow_null") 4297 drop = expression.args.get("drop") 4298 4299 if not drop and not allow_null: 4300 self.unsupported("Unsupported ALTER COLUMN syntax") 4301 4302 if allow_null is not None: 4303 keyword = "DROP" if drop else "SET" 4304 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4305 4306 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4307 4308 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4309 allow_null = expression.args.get("allow_null") 4310 if allow_null is None: 4311 return "" 4312 4313 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4314 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4315 return "" 4316 4317 return " NULL" if allow_null else " NOT NULL" 4318 4319 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4320 this = self.sql(expression, "this") 4321 rename_from = self.sql(expression, "rename_from") 4322 if rename_from: 4323 if not self.SUPPORTS_CHANGE_COLUMN: 4324 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4325 return f"CHANGE COLUMN {rename_from} {this}" 4326 if not self.SUPPORTS_MODIFY_COLUMN: 4327 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4328 return f"MODIFY COLUMN {this}" 4329 4330 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4331 this = self.sql(expression, "this") 4332 4333 visible = expression.args.get("visible") 4334 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4335 4336 return f"ALTER INDEX {this} {visible_sql}" 4337 4338 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4339 this = self.sql(expression, "this") 4340 if not isinstance(expression.this, exp.Var): 4341 this = f"KEY DISTKEY {this}" 4342 return f"ALTER DISTSTYLE {this}" 4343 4344 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4345 compound = " COMPOUND" if expression.args.get("compound") else "" 4346 this = self.sql(expression, "this") 4347 expressions = self.expressions(expression, flat=True) 4348 expressions = f"({expressions})" if expressions else "" 4349 return f"ALTER{compound} SORTKEY {this or expressions}" 4350 4351 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4352 if not self.RENAME_TABLE_WITH_DB: 4353 # Remove db from tables 4354 expression = expression.transform( 4355 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4356 ).assert_is(exp.AlterRename) 4357 this = self.sql(expression, "this") 4358 to_kw = " TO" if include_to else "" 4359 return f"RENAME{to_kw} {this}" 4360 4361 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4362 exists = " IF EXISTS" if expression.args.get("exists") else "" 4363 old_column = self.sql(expression, "this") 4364 new_column = self.sql(expression, "to") 4365 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4366 4367 def alterset_sql(self, expression: exp.AlterSet) -> str: 4368 exprs = self.expressions(expression, flat=True) 4369 if self.ALTER_SET_WRAPPED: 4370 exprs = f"({exprs})" 4371 4372 return f"SET {exprs}" 4373 4374 def alter_sql(self, expression: exp.Alter) -> str: 4375 actions = expression.args["actions"] 4376 4377 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4378 actions[0], exp.ColumnDef 4379 ): 4380 actions_sql = self.expressions(expression, key="actions", flat=True) 4381 actions_sql = f"ADD {actions_sql}" 4382 else: 4383 actions_list = [] 4384 for action in actions: 4385 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4386 action_sql = self.add_column_sql(action) 4387 else: 4388 action_sql = self.sql(action) 4389 if isinstance(action, exp.Query): 4390 action_sql = f"AS {action_sql}" 4391 4392 actions_list.append(action_sql) 4393 4394 actions_sql = self.format_args(*actions_list).lstrip("\n") 4395 4396 iceberg = ( 4397 "ICEBERG " 4398 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4399 else "" 4400 ) 4401 exists = " IF EXISTS" if expression.args.get("exists") else "" 4402 on_cluster = self.sql(expression, "cluster") 4403 on_cluster = f" {on_cluster}" if on_cluster else "" 4404 only = " ONLY" if expression.args.get("only") else "" 4405 options = self.expressions(expression, key="options") 4406 options = f", {options}" if options else "" 4407 kind = self.sql(expression, "kind") 4408 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4409 check = " WITH CHECK" if expression.args.get("check") else "" 4410 cascade = ( 4411 " CASCADE" 4412 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4413 else "" 4414 ) 4415 this = self.sql(expression, "this") 4416 this = f" {this}" if this else "" 4417 4418 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4419 4420 def altersession_sql(self, expression: exp.AlterSession) -> str: 4421 items_sql = self.expressions(expression, flat=True) 4422 keyword = "UNSET" if expression.args.get("unset") else "SET" 4423 return f"{keyword} {items_sql}" 4424 4425 def add_column_sql(self, expression: exp.Expr) -> str: 4426 sql = self.sql(expression) 4427 if isinstance(expression, exp.Schema): 4428 column_text = " COLUMNS" 4429 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4430 column_text = " COLUMN" 4431 else: 4432 column_text = "" 4433 4434 return f"ADD{column_text} {sql}" 4435 4436 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4437 expressions = self.expressions(expression) 4438 exists = " IF EXISTS " if expression.args.get("exists") else " " 4439 return f"DROP{exists}{expressions}" 4440 4441 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4442 return "DROP PRIMARY KEY" 4443 4444 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4445 return f"ADD {self.expressions(expression, indent=False)}" 4446 4447 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4448 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4449 location = self.sql(expression, "location") 4450 location = f" {location}" if location else "" 4451 return f"ADD {exists}{self.sql(expression.this)}{location}" 4452 4453 def distinct_sql(self, expression: exp.Distinct) -> str: 4454 this = self.expressions(expression, flat=True) 4455 4456 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4457 case = exp.case() 4458 for arg in expression.expressions: 4459 case = case.when(arg.is_(exp.null()), exp.null()) 4460 this = self.sql(case.else_(f"({this})")) 4461 4462 this = f" {this}" if this else "" 4463 4464 on = self.sql(expression, "on") 4465 on = f" ON {on}" if on else "" 4466 return f"DISTINCT{this}{on}" 4467 4468 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4469 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4470 4471 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4472 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4473 4474 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4475 this_sql = self.sql(expression, "this") 4476 expression_sql = self.sql(expression, "expression") 4477 kind = "MAX" if expression.args.get("max") else "MIN" 4478 return f"{this_sql} HAVING {kind} {expression_sql}" 4479 4480 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4481 return self.sql( 4482 exp.Cast( 4483 this=exp.Div(this=expression.this, expression=expression.expression), 4484 to=exp.DataType(this=exp.DType.INT), 4485 ) 4486 ) 4487 4488 def dpipe_sql(self, expression: exp.DPipe) -> str: 4489 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4490 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4491 return self.binary(expression, "||") 4492 4493 def div_sql(self, expression: exp.Div) -> str: 4494 l, r = expression.left, expression.right 4495 4496 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4497 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4498 4499 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4500 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4501 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4502 4503 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4504 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4505 return self.sql( 4506 exp.cast( 4507 l / r, 4508 to=exp.DType.BIGINT, 4509 ) 4510 ) 4511 4512 return self.binary(expression, "/") 4513 4514 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4515 n = exp._wrap(expression.this, exp.Binary) 4516 d = exp._wrap(expression.expression, exp.Binary) 4517 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4518 4519 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4520 return self.binary(expression, "OVERLAPS") 4521 4522 def distance_sql(self, expression: exp.Distance) -> str: 4523 return self.binary(expression, "<->") 4524 4525 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4526 return self.binary(expression, "<<->>") 4527 4528 def dot_sql(self, expression: exp.Dot) -> str: 4529 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4530 4531 def eq_sql(self, expression: exp.EQ) -> str: 4532 return self.binary(expression, "=") 4533 4534 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4535 return self.binary(expression, ":=") 4536 4537 def escape_sql(self, expression: exp.Escape) -> str: 4538 this = expression.this 4539 if ( 4540 isinstance(this, (exp.Like, exp.ILike)) 4541 and isinstance(this.expression, (exp.All, exp.Any)) 4542 and not self.SUPPORTS_LIKE_QUANTIFIERS 4543 ): 4544 return self._like_sql(this, escape=expression) 4545 return self.binary(expression, "ESCAPE") 4546 4547 def glob_sql(self, expression: exp.Glob) -> str: 4548 return self.binary(expression, "GLOB") 4549 4550 def gt_sql(self, expression: exp.GT) -> str: 4551 return self.binary(expression, ">") 4552 4553 def gte_sql(self, expression: exp.GTE) -> str: 4554 return self.binary(expression, ">=") 4555 4556 def is_sql(self, expression: exp.Is) -> str: 4557 negate = expression.args.get("negate") 4558 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4559 positive = bool(expression.expression.this) != bool(negate) 4560 return self.sql(expression.this if positive else exp.not_(expression.this)) 4561 return self.binary(expression, "IS NOT" if negate else "IS") 4562 4563 def _like_sql( 4564 self, 4565 expression: exp.Like | exp.ILike, 4566 escape: exp.Escape | None = None, 4567 ) -> str: 4568 this = expression.this 4569 rhs = expression.expression 4570 4571 if isinstance(expression, exp.Like): 4572 exp_class: type[exp.Like | exp.ILike] = exp.Like 4573 op = "LIKE" 4574 else: 4575 exp_class = exp.ILike 4576 op = "ILIKE" 4577 4578 if expression.args.get("negate"): 4579 op = f"NOT {op}" 4580 4581 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4582 exprs = rhs.this.unnest() 4583 4584 if isinstance(exprs, exp.Tuple): 4585 exprs = exprs.expressions 4586 else: 4587 exprs = [exprs] 4588 4589 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4590 4591 def _make_like(expr: exp.Expression) -> exp.Expression: 4592 like: exp.Expression = exp_class( 4593 this=this, expression=expr, negate=expression.args.get("negate") 4594 ) 4595 if escape: 4596 like = exp.Escape(this=like, expression=escape.expression.copy()) 4597 return like 4598 4599 like_expr: exp.Expr = _make_like(exprs[0]) 4600 for expr in exprs[1:]: 4601 like_expr = connective(like_expr, _make_like(expr), copy=False) 4602 4603 parent = escape.parent if escape else expression.parent 4604 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4605 parent, exp.Condition 4606 ): 4607 like_expr = exp.paren(like_expr, copy=False) 4608 4609 return self.sql(like_expr) 4610 4611 return self.binary(expression, op) 4612 4613 def like_sql(self, expression: exp.Like) -> str: 4614 return self._like_sql(expression) 4615 4616 def ilike_sql(self, expression: exp.ILike) -> str: 4617 return self._like_sql(expression) 4618 4619 def match_sql(self, expression: exp.Match) -> str: 4620 return self.binary(expression, "MATCH") 4621 4622 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4623 return self.binary(expression, "SIMILAR TO") 4624 4625 def lt_sql(self, expression: exp.LT) -> str: 4626 return self.binary(expression, "<") 4627 4628 def lte_sql(self, expression: exp.LTE) -> str: 4629 return self.binary(expression, "<=") 4630 4631 def mod_sql(self, expression: exp.Mod) -> str: 4632 this = self.sql(expression, "this") 4633 expr = self.sql(expression, "expression") 4634 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4635 4636 parent = expression.parent 4637 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4638 return f"({sql})" 4639 4640 return sql 4641 4642 def mul_sql(self, expression: exp.Mul) -> str: 4643 return self.binary(expression, "*") 4644 4645 def neq_sql(self, expression: exp.NEQ) -> str: 4646 return self.binary(expression, "<>") 4647 4648 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4649 return self.binary(expression, "IS NOT DISTINCT FROM") 4650 4651 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4652 return self.binary(expression, "IS DISTINCT FROM") 4653 4654 def sub_sql(self, expression: exp.Sub) -> str: 4655 return self.binary(expression, "-") 4656 4657 def trycast_sql(self, expression: exp.TryCast) -> str: 4658 return self.cast_sql(expression, safe_prefix="TRY_") 4659 4660 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4661 return self.cast_sql(expression) 4662 4663 def try_sql(self, expression: exp.Try) -> str: 4664 if not self.TRY_SUPPORTED: 4665 self.unsupported("Unsupported TRY function") 4666 return self.sql(expression, "this") 4667 4668 return self.func("TRY", expression.this) 4669 4670 def log_sql(self, expression: exp.Log) -> str: 4671 this = expression.this 4672 expr = expression.expression 4673 4674 if self.dialect.LOG_BASE_FIRST is False: 4675 this, expr = expr, this 4676 elif self.dialect.LOG_BASE_FIRST is None and expr: 4677 if this.name in ("2", "10"): 4678 return self.func(f"LOG{this.name}", expr) 4679 4680 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4681 4682 return self.func("LOG", this, expr) 4683 4684 def use_sql(self, expression: exp.Use) -> str: 4685 kind = self.sql(expression, "kind") 4686 kind = f" {kind}" if kind else "" 4687 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4688 this = f" {this}" if this else "" 4689 return f"USE{kind}{this}" 4690 4691 def binary(self, expression: exp.Binary, op: str) -> str: 4692 sqls: list[str] = [] 4693 stack: list[None | str | exp.Expr] = [expression] 4694 binary_type = type(expression) 4695 4696 while stack: 4697 node = stack.pop() 4698 4699 if type(node) is binary_type: 4700 op_func = node.args.get("operator") 4701 if op_func: 4702 op = f"OPERATOR({self.sql(op_func)})" 4703 4704 stack.append(node.args.get("expression")) 4705 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4706 stack.append(node.args.get("this")) 4707 else: 4708 sqls.append(self.sql(node)) 4709 4710 return "".join(sqls) 4711 4712 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4713 to_clause = self.sql(expression, "to") 4714 if to_clause: 4715 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4716 4717 return self.function_fallback_sql(expression) 4718 4719 def function_fallback_sql(self, expression: exp.Func) -> str: 4720 args = [] 4721 4722 for key in expression.arg_types: 4723 arg_value = expression.args.get(key) 4724 4725 if isinstance(arg_value, list): 4726 for value in arg_value: 4727 args.append(value) 4728 elif arg_value is not None: 4729 args.append(arg_value) 4730 4731 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4732 name = expression.meta_get("name") or expression.sql_name() 4733 else: 4734 name = expression.sql_name() 4735 4736 return self.func(name, *args) 4737 4738 def func( 4739 self, 4740 name: str, 4741 *args: t.Any, 4742 prefix: str = "(", 4743 suffix: str = ")", 4744 normalize: bool = True, 4745 ) -> str: 4746 name = self.normalize_func(name) if normalize else name 4747 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4748 4749 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4750 arg_sqls = tuple( 4751 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4752 ) 4753 if self.pretty and self.too_wide(arg_sqls): 4754 return self.indent( 4755 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4756 ) 4757 return sep.join(arg_sqls) 4758 4759 def too_wide(self, args: t.Iterable) -> bool: 4760 return sum(len(arg) for arg in args) > self.max_text_width 4761 4762 def format_time( 4763 self, 4764 expression: exp.Expr, 4765 inverse_time_mapping: dict[str, str] | None = None, 4766 inverse_time_trie: dict | None = None, 4767 ) -> str | None: 4768 return format_time( 4769 self.sql(expression, "format"), 4770 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4771 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4772 ) 4773 4774 def expressions( 4775 self, 4776 expression: exp.Expr | None = None, 4777 key: str | None = None, 4778 sqls: t.Collection[str | exp.Expr] | None = None, 4779 flat: bool = False, 4780 indent: bool = True, 4781 skip_first: bool = False, 4782 skip_last: bool = False, 4783 sep: str = ", ", 4784 prefix: str = "", 4785 dynamic: bool = False, 4786 new_line: bool = False, 4787 ) -> str: 4788 expressions = expression.args.get(key or "expressions") if expression else sqls 4789 4790 if not expressions: 4791 return "" 4792 4793 if flat: 4794 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4795 4796 num_sqls = len(expressions) 4797 result_sqls = [] 4798 4799 for i, e in enumerate(expressions): 4800 sql = self.sql(e, comment=False) 4801 if not sql: 4802 continue 4803 4804 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4805 4806 if self.pretty: 4807 if self.leading_comma: 4808 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4809 else: 4810 result_sqls.append( 4811 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4812 ) 4813 else: 4814 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4815 4816 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4817 if new_line: 4818 result_sqls.insert(0, "") 4819 result_sqls.append("") 4820 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4821 else: 4822 result_sql = "".join(result_sqls) 4823 4824 return ( 4825 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4826 if indent 4827 else result_sql 4828 ) 4829 4830 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4831 flat = flat or isinstance(expression.parent, exp.Properties) 4832 expressions_sql = self.expressions(expression, flat=flat) 4833 if flat: 4834 return f"{op} {expressions_sql}" 4835 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4836 4837 def naked_property(self, expression: exp.Property) -> str: 4838 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4839 if not property_name: 4840 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4841 return f"{property_name} {self.sql(expression, 'this')}" 4842 4843 def tag_sql(self, expression: exp.Tag) -> str: 4844 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4845 4846 def token_sql(self, token_type: TokenType) -> str: 4847 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4848 4849 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4850 this = self.sql(expression, "this") 4851 expressions = self.no_identify(self.expressions, expression) 4852 expressions = ( 4853 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4854 ) 4855 return f"{this}{expressions}" if expressions.strip() != "" else this 4856 4857 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4858 return self.expressions(expression, flat=True) 4859 4860 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4861 params = self.no_identify(self.expressions, expression, flat=True) 4862 body = self.sql(expression, "this") 4863 prefix = "TABLE " if expression.args.get("is_table") else "" 4864 return f"({params}) AS {prefix}{body}" 4865 4866 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4867 this = self.sql(expression, "this") 4868 expressions = self.expressions(expression, flat=True) 4869 return f"{this}({expressions})" 4870 4871 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4872 return self.binary(expression, "=>") 4873 4874 def when_sql(self, expression: exp.When) -> str: 4875 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4876 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4877 condition = self.sql(expression, "condition") 4878 condition = f" AND {condition}" if condition else "" 4879 4880 then_expression = expression.args.get("then") 4881 if isinstance(then_expression, exp.Insert): 4882 this = self.sql(then_expression, "this") 4883 this = f"INSERT {this}" if this else "INSERT" 4884 then = self.sql(then_expression, "expression") 4885 then = f"{this} VALUES {then}" if then else this 4886 elif isinstance(then_expression, exp.Update): 4887 if isinstance(then_expression.args.get("expressions"), exp.Star): 4888 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4889 else: 4890 expressions_sql = self.expressions(then_expression) 4891 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4892 else: 4893 then = self.sql(then_expression) 4894 4895 if isinstance(then_expression, (exp.Insert, exp.Update)): 4896 where = self.sql(then_expression, "where") 4897 if where and not self.SUPPORTS_MERGE_WHERE: 4898 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4899 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4900 where = "" 4901 then = f"{then}{where}" 4902 return f"WHEN {matched}{source}{condition} THEN {then}" 4903 4904 def whens_sql(self, expression: exp.Whens) -> str: 4905 return self.expressions(expression, sep=" ", indent=False) 4906 4907 def merge_sql(self, expression: exp.Merge) -> str: 4908 table = expression.this 4909 table_alias = "" 4910 4911 hints = table.args.get("hints") 4912 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4913 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4914 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4915 4916 this = self.sql(table) 4917 using = f"USING {self.sql(expression, 'using')}" 4918 whens = self.sql(expression, "whens") 4919 4920 on = self.sql(expression, "on") 4921 on = f"ON {on}" if on else "" 4922 4923 if not on: 4924 on = self.expressions(expression, key="using_cond") 4925 on = f"USING ({on})" if on else "" 4926 4927 returning = self.sql(expression, "returning") 4928 if returning: 4929 whens = f"{whens}{returning}" 4930 4931 sep = self.sep() 4932 4933 return self.prepend_ctes( 4934 expression, 4935 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4936 ) 4937 4938 @unsupported_args("format") 4939 def tochar_sql(self, expression: exp.ToChar) -> str: 4940 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4941 4942 @unsupported_args("default") 4943 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4944 if not self.SUPPORTS_TO_NUMBER: 4945 self.unsupported("Unsupported TO_NUMBER function") 4946 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4947 4948 fmt = expression.args.get("format") 4949 if not fmt: 4950 self.unsupported("Conversion format is required for TO_NUMBER") 4951 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4952 4953 return self.func("TO_NUMBER", expression.this, fmt) 4954 4955 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4956 this = self.sql(expression, "this") 4957 kind = self.sql(expression, "kind") 4958 settings_sql = self.expressions(expression, key="settings", sep=" ") 4959 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4960 return f"{this}({kind}{args})" 4961 4962 def dictrange_sql(self, expression: exp.DictRange) -> str: 4963 this = self.sql(expression, "this") 4964 max = self.sql(expression, "max") 4965 min = self.sql(expression, "min") 4966 return f"{this}(MIN {min} MAX {max})" 4967 4968 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4969 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4970 4971 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4972 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4973 4974 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4975 def uniquekeyproperty_sql( 4976 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4977 ) -> str: 4978 return f"{prefix} ({self.expressions(expression, flat=True)})" 4979 4980 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4981 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4982 expressions = self.expressions(expression, flat=True) 4983 expressions = f" {self.wrap(expressions)}" if expressions else "" 4984 buckets = self.sql(expression, "buckets") 4985 kind = self.sql(expression, "kind") 4986 buckets = f" BUCKETS {buckets}" if buckets else "" 4987 order = self.sql(expression, "order") 4988 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4989 4990 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4991 return "" 4992 4993 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4994 expressions = self.expressions(expression, key="expressions", flat=True) 4995 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4996 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4997 buckets = self.sql(expression, "buckets") 4998 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4999 5000 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5001 this = self.sql(expression, "this") 5002 having = self.sql(expression, "having") 5003 5004 if having: 5005 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5006 5007 return self.func("ANY_VALUE", this) 5008 5009 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5010 transform = self.func("TRANSFORM", *expression.expressions) 5011 row_format_before = self.sql(expression, "row_format_before") 5012 row_format_before = f" {row_format_before}" if row_format_before else "" 5013 record_writer = self.sql(expression, "record_writer") 5014 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5015 using = f" USING {self.sql(expression, 'command_script')}" 5016 schema = self.sql(expression, "schema") 5017 schema = f" AS {schema}" if schema else "" 5018 row_format_after = self.sql(expression, "row_format_after") 5019 row_format_after = f" {row_format_after}" if row_format_after else "" 5020 record_reader = self.sql(expression, "record_reader") 5021 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5022 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5023 5024 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5025 key_block_size = self.sql(expression, "key_block_size") 5026 if key_block_size: 5027 return f"KEY_BLOCK_SIZE = {key_block_size}" 5028 5029 using = self.sql(expression, "using") 5030 if using: 5031 return f"USING {using}" 5032 5033 parser = self.sql(expression, "parser") 5034 if parser: 5035 return f"WITH PARSER {parser}" 5036 5037 comment = self.sql(expression, "comment") 5038 if comment: 5039 return f"COMMENT {comment}" 5040 5041 visible = expression.args.get("visible") 5042 if visible is not None: 5043 return "VISIBLE" if visible else "INVISIBLE" 5044 5045 engine_attr = self.sql(expression, "engine_attr") 5046 if engine_attr: 5047 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5048 5049 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5050 if secondary_engine_attr: 5051 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5052 5053 self.unsupported("Unsupported index constraint option.") 5054 return "" 5055 5056 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5057 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5058 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5059 5060 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5061 kind = self.sql(expression, "kind") 5062 kind = f"{kind} INDEX" if kind else "INDEX" 5063 this = self.sql(expression, "this") 5064 this = f" {this}" if this else "" 5065 index_type = self.sql(expression, "index_type") 5066 index_type = f" USING {index_type}" if index_type else "" 5067 expressions = self.expressions(expression, flat=True) 5068 expressions = f" ({expressions})" if expressions else "" 5069 options = self.expressions(expression, key="options", sep=" ") 5070 options = f" {options}" if options else "" 5071 return f"{kind}{this}{index_type}{expressions}{options}" 5072 5073 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5074 if self.NVL2_SUPPORTED: 5075 return self.function_fallback_sql(expression) 5076 5077 case = exp.Case().when( 5078 expression.this.is_(exp.null()).not_(copy=False), 5079 expression.args["true"], 5080 copy=False, 5081 ) 5082 else_cond = expression.args.get("false") 5083 if else_cond: 5084 case.else_(else_cond, copy=False) 5085 5086 return self.sql(case) 5087 5088 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5089 if expression.args.get("from_first") is False: 5090 self.unsupported("NTH_VALUE FROM LAST is not supported") 5091 5092 return self.function_fallback_sql(expression) 5093 5094 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5095 this = self.sql(expression, "this") 5096 expr = self.sql(expression, "expression") 5097 position = self.sql(expression, "position") 5098 position = f", {position}" if position else "" 5099 iterator = self.sql(expression, "iterator") 5100 condition = self.sql(expression, "condition") 5101 condition = f" IF {condition}" if condition else "" 5102 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5103 5104 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5105 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5106 5107 def opclass_sql(self, expression: exp.Opclass) -> str: 5108 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5109 5110 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5111 model = self.sql(expression, "this") 5112 model = f"MODEL {model}" 5113 expr = expression.expression 5114 if expr: 5115 expr_sql = self.sql(expression, "expression") 5116 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5117 else: 5118 expr_sql = None 5119 5120 parameters = self.sql(expression, "params_struct") or None 5121 5122 return self.func(name, model, expr_sql, parameters) 5123 5124 def predict_sql(self, expression: exp.Predict) -> str: 5125 return self._ml_sql(expression, "PREDICT") 5126 5127 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5128 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5129 return self._ml_sql(expression, name) 5130 5131 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5132 return self._ml_sql(expression, "GENERATE_TEXT") 5133 5134 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5135 return self._ml_sql(expression, "GENERATE_TABLE") 5136 5137 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5138 return self._ml_sql(expression, "GENERATE_BOOL") 5139 5140 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5141 return self._ml_sql(expression, "GENERATE_INT") 5142 5143 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5144 return self._ml_sql(expression, "GENERATE_DOUBLE") 5145 5146 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5147 return self._ml_sql(expression, "TRANSLATE") 5148 5149 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5150 return self._ml_sql(expression, "FORECAST") 5151 5152 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5153 this_sql = self.sql(expression, "this") 5154 if isinstance(expression.this, exp.Table): 5155 this_sql = f"TABLE {this_sql}" 5156 5157 return self.func( 5158 "FORECAST", 5159 this_sql, 5160 expression.args.get("data_col"), 5161 expression.args.get("timestamp_col"), 5162 expression.args.get("model"), 5163 expression.args.get("id_cols"), 5164 expression.args.get("horizon"), 5165 expression.args.get("forecast_end_timestamp"), 5166 expression.args.get("confidence_level"), 5167 expression.args.get("output_historical_time_series"), 5168 expression.args.get("context_window"), 5169 ) 5170 5171 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5172 this_sql = self.sql(expression, "this") 5173 if isinstance(expression.this, exp.Table): 5174 this_sql = f"TABLE {this_sql}" 5175 5176 return self.func( 5177 "FEATURES_AT_TIME", 5178 this_sql, 5179 expression.args.get("time"), 5180 expression.args.get("num_rows"), 5181 expression.args.get("ignore_feature_nulls"), 5182 ) 5183 5184 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5185 this_sql = self.sql(expression, "this") 5186 if isinstance(expression.this, exp.Table): 5187 this_sql = f"TABLE {this_sql}" 5188 5189 query_table = self.sql(expression, "query_table") 5190 if isinstance(expression.args["query_table"], exp.Table): 5191 query_table = f"TABLE {query_table}" 5192 5193 return self.func( 5194 "VECTOR_SEARCH", 5195 this_sql, 5196 expression.args.get("column_to_search"), 5197 query_table, 5198 expression.args.get("query_column_to_search"), 5199 expression.args.get("top_k"), 5200 expression.args.get("distance_type"), 5201 expression.args.get("options"), 5202 ) 5203 5204 def forin_sql(self, expression: exp.ForIn) -> str: 5205 this = self.sql(expression, "this") 5206 expression_sql = self.sql(expression, "expression") 5207 return f"FOR {this} DO {expression_sql}" 5208 5209 def refresh_sql(self, expression: exp.Refresh) -> str: 5210 this = self.sql(expression, "this") 5211 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5212 return f"REFRESH {kind}{this}" 5213 5214 def toarray_sql(self, expression: exp.ToArray) -> str: 5215 arg = expression.this 5216 if not arg.type: 5217 import sqlglot.optimizer.annotate_types 5218 5219 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5220 5221 if arg.is_type(exp.DType.ARRAY): 5222 return self.sql(arg) 5223 5224 cond_for_null = arg.is_(exp.null()) 5225 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5226 5227 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5228 this = expression.this 5229 time_format = self.format_time(expression) 5230 5231 if time_format: 5232 return self.sql( 5233 exp.cast( 5234 exp.StrToTime(this=this, format=expression.args["format"]), 5235 exp.DType.TIME, 5236 ) 5237 ) 5238 5239 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5240 return self.sql(this) 5241 5242 return self.sql(exp.cast(this, exp.DType.TIME)) 5243 5244 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5245 this = expression.this 5246 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5247 return self.sql(this) 5248 5249 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5250 5251 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5252 this = expression.this 5253 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5254 return self.sql(this) 5255 5256 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5257 5258 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5259 this = expression.this 5260 time_format = self.format_time(expression) 5261 safe = expression.args.get("safe") 5262 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5263 return self.sql( 5264 exp.cast( 5265 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5266 exp.DType.DATE, 5267 ) 5268 ) 5269 5270 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5271 return self.sql(this) 5272 5273 if safe: 5274 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5275 5276 return self.sql(exp.cast(this, exp.DType.DATE)) 5277 5278 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5279 return self.sql( 5280 exp.func( 5281 "DATEDIFF", 5282 expression.this, 5283 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5284 "day", 5285 ) 5286 ) 5287 5288 def lastday_sql(self, expression: exp.LastDay) -> str: 5289 if self.LAST_DAY_SUPPORTS_DATE_PART: 5290 return self.function_fallback_sql(expression) 5291 5292 unit = expression.args.get("unit") 5293 if unit and unit.name.upper() != "MONTH": 5294 self.unsupported("Date parts are not supported in LAST_DAY.") 5295 5296 return self.func("LAST_DAY", expression.this) 5297 5298 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5299 import sqlglot.dialects.dialect 5300 5301 return self.func( 5302 "DATE_ADD", 5303 expression.this, 5304 expression.expression, 5305 sqlglot.dialects.dialect.unit_to_str(expression), 5306 ) 5307 5308 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5309 if self.CAN_IMPLEMENT_ARRAY_ANY: 5310 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5311 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5312 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5313 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5314 5315 import sqlglot.dialects.dialect 5316 5317 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5318 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5319 self.unsupported("ARRAY_ANY is unsupported") 5320 5321 return self.function_fallback_sql(expression) 5322 5323 def struct_sql(self, expression: exp.Struct) -> str: 5324 expression.set( 5325 "expressions", 5326 [ 5327 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5328 if isinstance(e, exp.PropertyEQ) 5329 else e 5330 for e in expression.expressions 5331 ], 5332 ) 5333 5334 return self.function_fallback_sql(expression) 5335 5336 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5337 low = self.sql(expression, "this") 5338 high = self.sql(expression, "expression") 5339 5340 return f"{low} TO {high}" 5341 5342 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5343 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5344 tables = f" {self.expressions(expression)}" 5345 5346 exists = " IF EXISTS" if expression.args.get("exists") else "" 5347 5348 on_cluster = self.sql(expression, "cluster") 5349 on_cluster = f" {on_cluster}" if on_cluster else "" 5350 5351 identity = self.sql(expression, "identity") 5352 identity = f" {identity} IDENTITY" if identity else "" 5353 5354 option = self.sql(expression, "option") 5355 option = f" {option}" if option else "" 5356 5357 partition = self.sql(expression, "partition") 5358 partition = f" {partition}" if partition else "" 5359 5360 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5361 5362 # This transpiles T-SQL's CONVERT function 5363 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5364 def convert_sql(self, expression: exp.Convert) -> str: 5365 to = expression.this 5366 value = expression.expression 5367 style = expression.args.get("style") 5368 safe = expression.args.get("safe") 5369 strict = expression.args.get("strict") 5370 5371 if not to or not value: 5372 return "" 5373 5374 # Retrieve length of datatype and override to default if not specified 5375 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5376 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5377 5378 transformed: exp.Expr | None = None 5379 cast = exp.Cast if strict else exp.TryCast 5380 5381 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5382 if isinstance(style, exp.Literal) and style.is_int: 5383 import sqlglot.dialects.tsql 5384 5385 style_value = style.name 5386 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5387 if not converted_style: 5388 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5389 5390 fmt = exp.Literal.string(converted_style) 5391 5392 if to.this == exp.DType.DATE: 5393 transformed = exp.StrToDate(this=value, format=fmt) 5394 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5395 transformed = exp.StrToTime(this=value, format=fmt) 5396 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5397 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5398 elif to.this == exp.DType.TEXT: 5399 transformed = exp.TimeToStr(this=value, format=fmt) 5400 5401 if not transformed: 5402 transformed = cast(this=value, to=to, safe=safe) 5403 5404 return self.sql(transformed) 5405 5406 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5407 this = expression.this 5408 if isinstance(this, exp.JSONPathWildcard): 5409 this = self.json_path_part(this) 5410 return f".{this}" if this else "" 5411 5412 quoted = expression.args.get("quoted") 5413 if not ( 5414 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5415 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5416 return f".{this}" 5417 5418 this = self.json_path_part(this) 5419 5420 return ( 5421 f"[{this}]" 5422 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5423 else f".{this}" 5424 ) 5425 5426 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5427 this = self.json_path_part(expression.this) 5428 return f"[{this}]" if this else "" 5429 5430 def _simplify_unless_literal(self, expression: E) -> E: 5431 if not isinstance(expression, exp.Literal): 5432 import sqlglot.optimizer.simplify 5433 5434 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5435 5436 return expression 5437 5438 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5439 this = expression.this 5440 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5441 self.unsupported( 5442 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5443 ) 5444 return self.sql(this) 5445 5446 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5447 if self.IGNORE_NULLS_BEFORE_ORDER: 5448 from sqlglot.optimizer.scope import find_all_in_scope 5449 5450 # The first modifier here will be the one closest to the AggFunc's arg 5451 mods = sorted( 5452 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5453 key=lambda x: ( 5454 0 5455 if isinstance(x, exp.HavingMax) 5456 else (1 if isinstance(x, exp.Order) else 2) 5457 ), 5458 ) 5459 5460 if mods: 5461 mod = mods[0] 5462 this = expression.__class__(this=mod.this.copy()) 5463 this.meta["inline"] = True 5464 mod.this.replace(this) 5465 return self.sql(expression.this) 5466 5467 agg_func = expression.find(exp.AggFunc) 5468 5469 if agg_func: 5470 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5471 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5472 5473 return f"{self.sql(expression, 'this')} {text}" 5474 5475 def _replace_line_breaks(self, string: str) -> str: 5476 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5477 if self.pretty: 5478 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5479 return string 5480 5481 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5482 option = self.sql(expression, "this") 5483 5484 if expression.expressions: 5485 upper = option.upper() 5486 5487 # Snowflake FILE_FORMAT options are separated by whitespace 5488 sep = " " if upper == "FILE_FORMAT" else ", " 5489 5490 # Databricks copy/format options do not set their list of values with EQ 5491 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5492 values = self.expressions(expression, flat=True, sep=sep) 5493 return f"{option}{op}({values})" 5494 5495 value = self.sql(expression, "expression") 5496 5497 if not value: 5498 return option 5499 5500 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5501 5502 return f"{option}{op}{value}" 5503 5504 def credentials_sql(self, expression: exp.Credentials) -> str: 5505 cred_expr = expression.args.get("credentials") 5506 if isinstance(cred_expr, exp.Literal): 5507 # Redshift case: CREDENTIALS <string> 5508 credentials = self.sql(expression, "credentials") 5509 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5510 else: 5511 # Snowflake case: CREDENTIALS = (...) 5512 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5513 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5514 5515 storage = self.sql(expression, "storage") 5516 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5517 5518 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5519 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5520 5521 iam_role = self.sql(expression, "iam_role") 5522 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5523 5524 region = self.sql(expression, "region") 5525 region = f" REGION {region}" if region else "" 5526 5527 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5528 5529 def copy_sql(self, expression: exp.Copy) -> str: 5530 this = self.sql(expression, "this") 5531 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5532 5533 credentials = self.sql(expression, "credentials") 5534 credentials = self.seg(credentials) if credentials else "" 5535 files = self.expressions(expression, key="files", flat=True) 5536 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5537 5538 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5539 params = self.expressions( 5540 expression, 5541 key="params", 5542 sep=sep, 5543 new_line=True, 5544 skip_last=True, 5545 skip_first=True, 5546 indent=self.COPY_PARAMS_ARE_WRAPPED, 5547 ) 5548 5549 if params: 5550 if self.COPY_PARAMS_ARE_WRAPPED: 5551 params = f" WITH ({params})" 5552 elif not self.pretty and (files or credentials): 5553 params = f" {params}" 5554 5555 return f"COPY{this}{kind} {files}{credentials}{params}" 5556 5557 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5558 return "" 5559 5560 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5561 on_sql = "ON" if expression.args.get("on") else "OFF" 5562 filter_col: str | None = self.sql(expression, "filter_column") 5563 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5564 retention_period: str | None = self.sql(expression, "retention_period") 5565 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5566 5567 if filter_col or retention_period: 5568 on_sql = self.func("ON", filter_col, retention_period) 5569 5570 return f"DATA_DELETION={on_sql}" 5571 5572 def maskingpolicycolumnconstraint_sql( 5573 self, expression: exp.MaskingPolicyColumnConstraint 5574 ) -> str: 5575 this = self.sql(expression, "this") 5576 expressions = self.expressions(expression, flat=True) 5577 expressions = f" USING ({expressions})" if expressions else "" 5578 return f"MASKING POLICY {this}{expressions}" 5579 5580 def gapfill_sql(self, expression: exp.GapFill) -> str: 5581 this = self.sql(expression, "this") 5582 this = f"TABLE {this}" 5583 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5584 5585 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5586 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5587 5588 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5589 this = self.sql(expression, "this") 5590 expr = expression.expression 5591 5592 if isinstance(expr, exp.Func): 5593 # T-SQL's CLR functions are case sensitive 5594 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5595 else: 5596 expr = self.sql(expression, "expression") 5597 5598 return self.scope_resolution(expr, this) 5599 5600 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5601 if self.PARSE_JSON_NAME is None: 5602 return self.sql(expression.this) 5603 5604 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5605 5606 def rand_sql(self, expression: exp.Rand) -> str: 5607 lower = self.sql(expression, "lower") 5608 upper = self.sql(expression, "upper") 5609 5610 if lower and upper: 5611 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5612 return self.func("RAND", expression.this) 5613 5614 def changes_sql(self, expression: exp.Changes) -> str: 5615 information = self.sql(expression, "information") 5616 information = f"INFORMATION => {information}" 5617 at_before = self.sql(expression, "at_before") 5618 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5619 end = self.sql(expression, "end") 5620 end = f"{self.seg('')}{end}" if end else "" 5621 5622 return f"CHANGES ({information}){at_before}{end}" 5623 5624 def pad_sql(self, expression: exp.Pad) -> str: 5625 prefix = "L" if expression.args.get("is_left") else "R" 5626 5627 fill_pattern = self.sql(expression, "fill_pattern") or None 5628 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5629 fill_pattern = "' '" 5630 5631 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5632 5633 def summarize_sql(self, expression: exp.Summarize) -> str: 5634 table = " TABLE" if expression.args.get("table") else "" 5635 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5636 5637 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5638 generate_series = exp.GenerateSeries(**expression.args) 5639 5640 parent = expression.parent 5641 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5642 parent = parent.parent 5643 5644 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5645 return self.sql(exp.Unnest(expressions=[generate_series])) 5646 5647 if isinstance(parent, exp.Select): 5648 self.unsupported("GenerateSeries projection unnesting is not supported.") 5649 5650 return self.sql(generate_series) 5651 5652 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5653 if self.SUPPORTS_CONVERT_TIMEZONE: 5654 return self.function_fallback_sql(expression) 5655 5656 source_tz = expression.args.get("source_tz") 5657 target_tz = expression.args.get("target_tz") 5658 timestamp = expression.args.get("timestamp") 5659 5660 if source_tz and timestamp: 5661 timestamp = exp.AtTimeZone( 5662 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5663 ) 5664 5665 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5666 5667 return self.sql(expr) 5668 5669 def json_sql(self, expression: exp.JSON) -> str: 5670 this = self.sql(expression, "this") 5671 this = f" {this}" if this else "" 5672 5673 _with = expression.args.get("with_") 5674 5675 if _with is None: 5676 with_sql = "" 5677 elif not _with: 5678 with_sql = " WITHOUT" 5679 else: 5680 with_sql = " WITH" 5681 5682 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5683 5684 return f"JSON{this}{with_sql}{unique_sql}" 5685 5686 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5687 path = self.sql(expression, "path") 5688 returning = self.sql(expression, "returning") 5689 returning = f" RETURNING {returning}" if returning else "" 5690 5691 on_condition = self.sql(expression, "on_condition") 5692 on_condition = f" {on_condition}" if on_condition else "" 5693 5694 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5695 5696 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5697 regexp = " REGEXP" if expression.args.get("regexp") else "" 5698 return f"SKIP{regexp} {self.sql(expression.expression)}" 5699 5700 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5701 else_ = "ELSE " if expression.args.get("else_") else "" 5702 condition = self.sql(expression, "expression") 5703 condition = f"WHEN {condition} THEN " if condition else else_ 5704 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5705 return f"{condition}{insert}" 5706 5707 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5708 kind = self.sql(expression, "kind") 5709 expressions = self.seg(self.expressions(expression, sep=" ")) 5710 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5711 return res 5712 5713 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5714 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5715 empty = expression.args.get("empty") 5716 empty = ( 5717 f"DEFAULT {empty} ON EMPTY" 5718 if isinstance(empty, exp.Expr) 5719 else self.sql(expression, "empty") 5720 ) 5721 5722 error = expression.args.get("error") 5723 error = ( 5724 f"DEFAULT {error} ON ERROR" 5725 if isinstance(error, exp.Expr) 5726 else self.sql(expression, "error") 5727 ) 5728 5729 if error and empty: 5730 error = ( 5731 f"{empty} {error}" 5732 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5733 else f"{error} {empty}" 5734 ) 5735 empty = "" 5736 5737 null = self.sql(expression, "null") 5738 5739 return f"{empty}{error}{null}" 5740 5741 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5742 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5743 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5744 5745 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5746 this = self.sql(expression, "this") 5747 path = self.sql(expression, "path") 5748 5749 passing = self.expressions(expression, "passing") 5750 passing = f" PASSING {passing}" if passing else "" 5751 5752 on_condition = self.sql(expression, "on_condition") 5753 on_condition = f" {on_condition}" if on_condition else "" 5754 5755 path = f"{path}{passing}{on_condition}" 5756 5757 return self.func("JSON_EXISTS", this, path) 5758 5759 def _add_arrayagg_null_filter( 5760 self, 5761 array_agg_sql: str, 5762 array_agg_expr: exp.ArrayAgg, 5763 column_expr: exp.Expr, 5764 ) -> str: 5765 """ 5766 Add NULL filter to ARRAY_AGG if dialect requires it. 5767 5768 Args: 5769 array_agg_sql: The generated ARRAY_AGG SQL string 5770 array_agg_expr: The ArrayAgg expression node 5771 column_expr: The column/expression to filter (before ORDER BY wrapping) 5772 5773 Returns: 5774 SQL string with FILTER clause added if needed 5775 """ 5776 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5777 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5778 if not ( 5779 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5780 ): 5781 return array_agg_sql 5782 5783 parent = array_agg_expr.parent 5784 if isinstance(parent, exp.Filter): 5785 parent_cond = parent.expression.this 5786 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5787 elif column_expr.find(exp.Column): 5788 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5789 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5790 this_sql = ( 5791 self.expressions(column_expr) 5792 if isinstance(column_expr, exp.Distinct) 5793 else self.sql(column_expr) 5794 ) 5795 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5796 5797 return array_agg_sql 5798 5799 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5800 array_agg = self.function_fallback_sql(expression) 5801 column_expr = expression.this 5802 if isinstance(column_expr, exp.Order): 5803 column_expr = column_expr.this 5804 5805 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5806 5807 def slice_sql(self, expression: exp.Slice) -> str: 5808 step = self.sql(expression, "step") 5809 end = self.sql(expression.expression) 5810 begin = self.sql(expression.this) 5811 5812 sql = f"{end}:{step}" if step else end 5813 return f"{begin}:{sql}" if sql else f"{begin}:" 5814 5815 def apply_sql(self, expression: exp.Apply) -> str: 5816 this = self.sql(expression, "this") 5817 expr = self.sql(expression, "expression") 5818 5819 return f"{this} APPLY({expr})" 5820 5821 def _grant_or_revoke_sql( 5822 self, 5823 expression: exp.Grant | exp.Revoke, 5824 keyword: str, 5825 preposition: str, 5826 grant_option_prefix: str = "", 5827 grant_option_suffix: str = "", 5828 ) -> str: 5829 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5830 5831 kind = self.sql(expression, "kind") 5832 kind = f" {kind}" if kind else "" 5833 5834 securable = self.sql(expression, "securable") 5835 securable = f" {securable}" if securable else "" 5836 5837 principals = self.expressions(expression, key="principals", flat=True) 5838 5839 if not expression.args.get("grant_option"): 5840 grant_option_prefix = grant_option_suffix = "" 5841 5842 # cascade for revoke only 5843 cascade = self.sql(expression, "cascade") 5844 cascade = f" {cascade}" if cascade else "" 5845 5846 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5847 5848 def grant_sql(self, expression: exp.Grant) -> str: 5849 return self._grant_or_revoke_sql( 5850 expression, 5851 keyword="GRANT", 5852 preposition="TO", 5853 grant_option_suffix=" WITH GRANT OPTION", 5854 ) 5855 5856 def revoke_sql(self, expression: exp.Revoke) -> str: 5857 return self._grant_or_revoke_sql( 5858 expression, 5859 keyword="REVOKE", 5860 preposition="FROM", 5861 grant_option_prefix="GRANT OPTION FOR ", 5862 ) 5863 5864 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5865 this = self.sql(expression, "this") 5866 columns = self.expressions(expression, flat=True) 5867 columns = f"({columns})" if columns else "" 5868 5869 return f"{this}{columns}" 5870 5871 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5872 this = self.sql(expression, "this") 5873 5874 kind = self.sql(expression, "kind") 5875 kind = f"{kind} " if kind else "" 5876 5877 return f"{kind}{this}" 5878 5879 def columns_sql(self, expression: exp.Columns) -> str: 5880 func = self.function_fallback_sql(expression) 5881 if expression.args.get("unpack"): 5882 func = f"*{func}" 5883 5884 return func 5885 5886 def overlay_sql(self, expression: exp.Overlay) -> str: 5887 this = self.sql(expression, "this") 5888 expr = self.sql(expression, "expression") 5889 from_sql = self.sql(expression, "from_") 5890 for_sql = self.sql(expression, "for_") 5891 for_sql = f" FOR {for_sql}" if for_sql else "" 5892 5893 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5894 5895 @unsupported_args("format") 5896 def todouble_sql(self, expression: exp.ToDouble) -> str: 5897 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5898 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5899 5900 def string_sql(self, expression: exp.String) -> str: 5901 this = expression.this 5902 zone = expression.args.get("zone") 5903 5904 if zone: 5905 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5906 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5907 # set for source_tz to transpile the time conversion before the STRING cast 5908 this = exp.ConvertTimezone( 5909 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5910 ) 5911 5912 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5913 5914 def median_sql(self, expression: exp.Median) -> str: 5915 if not self.SUPPORTS_MEDIAN: 5916 return self.sql( 5917 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5918 ) 5919 5920 return self.function_fallback_sql(expression) 5921 5922 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5923 filler = self.sql(expression, "this") 5924 filler = f" {filler}" if filler else "" 5925 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5926 return f"TRUNCATE{filler} {with_count}" 5927 5928 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5929 if self.SUPPORTS_UNIX_SECONDS: 5930 return self.function_fallback_sql(expression) 5931 5932 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5933 5934 return self.sql( 5935 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5936 ) 5937 5938 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5939 dim = expression.expression 5940 5941 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5942 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5943 if not (dim.is_int and dim.name == "1"): 5944 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5945 dim = None 5946 5947 # If dimension is required but not specified, default initialize it 5948 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5949 dim = exp.Literal.number(1) 5950 5951 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5952 5953 def attach_sql(self, expression: exp.Attach) -> str: 5954 this = self.sql(expression, "this") 5955 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5956 expressions = self.expressions(expression) 5957 expressions = f" ({expressions})" if expressions else "" 5958 5959 return f"ATTACH{exists_sql} {this}{expressions}" 5960 5961 def detach_sql(self, expression: exp.Detach) -> str: 5962 kind = self.sql(expression, "kind") 5963 kind = f" {kind}" if kind else "" 5964 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5965 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5966 exists = " IF EXISTS" if expression.args.get("exists") else "" 5967 if exists: 5968 kind = kind or " DATABASE" 5969 5970 this = self.sql(expression, "this") 5971 this = f" {this}" if this else "" 5972 cluster = self.sql(expression, "cluster") 5973 cluster = f" {cluster}" if cluster else "" 5974 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5975 sync = " SYNC" if expression.args.get("sync") else "" 5976 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5977 5978 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5979 this = self.sql(expression, "this") 5980 value = self.sql(expression, "expression") 5981 value = f" {value}" if value else "" 5982 return f"{this}{value}" 5983 5984 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5985 return ( 5986 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5987 ) 5988 5989 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5990 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5991 encode = f"{encode} {self.sql(expression, 'this')}" 5992 5993 properties = expression.args.get("properties") 5994 if properties: 5995 encode = f"{encode} {self.properties(properties)}" 5996 5997 return encode 5998 5999 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6000 this = self.sql(expression, "this") 6001 include = f"INCLUDE {this}" 6002 6003 column_def = self.sql(expression, "column_def") 6004 if column_def: 6005 include = f"{include} {column_def}" 6006 6007 alias = self.sql(expression, "alias") 6008 if alias: 6009 include = f"{include} AS {alias}" 6010 6011 return include 6012 6013 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6014 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6015 name = f"{prefix} {self.sql(expression, 'this')}" 6016 return self.func("XMLELEMENT", name, *expression.expressions) 6017 6018 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6019 this = self.sql(expression, "this") 6020 expr = self.sql(expression, "expression") 6021 expr = f"({expr})" if expr else "" 6022 return f"{this}{expr}" 6023 6024 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6025 partitions = self.expressions(expression, "partition_expressions") 6026 create = self.expressions(expression, "create_expressions") 6027 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6028 6029 def partitionbyrangepropertydynamic_sql( 6030 self, expression: exp.PartitionByRangePropertyDynamic 6031 ) -> str: 6032 start = self.sql(expression, "start") 6033 end = self.sql(expression, "end") 6034 6035 every = expression.args["every"] 6036 if isinstance(every, exp.Interval) and every.this.is_string: 6037 every.this.replace(exp.Literal.number(every.name)) 6038 6039 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6040 6041 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6042 name = self.sql(expression, "this") 6043 values = self.expressions(expression, flat=True) 6044 6045 return f"NAME {name} VALUE {values}" 6046 6047 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6048 kind = self.sql(expression, "kind") 6049 sample = self.sql(expression, "sample") 6050 return f"SAMPLE {sample} {kind}" 6051 6052 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6053 kind = self.sql(expression, "kind") 6054 option = self.sql(expression, "option") 6055 option = f" {option}" if option else "" 6056 this = self.sql(expression, "this") 6057 this = f" {this}" if this else "" 6058 columns = self.expressions(expression) 6059 columns = f" {columns}" if columns else "" 6060 return f"{kind}{option} STATISTICS{this}{columns}" 6061 6062 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6063 this = self.sql(expression, "this") 6064 columns = self.expressions(expression) 6065 inner_expression = self.sql(expression, "expression") 6066 inner_expression = f" {inner_expression}" if inner_expression else "" 6067 update_options = self.sql(expression, "update_options") 6068 update_options = f" {update_options} UPDATE" if update_options else "" 6069 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6070 6071 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6072 kind = self.sql(expression, "kind") 6073 kind = f" {kind}" if kind else "" 6074 return f"DELETE{kind} STATISTICS" 6075 6076 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6077 inner_expression = self.sql(expression, "expression") 6078 return f"LIST CHAINED ROWS{inner_expression}" 6079 6080 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6081 kind = self.sql(expression, "kind") 6082 this = self.sql(expression, "this") 6083 this = f" {this}" if this else "" 6084 inner_expression = self.sql(expression, "expression") 6085 return f"VALIDATE {kind}{this}{inner_expression}" 6086 6087 def analyze_sql(self, expression: exp.Analyze) -> str: 6088 options = self.expressions(expression, key="options", sep=" ") 6089 options = f" {options}" if options else "" 6090 kind = self.sql(expression, "kind") 6091 kind = f" {kind}" if kind else "" 6092 tables = self.expressions(expression, key="tables", flat=True) 6093 tables = f" {tables}" if tables else "" 6094 mode = self.sql(expression, "mode") 6095 mode = f" {mode}" if mode else "" 6096 properties = self.sql(expression, "properties") 6097 properties = f" {properties}" if properties else "" 6098 partition = self.sql(expression, "partition") 6099 partition = f" {partition}" if partition else "" 6100 inner_expression = self.sql(expression, "expression") 6101 inner_expression = f" {inner_expression}" if inner_expression else "" 6102 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6103 6104 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6105 this = self.sql(expression, "this") 6106 namespaces = self.expressions(expression, key="namespaces") 6107 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6108 passing = self.expressions(expression, key="passing") 6109 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6110 columns = self.expressions(expression, key="columns") 6111 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6112 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6113 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6114 6115 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6116 this = self.sql(expression, "this") 6117 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6118 6119 def export_sql(self, expression: exp.Export) -> str: 6120 this = self.sql(expression, "this") 6121 connection = self.sql(expression, "connection") 6122 connection = f"WITH CONNECTION {connection} " if connection else "" 6123 options = self.sql(expression, "options") 6124 return f"EXPORT DATA {connection}{options} AS {this}" 6125 6126 def declare_sql(self, expression: exp.Declare) -> str: 6127 replace = "OR REPLACE " if expression.args.get("replace") else "" 6128 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6129 6130 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6131 variables = self.expressions(expression, "this") 6132 default = self.sql(expression, "default") 6133 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6134 6135 kind = self.sql(expression, "kind") 6136 if isinstance(expression.args.get("kind"), exp.Schema): 6137 kind = f"TABLE {kind}" 6138 6139 kind = f" {kind}" if kind else "" 6140 6141 return f"{variables}{kind}{default}" 6142 6143 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6144 kind = self.sql(expression, "kind") 6145 this = self.sql(expression, "this") 6146 set = self.sql(expression, "expression") 6147 using = self.sql(expression, "using") 6148 using = f" USING {using}" if using else "" 6149 6150 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6151 6152 return f"{kind_sql} {this} SET {set}{using}" 6153 6154 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6155 params = self.expressions(expression, key="params", flat=True) 6156 return self.func(expression.name, *expression.expressions) + f"({params})" 6157 6158 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6159 return self.func(expression.name, *expression.expressions) 6160 6161 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6162 return self.anonymousaggfunc_sql(expression) 6163 6164 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6165 return self.parameterizedagg_sql(expression) 6166 6167 def show_sql(self, expression: exp.Show) -> str: 6168 self.unsupported("Unsupported SHOW statement") 6169 return "" 6170 6171 def install_sql(self, expression: exp.Install) -> str: 6172 self.unsupported("Unsupported INSTALL statement") 6173 return "" 6174 6175 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6176 # Snowflake GET/PUT statements: 6177 # PUT <file> <internalStage> <properties> 6178 # GET <internalStage> <file> <properties> 6179 props = expression.args.get("properties") 6180 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6181 this = self.sql(expression, "this") 6182 target = self.sql(expression, "target") 6183 6184 if isinstance(expression, exp.Put): 6185 return f"PUT {this} {target}{props_sql}" 6186 else: 6187 return f"GET {target} {this}{props_sql}" 6188 6189 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6190 this = self.sql(expression, "this") 6191 expr = self.sql(expression, "expression") 6192 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6193 return f"TRANSLATE({this} USING {expr}{with_error})" 6194 6195 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6196 if self.SUPPORTS_DECODE_CASE: 6197 return self.func("DECODE", *expression.expressions) 6198 6199 decode_expr, *expressions = expression.expressions 6200 6201 ifs = [] 6202 for search, result in zip(expressions[::2], expressions[1::2]): 6203 if isinstance(search, exp.Literal): 6204 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6205 elif isinstance(search, exp.Null): 6206 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6207 else: 6208 if isinstance(search, exp.Binary): 6209 search = exp.paren(search) 6210 6211 cond = exp.or_( 6212 decode_expr.eq(search), 6213 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6214 copy=False, 6215 ) 6216 ifs.append(exp.If(this=cond, true=result)) 6217 6218 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6219 return self.sql(case) 6220 6221 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6222 this = self.sql(expression, "this") 6223 this = self.seg(this, sep="") 6224 dimensions = self.expressions( 6225 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6226 ) 6227 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6228 metrics = self.expressions( 6229 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6230 ) 6231 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6232 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6233 facts = self.seg(f"FACTS {facts}") if facts else "" 6234 where = self.sql(expression, "where") 6235 where = self.seg(f"WHERE {where}") if where else "" 6236 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6237 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6238 6239 def getextract_sql(self, expression: exp.GetExtract) -> str: 6240 this = expression.this 6241 expr = expression.expression 6242 6243 if not this.type or not expression.type: 6244 import sqlglot.optimizer.annotate_types 6245 6246 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6247 6248 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6249 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6250 6251 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6252 6253 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6254 return self.sql( 6255 exp.DateAdd( 6256 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6257 expression=expression.this, 6258 unit=exp.var("DAY"), 6259 ) 6260 ) 6261 6262 def space_sql(self: Generator, expression: exp.Space) -> str: 6263 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6264 6265 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6266 return f"BUILD {self.sql(expression, 'this')}" 6267 6268 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6269 method = self.sql(expression, "method") 6270 kind = expression.args.get("kind") 6271 if not kind: 6272 return f"REFRESH {method}" 6273 6274 every = self.sql(expression, "every") 6275 unit = self.sql(expression, "unit") 6276 every = f" EVERY {every} {unit}" if every else "" 6277 starts = self.sql(expression, "starts") 6278 starts = f" STARTS {starts}" if starts else "" 6279 6280 return f"REFRESH {method} ON {kind}{every}{starts}" 6281 6282 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6283 self.unsupported("The model!attribute syntax is not supported") 6284 return "" 6285 6286 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6287 return self.func("DIRECTORY", expression.this) 6288 6289 def uuid_sql(self, expression: exp.Uuid) -> str: 6290 is_string = expression.args.get("is_string", False) 6291 uuid_func_sql = self.func("UUID") 6292 6293 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6294 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6295 6296 return uuid_func_sql 6297 6298 def initcap_sql(self, expression: exp.Initcap) -> str: 6299 delimiters = expression.expression 6300 6301 if delimiters: 6302 # do not generate delimiters arg if we are round-tripping from default delimiters 6303 if ( 6304 delimiters.is_string 6305 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6306 ): 6307 delimiters = None 6308 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6309 self.unsupported("INITCAP does not support custom delimiters") 6310 delimiters = None 6311 6312 return self.func("INITCAP", expression.this, delimiters) 6313 6314 def localtime_sql(self, expression: exp.Localtime) -> str: 6315 this = expression.this 6316 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6317 6318 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6319 this = expression.this 6320 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6321 6322 def weekstart_name(self, expression: exp.WeekStart) -> str: 6323 import sqlglot.dialects.dialect 6324 6325 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6326 this = expression.this.name.upper() 6327 6328 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6329 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6330 6331 if dow_from_week_start_day != dow_from_week_offset: 6332 self.unsupported( 6333 f"WEEK({this}) is not supported; falling back to the default week start day" 6334 ) 6335 6336 return "WEEK" 6337 6338 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6339 name = self.weekstart_name(expression) 6340 6341 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6342 if isinstance(expression.parent, exp.DateTrunc): 6343 return self.sql(exp.Literal.string(name)) 6344 6345 return name 6346 6347 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6348 this = self.expressions(expression) 6349 charset = self.sql(expression, "charset") 6350 using = f" USING {charset}" if charset else "" 6351 return self.func(name, this + using) 6352 6353 def block_sql(self, expression: exp.Block) -> str: 6354 expressions = self.expressions(expression, sep="; ", flat=True) 6355 begin = "BEGIN " if expression.args.get("begin") else "" 6356 return f"{begin}{expressions}" if expressions else "" 6357 6358 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6359 self.unsupported("Unsupported Inline UDFs syntax") 6360 return "" 6361 6362 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6363 self.unsupported("Unsupported Stored Procedure syntax") 6364 return "" 6365 6366 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6367 self.unsupported("Unsupported If block syntax") 6368 return "" 6369 6370 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6371 self.unsupported("Unsupported Case statement syntax") 6372 return "" 6373 6374 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6375 self.unsupported("Unsupported While block syntax") 6376 return "" 6377 6378 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6379 self.unsupported("Unsupported Loop block syntax") 6380 return "" 6381 6382 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6383 self.unsupported("Unsupported Repeat block syntax") 6384 return "" 6385 6386 def leave_sql(self, expression: exp.Leave) -> str: 6387 self.unsupported("Unsupported Leave syntax") 6388 return "" 6389 6390 def iterate_sql(self, expression: exp.Iterate) -> str: 6391 self.unsupported("Unsupported Iterate syntax") 6392 return "" 6393 6394 def execute_sql(self, expression: exp.Execute) -> str: 6395 self.unsupported("Unsupported Execute syntax") 6396 return "" 6397 6398 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6399 self.unsupported("Unsupported Execute syntax") 6400 return "" 6401 6402 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6403 props = self.expressions(expression, sep=" ") 6404 return f"MODIFY {props}" 6405 6406 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6407 kind = expression.args.get("kind") 6408 return f"USING {kind} {self.sql(expression, 'this')}" 6409 6410 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6411 this = self.sql(expression, "this") 6412 to = self.sql(expression, "to") 6413 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.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>, <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'>}
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 alias = self.sql(expression, "alias") 2964 alias = f" AS {alias}" if alias else "" 2965 2966 ordinality = expression.args.get("ordinality") or "" 2967 if ordinality: 2968 ordinality = f" WITH ORDINALITY{alias}" 2969 alias = "" 2970 2971 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2973 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2974 this = self.sql(expression, "this") 2975 2976 args = [ 2977 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2978 for e in (expression.args.get(k) for k in ("offset", "expression")) 2979 if e 2980 ] 2981 2982 args_sql = ", ".join(self.sql(e) for e in args) 2983 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2984 expressions = self.expressions(expression, flat=True) 2985 limit_options = self.sql(expression, "limit_options") 2986 expressions = f" BY {expressions}" if expressions else "" 2987 2988 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2990 def offset_sql(self, expression: exp.Offset) -> str: 2991 this = self.sql(expression, "this") 2992 value = expression.expression 2993 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2994 expressions = self.expressions(expression, flat=True) 2995 expressions = f" BY {expressions}" if expressions else "" 2996 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2998 def setitem_sql(self, expression: exp.SetItem) -> str: 2999 kind = self.sql(expression, "kind") 3000 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3001 kind = "" 3002 else: 3003 kind = f"{kind} " if kind else "" 3004 this = self.sql(expression, "this") 3005 expressions = self.expressions(expression) 3006 collate = self.sql(expression, "collate") 3007 collate = f" COLLATE {collate}" if collate else "" 3008 global_ = "GLOBAL " if expression.args.get("global_") else "" 3009 return f"{global_}{kind}{this}{expressions}{collate}"
3016 def queryband_sql(self, expression: exp.QueryBand) -> str: 3017 this = self.sql(expression, "this") 3018 update = " UPDATE" if expression.args.get("update") else "" 3019 scope = self.sql(expression, "scope") 3020 scope = f" FOR {scope}" if scope else "" 3021 3022 return f"QUERY_BAND = {this}{update}{scope}"
3027 def lock_sql(self, expression: exp.Lock) -> str: 3028 if not self.LOCKING_READS_SUPPORTED: 3029 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3030 return "" 3031 3032 update = expression.args["update"] 3033 key = expression.args.get("key") 3034 if update: 3035 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3036 else: 3037 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3038 expressions = self.expressions(expression, flat=True) 3039 expressions = f" OF {expressions}" if expressions else "" 3040 wait = expression.args.get("wait") 3041 3042 if wait is not None: 3043 if isinstance(wait, exp.Literal): 3044 wait = f" WAIT {self.sql(wait)}" 3045 else: 3046 wait = " NOWAIT" if wait else " SKIP LOCKED" 3047 3048 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:
3056 def escape_str( 3057 self, 3058 text: str, 3059 escape_backslash: bool = True, 3060 delimiter: str | None = None, 3061 escaped_delimiter: str | None = None, 3062 is_byte_string: bool = False, 3063 ) -> str: 3064 if is_byte_string: 3065 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3066 else: 3067 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3068 3069 if supports_escape_sequences: 3070 text = "".join( 3071 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3072 for ch in text 3073 ) 3074 3075 delimiter = delimiter or self.dialect.QUOTE_END 3076 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3077 3078 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3080 def loaddata_sql(self, expression: exp.LoadData) -> str: 3081 is_overwrite = expression.args.get("overwrite") 3082 overwrite = " OVERWRITE" if is_overwrite else "" 3083 this = self.sql(expression, "this") 3084 3085 files = expression.args.get("files") 3086 if files: 3087 files_sql = self.expressions(files, flat=True) 3088 files_sql = f"FILES{self.wrap(files_sql)}" 3089 if is_overwrite: 3090 this = f" {this}" 3091 elif expression.args.get("temp"): 3092 this = f" INTO TEMP TABLE {this}" 3093 else: 3094 this = f" INTO TABLE {this}" 3095 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3096 3097 local = " LOCAL" if expression.args.get("local") else "" 3098 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3099 this = f" INTO TABLE {this}" 3100 partition = self.sql(expression, "partition") 3101 partition = f" {partition}" if partition else "" 3102 input_format = self.sql(expression, "input_format") 3103 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3104 serde = self.sql(expression, "serde") 3105 serde = f" SERDE {serde}" if serde else "" 3106 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3120 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3121 this = self.sql(expression, "this") 3122 this = f"{this} " if this else this 3123 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3124 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3126 def withfill_sql(self, expression: exp.WithFill) -> str: 3127 from_sql = self.sql(expression, "from_") 3128 from_sql = f" FROM {from_sql}" if from_sql else "" 3129 to_sql = self.sql(expression, "to") 3130 to_sql = f" TO {to_sql}" if to_sql else "" 3131 step_sql = self.sql(expression, "step") 3132 step_sql = f" STEP {step_sql}" if step_sql else "" 3133 interpolated_values = [ 3134 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3135 if isinstance(e, exp.Alias) 3136 else self.sql(e, "this") 3137 for e in expression.args.get("interpolate") or [] 3138 ] 3139 interpolate = ( 3140 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3141 ) 3142 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3194 def ordered_sql(self, expression: exp.Ordered) -> str: 3195 desc = expression.args.get("desc") 3196 asc = not desc 3197 3198 nulls_first = expression.args.get("nulls_first") 3199 nulls_last = not nulls_first 3200 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3201 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3202 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3203 3204 this = self.sql(expression, "this") 3205 3206 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3207 nulls_sort_change = "" 3208 if nulls_first and ( 3209 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3210 ): 3211 nulls_sort_change = " NULLS FIRST" 3212 elif ( 3213 nulls_last 3214 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3215 and not nulls_are_last 3216 ): 3217 nulls_sort_change = " NULLS LAST" 3218 3219 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3220 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3221 window = expression.find_ancestor(exp.Window, exp.Select) 3222 3223 if isinstance(window, exp.Window): 3224 window_this = window.this 3225 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3226 window_this = window_this.this 3227 spec = window.args.get("spec") 3228 else: 3229 window_this = None 3230 spec = None 3231 3232 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3233 # without a spec or with a ROWS spec, but not with RANGE 3234 if not ( 3235 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3236 and (not spec or spec.text("kind").upper() == "ROWS") 3237 ): 3238 if window_this and spec: 3239 self.unsupported( 3240 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3241 ) 3242 nulls_sort_change = "" 3243 elif self.NULL_ORDERING_SUPPORTED is False and ( 3244 (asc and nulls_sort_change == " NULLS LAST") 3245 or (desc and nulls_sort_change == " NULLS FIRST") 3246 ): 3247 # BigQuery does not allow these ordering/nulls combinations when used under 3248 # an aggregation func or under a window containing one 3249 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3250 3251 if isinstance(ancestor, exp.Window): 3252 ancestor = ancestor.this 3253 if isinstance(ancestor, exp.AggFunc): 3254 self.unsupported( 3255 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3256 ) 3257 nulls_sort_change = "" 3258 elif self.NULL_ORDERING_SUPPORTED is None: 3259 if expression.this.is_int: 3260 self.unsupported( 3261 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3262 ) 3263 elif not isinstance(expression.this, exp.Rand): 3264 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3265 target = self.sql(resolved) if resolved is not None else this 3266 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3267 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3268 nulls_sort_change = "" 3269 3270 with_fill = self.sql(expression, "with_fill") 3271 with_fill = f" {with_fill}" if with_fill else "" 3272 3273 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3283 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3284 partition = self.partition_by_sql(expression) 3285 order = self.sql(expression, "order") 3286 measures = self.expressions(expression, key="measures") 3287 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3288 rows = self.sql(expression, "rows") 3289 rows = self.seg(rows) if rows else "" 3290 after = self.sql(expression, "after") 3291 after = self.seg(after) if after else "" 3292 pattern = self.sql(expression, "pattern") 3293 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3294 definition_sqls = [ 3295 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3296 for definition in expression.args.get("define", []) 3297 ] 3298 definitions = self.expressions(sqls=definition_sqls) 3299 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3300 body = "".join( 3301 ( 3302 partition, 3303 order, 3304 measures, 3305 rows, 3306 after, 3307 pattern, 3308 define, 3309 ) 3310 ) 3311 alias = self.sql(expression, "alias") 3312 alias = f" {alias}" if alias else "" 3313 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3315 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3316 limit = expression.args.get("limit") 3317 3318 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3319 count = limit.args.get("count") 3320 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3321 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3322 limit = exp.Limit( 3323 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3324 ) 3325 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3326 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3327 3328 return csv( 3329 *sqls, 3330 *[self.sql(join) for join in expression.args.get("joins") or []], 3331 self.sql(expression, "match"), 3332 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3333 self.sql(expression, "prewhere"), 3334 self.sql(expression, "where"), 3335 self.sql(expression, "connect"), 3336 self.sql(expression, "group"), 3337 self.sql(expression, "having"), 3338 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3339 self.sql(expression, "order"), 3340 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3341 *self.after_limit_modifiers(expression), 3342 self.sql(expression, "for_"), 3343 self.options_modifier(expression), 3344 sep="", 3345 )
3351 def forclause_sql(self, expression: exp.ForClause) -> str: 3352 kind = expression.args["kind"] 3353 if kind == "BROWSE": 3354 return f"{self.sep()}FOR BROWSE" 3355 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3356 # the target dialect doesn't support QueryOption, so we drop the clause. 3357 options = self.expressions(expression, key="expressions") 3358 if not options: 3359 return "" 3360 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]:
3379 def select_sql(self, expression: exp.Select) -> str: 3380 into = expression.args.get("into") 3381 if not self.SUPPORTS_SELECT_INTO and into: 3382 into.pop() 3383 3384 hint = self.sql(expression, "hint") 3385 distinct = self.sql(expression, "distinct") 3386 distinct = f" {distinct}" if distinct else "" 3387 kind = self.sql(expression, "kind") 3388 3389 limit = expression.args.get("limit") 3390 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3391 top = self.limit_sql(limit, top=True) 3392 limit.pop() 3393 else: 3394 top = "" 3395 3396 expressions = self.expressions(expression) 3397 3398 if kind: 3399 if kind in self.SELECT_KINDS: 3400 kind = f" AS {kind}" 3401 else: 3402 if kind == "STRUCT": 3403 expressions = self.expressions( 3404 sqls=[ 3405 self.sql( 3406 exp.Struct( 3407 expressions=[ 3408 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3409 if isinstance(e, exp.Alias) 3410 else e 3411 for e in expression.expressions 3412 ] 3413 ) 3414 ) 3415 ] 3416 ) 3417 kind = "" 3418 3419 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3420 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3421 3422 exclude = expression.args.get("exclude") 3423 3424 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3425 exclude_sql = self.expressions(sqls=exclude, flat=True) 3426 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3427 3428 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3429 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3430 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3431 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3432 sql = self.query_modifiers( 3433 expression, 3434 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3435 self.sql(expression, "into", comment=False), 3436 self.sql(expression, "from_", comment=False), 3437 ) 3438 3439 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3440 if expression.args.get("with_"): 3441 sql = self.maybe_comment(sql, expression) 3442 expression.pop_comments() 3443 3444 sql = self.prepend_ctes(expression, sql) 3445 3446 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3447 expression.set("exclude", None) 3448 subquery = expression.subquery(copy=False) 3449 star = exp.Star(except_=exclude) 3450 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3451 3452 if not self.SUPPORTS_SELECT_INTO and into: 3453 if into.args.get("temporary"): 3454 table_kind = " TEMPORARY" 3455 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3456 table_kind = " UNLOGGED" 3457 else: 3458 table_kind = "" 3459 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3460 3461 return sql
3473 def star_sql(self, expression: exp.Star) -> str: 3474 except_ = self.expressions(expression, key="except_", flat=True) 3475 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3476 replace = self.expressions(expression, key="replace", flat=True) 3477 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3478 rename = self.expressions(expression, key="rename", flat=True) 3479 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3480 ilike = self.sql(expression, "ilike") 3481 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3482 return f"*{ilike}{except_}{replace}{rename}"
3498 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3499 alias = self.sql(expression, "alias") 3500 alias = f"{sep}{alias}" if alias else "" 3501 sample = self.sql(expression, "sample") 3502 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3503 alias = f"{sample}{alias}" 3504 3505 # Set to None so it's not generated again by self.query_modifiers() 3506 expression.set("sample", None) 3507 3508 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3509 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3510 return self.prepend_ctes(expression, sql)
3516 def unnest_sql(self, expression: exp.Unnest) -> str: 3517 args = self.expressions(expression, flat=True) 3518 3519 alias = expression.args.get("alias") 3520 offset = expression.args.get("offset") 3521 3522 if self.UNNEST_WITH_ORDINALITY: 3523 if alias and isinstance(offset, exp.Expr): 3524 alias.append("columns", offset) 3525 expression.set("offset", None) 3526 3527 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3528 columns = alias.columns 3529 alias = self.sql(columns[0]) if columns else "" 3530 else: 3531 alias = self.sql(alias) 3532 3533 alias = f" AS {alias}" if alias else alias 3534 if self.UNNEST_WITH_ORDINALITY: 3535 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3536 else: 3537 if isinstance(offset, exp.Expr): 3538 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3539 elif offset: 3540 suffix = f"{alias} WITH OFFSET" 3541 else: 3542 suffix = alias 3543 3544 return f"UNNEST({args}){suffix}"
3553 def window_sql(self, expression: exp.Window) -> str: 3554 this = self.sql(expression, "this") 3555 partition = self.partition_by_sql(expression) 3556 order = expression.args.get("order") 3557 order = self.order_sql(order, flat=True) if order else "" 3558 spec = self.sql(expression, "spec") 3559 alias = self.sql(expression, "alias") 3560 over = self.sql(expression, "over") or "OVER" 3561 3562 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3563 3564 first = expression.args.get("first") 3565 if first is None: 3566 first = "" 3567 else: 3568 first = "FIRST" if first else "LAST" 3569 3570 if not partition and not order and not spec and alias: 3571 return f"{this} {alias}" 3572 3573 args = self.format_args( 3574 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3575 ) 3576 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3582 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3583 kind = self.sql(expression, "kind") 3584 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3585 end = ( 3586 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3587 or "CURRENT ROW" 3588 ) 3589 3590 window_spec = f"{kind} BETWEEN {start} AND {end}" 3591 3592 exclude = self.sql(expression, "exclude") 3593 if exclude: 3594 if self.SUPPORTS_WINDOW_EXCLUDE: 3595 window_spec += f" EXCLUDE {exclude}" 3596 else: 3597 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3598 3599 return window_spec
3606 def between_sql(self, expression: exp.Between) -> str: 3607 this = self.sql(expression, "this") 3608 low = self.sql(expression, "low") 3609 high = self.sql(expression, "high") 3610 symmetric = expression.args.get("symmetric") 3611 3612 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3613 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3614 3615 flag = ( 3616 " SYMMETRIC" 3617 if symmetric 3618 else " ASYMMETRIC" 3619 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3620 else "" # silently drop ASYMMETRIC – semantics identical 3621 ) 3622 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]:
3624 def bracket_offset_expressions( 3625 self, expression: exp.Bracket, index_offset: int | None = None 3626 ) -> list[exp.Expr]: 3627 if expression.args.get("json_access"): 3628 return expression.expressions 3629 3630 return apply_index_offset( 3631 expression.this, 3632 expression.expressions, 3633 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3634 dialect=self.dialect, 3635 )
3648 def any_sql(self, expression: exp.Any) -> str: 3649 this = self.sql(expression, "this") 3650 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3651 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3652 this = self.wrap(this) 3653 return f"ANY{this}" 3654 return f"ANY {this}"
3659 def case_sql(self, expression: exp.Case) -> str: 3660 this = self.sql(expression, "this") 3661 statements = [f"CASE {this}" if this else "CASE"] 3662 3663 for e in expression.args["ifs"]: 3664 statements.append(f"WHEN {self.sql(e, 'this')}") 3665 statements.append(f"THEN {self.sql(e, 'true')}") 3666 3667 default = self.sql(expression, "default") 3668 3669 if default: 3670 statements.append(f"ELSE {default}") 3671 3672 statements.append("END") 3673 3674 if self.pretty and self.too_wide(statements): 3675 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3676 3677 return " ".join(statements)
3689 def extract_sql(self, expression: exp.Extract) -> str: 3690 import sqlglot.dialects.dialect 3691 3692 this = ( 3693 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3694 if self.NORMALIZE_EXTRACT_DATE_PARTS 3695 else expression.this 3696 ) 3697 if self.EXTRACT_ALLOWS_QUOTES: 3698 this_sql = self.sql(this) 3699 elif isinstance(this, exp.WeekStart): 3700 this_sql = self.weekstart_name(this) 3701 else: 3702 this_sql = this.name 3703 expression_sql = self.sql(expression, "expression") 3704 3705 return f"EXTRACT({this_sql} FROM {expression_sql})"
3707 def trim_sql(self, expression: exp.Trim) -> str: 3708 trim_type = self.sql(expression, "position") 3709 3710 if trim_type == "LEADING": 3711 func_name = "LTRIM" 3712 elif trim_type == "TRAILING": 3713 func_name = "RTRIM" 3714 else: 3715 func_name = "TRIM" 3716 3717 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3719 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3720 args = expression.expressions 3721 if isinstance(expression, exp.ConcatWs): 3722 args = args[1:] # Skip the delimiter 3723 3724 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3725 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3726 3727 concat_coalesce = ( 3728 self.dialect.CONCAT_WS_COALESCE 3729 if isinstance(expression, exp.ConcatWs) 3730 else self.dialect.CONCAT_COALESCE 3731 ) 3732 3733 if not concat_coalesce and expression.args.get("coalesce"): 3734 3735 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3736 if not e.type: 3737 import sqlglot.optimizer.annotate_types 3738 3739 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3740 3741 if e.is_string or e.is_type(exp.DType.ARRAY): 3742 return e 3743 3744 return exp.func("coalesce", e, exp.Literal.string("")) 3745 3746 args = [_wrap_with_coalesce(e) for e in args] 3747 3748 return args
3750 def concat_sql(self, expression: exp.Concat) -> str: 3751 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3752 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3753 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3754 # instead of coalescing them to empty string. 3755 import sqlglot.dialects.dialect 3756 3757 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3758 3759 expressions = self.convert_concat_args(expression) 3760 3761 # Some dialects don't allow a single-argument CONCAT call 3762 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3763 return self.sql(expressions[0]) 3764 3765 return self.func("CONCAT", *expressions)
3767 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3768 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3769 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3770 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3771 all_args = expression.expressions 3772 expression.set("coalesce", True) 3773 return self.sql( 3774 exp.case() 3775 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3776 .else_(expression) 3777 ) 3778 3779 return self.func( 3780 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3781 )
3787 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3788 expressions = self.expressions(expression, flat=True) 3789 expressions = f" ({expressions})" if expressions else "" 3790 reference = self.sql(expression, "reference") 3791 reference = f" {reference}" if reference else "" 3792 delete = self.sql(expression, "delete") 3793 delete = f" ON DELETE {delete}" if delete else "" 3794 update = self.sql(expression, "update") 3795 update = f" ON UPDATE {update}" if update else "" 3796 options = self.expressions(expression, key="options", flat=True, sep=" ") 3797 options = f" {options}" if options else "" 3798 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3800 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3801 this = self.sql(expression, "this") 3802 this = f" {this}" if this else "" 3803 expressions = self.expressions(expression, flat=True) 3804 include = self.sql(expression, "include") 3805 options = self.expressions(expression, key="options", flat=True, sep=" ") 3806 options = f" {options}" if options else "" 3807 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3816 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3817 if self.MATCH_AGAINST_TABLE_PREFIX: 3818 expressions = [] 3819 for expr in expression.expressions: 3820 if isinstance(expr, exp.Table): 3821 expressions.append(f"TABLE {self.sql(expr)}") 3822 else: 3823 expressions.append(expr) 3824 else: 3825 expressions = expression.expressions 3826 3827 modifier = expression.args.get("modifier") 3828 modifier = f" {modifier}" if modifier else "" 3829 return ( 3830 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3831 )
3845 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3846 if isinstance(expression, exp.JSONPathPart): 3847 transform = self.TRANSFORMS.get(expression.__class__) 3848 if not callable(transform): 3849 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3850 return "" 3851 3852 return transform(self, expression) 3853 3854 if isinstance(expression, int): 3855 return str(expression) 3856 3857 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3858 escaped = expression.replace("'", "\\'") 3859 escaped = f"'{escaped}'" 3860 else: 3861 escaped = expression.replace('"', '\\"') 3862 escaped = f'"{escaped}"' 3863 3864 return escaped
3869 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3870 # Output the Teradata column FORMAT override. 3871 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3872 this = self.sql(expression, "this") 3873 fmt = self.sql(expression, "format") 3874 return f"{this} (FORMAT {fmt})"
3902 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3903 null_handling = expression.args.get("null_handling") 3904 null_handling = f" {null_handling}" if null_handling else "" 3905 return_type = self.sql(expression, "return_type") 3906 return_type = f" RETURNING {return_type}" if return_type else "" 3907 strict = " STRICT" if expression.args.get("strict") else "" 3908 return self.func( 3909 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3910 )
3912 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3913 this = self.sql(expression, "this") 3914 order = self.sql(expression, "order") 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_ARRAYAGG", 3922 this, 3923 suffix=f"{order}{null_handling}{return_type}{strict})", 3924 )
3926 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3927 path = self.sql(expression, "path") 3928 path = f" PATH {path}" if path else "" 3929 nested_schema = self.sql(expression, "nested_schema") 3930 3931 if nested_schema: 3932 return f"NESTED{path} {nested_schema}" 3933 3934 this = self.sql(expression, "this") 3935 kind = self.sql(expression, "kind") 3936 kind = f" {kind}" if kind else "" 3937 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3938 3939 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3940 return f"{this}{kind}{format_json}{path}{ordinality}"
3945 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3946 this = self.sql(expression, "this") 3947 path = self.sql(expression, "path") 3948 path = f", {path}" if path else "" 3949 error_handling = expression.args.get("error_handling") 3950 error_handling = f" {error_handling}" if error_handling else "" 3951 empty_handling = expression.args.get("empty_handling") 3952 empty_handling = f" {empty_handling}" if empty_handling else "" 3953 schema = self.sql(expression, "schema") 3954 return self.func( 3955 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3956 )
3958 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3959 this = self.sql(expression, "this") 3960 kind = self.sql(expression, "kind") 3961 path = self.sql(expression, "path") 3962 path = f" {path}" if path else "" 3963 as_json = " AS JSON" if expression.args.get("as_json") else "" 3964 return f"{this} {kind}{path}{as_json}"
3966 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3967 this = self.sql(expression, "this") 3968 path = self.sql(expression, "path") 3969 path = f", {path}" if path else "" 3970 expressions = self.expressions(expression) 3971 with_ = ( 3972 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3973 if expressions 3974 else "" 3975 ) 3976 return f"OPENJSON({this}{path}){with_}"
3978 def in_sql(self, expression: exp.In) -> str: 3979 query = expression.args.get("query") 3980 unnest = expression.args.get("unnest") 3981 field = expression.args.get("field") 3982 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3983 3984 if query: 3985 in_sql = self.sql(query) 3986 elif unnest: 3987 in_sql = self.in_unnest_op(unnest) 3988 elif field: 3989 in_sql = self.sql(field) 3990 else: 3991 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3992 3993 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3998 def interval_sql(self, expression: exp.Interval) -> str: 3999 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4000 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4001 exp.AutoRefreshProperty, 4002 ) 4003 interval_keyword = "INTERVAL" if include_keyword else "" 4004 unit_expression = expression.args.get("unit") 4005 unit = self.sql(unit_expression) if unit_expression else "" 4006 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4007 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4008 unit = f" {unit}" if unit else "" 4009 4010 if self.SINGLE_STRING_INTERVAL: 4011 this = expression.this.name if expression.this else "" 4012 if this: 4013 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4014 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4015 return f"{interval_keyword}'{this}'{unit}" 4016 return f"{interval_keyword}'{this}{unit}'" 4017 return f"{interval_keyword}{unit}" 4018 4019 this = self.sql(expression, "this") 4020 if this: 4021 if not include_keyword and expression.this.is_string: 4022 this = expression.this.name 4023 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4024 this = f"({this})" 4025 if include_keyword: 4026 this = f" {this}" 4027 4028 return f"{interval_keyword}{this}{unit}"
4033 def reference_sql(self, expression: exp.Reference) -> str: 4034 this = self.sql(expression, "this") 4035 expressions = self.expressions(expression, flat=True) 4036 expressions = f"({expressions})" if expressions else "" 4037 options = self.expressions(expression, key="options", flat=True, sep=" ") 4038 options = f" {options}" if options else "" 4039 return f"REFERENCES {this}{expressions}{options}"
4041 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4042 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4043 parent = expression.parent 4044 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4045 4046 return self.func( 4047 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4048 )
4068 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4069 alias = expression.args["alias"] 4070 4071 parent = expression.parent 4072 pivot = parent and parent.parent 4073 4074 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4075 identifier_alias = isinstance(alias, exp.Identifier) 4076 literal_alias = isinstance(alias, exp.Literal) 4077 4078 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4079 alias.replace(exp.Literal.string(alias.output_name)) 4080 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4081 alias.replace(exp.to_identifier(alias.output_name)) 4082 4083 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:
4124 def connector_sql( 4125 self, 4126 expression: exp.Connector, 4127 op: str, 4128 stack: list[str | exp.Expr] | None = None, 4129 ) -> str: 4130 if stack is not None: 4131 stack.append(expression.right) 4132 if expression.comments and self.comments: 4133 op = self.maybe_comment(op, comments=expression.comments) 4134 4135 stack.extend((op, expression.left)) 4136 return op 4137 4138 stack = [expression] 4139 sqls: list[str] = [] 4140 ops = set() 4141 4142 while stack: 4143 node = stack.pop() 4144 if isinstance(node, exp.Connector): 4145 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4146 else: 4147 sql = self.sql(node) 4148 if sqls and sqls[-1] in ops: 4149 sqls[-1] += f" {sql}" 4150 else: 4151 sqls.append(sql) 4152 4153 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4154 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4174 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4175 format_sql = self.sql(expression, "format") 4176 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4177 to_sql = self.sql(expression, "to") 4178 to_sql = f" {to_sql}" if to_sql else "" 4179 action = self.sql(expression, "action") 4180 action = f" {action}" if action else "" 4181 default = self.sql(expression, "default") 4182 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4183 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4213 def comment_sql(self, expression: exp.Comment) -> str: 4214 this = self.sql(expression, "this") 4215 kind = expression.args["kind"] 4216 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4217 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4218 expression_sql = self.sql(expression, "expression") 4219 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4221 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4222 this = self.sql(expression, "this") 4223 delete = " DELETE" if expression.args.get("delete") else "" 4224 recompress = self.sql(expression, "recompress") 4225 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4226 to_disk = self.sql(expression, "to_disk") 4227 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4228 to_volume = self.sql(expression, "to_volume") 4229 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4230 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4232 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4233 where = self.sql(expression, "where") 4234 group = self.sql(expression, "group") 4235 aggregates = self.expressions(expression, key="aggregates") 4236 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4237 4238 if not (where or group or aggregates) and len(expression.expressions) == 1: 4239 return f"TTL {self.expressions(expression, flat=True)}" 4240 4241 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4260 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4261 this = self.sql(expression, "this") 4262 4263 exists = "" 4264 if expression.args.get("exists"): 4265 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4266 exists = " IF EXISTS" 4267 else: 4268 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4269 4270 dtype = self.sql(expression, "dtype") 4271 if dtype: 4272 collate = self.sql(expression, "collate") 4273 collate = f" COLLATE {collate}" if collate else "" 4274 using = self.sql(expression, "using") 4275 using = f" USING {using}" if using else "" 4276 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4277 null_constraint = self._alter_column_null_constraint_sql(expression) 4278 4279 return ( 4280 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4281 f"{collate}{using}{null_constraint}" 4282 ) 4283 4284 default = self.sql(expression, "default") 4285 if default: 4286 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4287 4288 comment = self.sql(expression, "comment") 4289 if comment: 4290 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4291 4292 visible = expression.args.get("visible") 4293 if visible: 4294 return f"ALTER COLUMN{exists} {this} SET {visible}" 4295 4296 allow_null = expression.args.get("allow_null") 4297 drop = expression.args.get("drop") 4298 4299 if not drop and not allow_null: 4300 self.unsupported("Unsupported ALTER COLUMN syntax") 4301 4302 if allow_null is not None: 4303 keyword = "DROP" if drop else "SET" 4304 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4305 4306 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4319 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4320 this = self.sql(expression, "this") 4321 rename_from = self.sql(expression, "rename_from") 4322 if rename_from: 4323 if not self.SUPPORTS_CHANGE_COLUMN: 4324 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4325 return f"CHANGE COLUMN {rename_from} {this}" 4326 if not self.SUPPORTS_MODIFY_COLUMN: 4327 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4328 return f"MODIFY COLUMN {this}"
4344 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4345 compound = " COMPOUND" if expression.args.get("compound") else "" 4346 this = self.sql(expression, "this") 4347 expressions = self.expressions(expression, flat=True) 4348 expressions = f"({expressions})" if expressions else "" 4349 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4351 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4352 if not self.RENAME_TABLE_WITH_DB: 4353 # Remove db from tables 4354 expression = expression.transform( 4355 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4356 ).assert_is(exp.AlterRename) 4357 this = self.sql(expression, "this") 4358 to_kw = " TO" if include_to else "" 4359 return f"RENAME{to_kw} {this}"
4374 def alter_sql(self, expression: exp.Alter) -> str: 4375 actions = expression.args["actions"] 4376 4377 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4378 actions[0], exp.ColumnDef 4379 ): 4380 actions_sql = self.expressions(expression, key="actions", flat=True) 4381 actions_sql = f"ADD {actions_sql}" 4382 else: 4383 actions_list = [] 4384 for action in actions: 4385 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4386 action_sql = self.add_column_sql(action) 4387 else: 4388 action_sql = self.sql(action) 4389 if isinstance(action, exp.Query): 4390 action_sql = f"AS {action_sql}" 4391 4392 actions_list.append(action_sql) 4393 4394 actions_sql = self.format_args(*actions_list).lstrip("\n") 4395 4396 iceberg = ( 4397 "ICEBERG " 4398 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4399 else "" 4400 ) 4401 exists = " IF EXISTS" if expression.args.get("exists") else "" 4402 on_cluster = self.sql(expression, "cluster") 4403 on_cluster = f" {on_cluster}" if on_cluster else "" 4404 only = " ONLY" if expression.args.get("only") else "" 4405 options = self.expressions(expression, key="options") 4406 options = f", {options}" if options else "" 4407 kind = self.sql(expression, "kind") 4408 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4409 check = " WITH CHECK" if expression.args.get("check") else "" 4410 cascade = ( 4411 " CASCADE" 4412 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4413 else "" 4414 ) 4415 this = self.sql(expression, "this") 4416 this = f" {this}" if this else "" 4417 4418 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4425 def add_column_sql(self, expression: exp.Expr) -> str: 4426 sql = self.sql(expression) 4427 if isinstance(expression, exp.Schema): 4428 column_text = " COLUMNS" 4429 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4430 column_text = " COLUMN" 4431 else: 4432 column_text = "" 4433 4434 return f"ADD{column_text} {sql}"
4447 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4448 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4449 location = self.sql(expression, "location") 4450 location = f" {location}" if location else "" 4451 return f"ADD {exists}{self.sql(expression.this)}{location}"
4453 def distinct_sql(self, expression: exp.Distinct) -> str: 4454 this = self.expressions(expression, flat=True) 4455 4456 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4457 case = exp.case() 4458 for arg in expression.expressions: 4459 case = case.when(arg.is_(exp.null()), exp.null()) 4460 this = self.sql(case.else_(f"({this})")) 4461 4462 this = f" {this}" if this else "" 4463 4464 on = self.sql(expression, "on") 4465 on = f" ON {on}" if on else "" 4466 return f"DISTINCT{this}{on}"
4493 def div_sql(self, expression: exp.Div) -> str: 4494 l, r = expression.left, expression.right 4495 4496 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4497 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4498 4499 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4500 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4501 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4502 4503 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4504 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4505 return self.sql( 4506 exp.cast( 4507 l / r, 4508 to=exp.DType.BIGINT, 4509 ) 4510 ) 4511 4512 return self.binary(expression, "/")
4537 def escape_sql(self, expression: exp.Escape) -> str: 4538 this = expression.this 4539 if ( 4540 isinstance(this, (exp.Like, exp.ILike)) 4541 and isinstance(this.expression, (exp.All, exp.Any)) 4542 and not self.SUPPORTS_LIKE_QUANTIFIERS 4543 ): 4544 return self._like_sql(this, escape=expression) 4545 return self.binary(expression, "ESCAPE")
4556 def is_sql(self, expression: exp.Is) -> str: 4557 negate = expression.args.get("negate") 4558 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4559 positive = bool(expression.expression.this) != bool(negate) 4560 return self.sql(expression.this if positive else exp.not_(expression.this)) 4561 return self.binary(expression, "IS NOT" if negate else "IS")
4631 def mod_sql(self, expression: exp.Mod) -> str: 4632 this = self.sql(expression, "this") 4633 expr = self.sql(expression, "expression") 4634 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4635 4636 parent = expression.parent 4637 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4638 return f"({sql})" 4639 4640 return sql
4670 def log_sql(self, expression: exp.Log) -> str: 4671 this = expression.this 4672 expr = expression.expression 4673 4674 if self.dialect.LOG_BASE_FIRST is False: 4675 this, expr = expr, this 4676 elif self.dialect.LOG_BASE_FIRST is None and expr: 4677 if this.name in ("2", "10"): 4678 return self.func(f"LOG{this.name}", expr) 4679 4680 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4681 4682 return self.func("LOG", this, expr)
4691 def binary(self, expression: exp.Binary, op: str) -> str: 4692 sqls: list[str] = [] 4693 stack: list[None | str | exp.Expr] = [expression] 4694 binary_type = type(expression) 4695 4696 while stack: 4697 node = stack.pop() 4698 4699 if type(node) is binary_type: 4700 op_func = node.args.get("operator") 4701 if op_func: 4702 op = f"OPERATOR({self.sql(op_func)})" 4703 4704 stack.append(node.args.get("expression")) 4705 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4706 stack.append(node.args.get("this")) 4707 else: 4708 sqls.append(self.sql(node)) 4709 4710 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4719 def function_fallback_sql(self, expression: exp.Func) -> str: 4720 args = [] 4721 4722 for key in expression.arg_types: 4723 arg_value = expression.args.get(key) 4724 4725 if isinstance(arg_value, list): 4726 for value in arg_value: 4727 args.append(value) 4728 elif arg_value is not None: 4729 args.append(arg_value) 4730 4731 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4732 name = expression.meta_get("name") or expression.sql_name() 4733 else: 4734 name = expression.sql_name() 4735 4736 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:
4749 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4750 arg_sqls = tuple( 4751 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4752 ) 4753 if self.pretty and self.too_wide(arg_sqls): 4754 return self.indent( 4755 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4756 ) 4757 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:
4762 def format_time( 4763 self, 4764 expression: exp.Expr, 4765 inverse_time_mapping: dict[str, str] | None = None, 4766 inverse_time_trie: dict | None = None, 4767 ) -> str | None: 4768 return format_time( 4769 self.sql(expression, "format"), 4770 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4771 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4772 )
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:
4774 def expressions( 4775 self, 4776 expression: exp.Expr | None = None, 4777 key: str | None = None, 4778 sqls: t.Collection[str | exp.Expr] | None = None, 4779 flat: bool = False, 4780 indent: bool = True, 4781 skip_first: bool = False, 4782 skip_last: bool = False, 4783 sep: str = ", ", 4784 prefix: str = "", 4785 dynamic: bool = False, 4786 new_line: bool = False, 4787 ) -> str: 4788 expressions = expression.args.get(key or "expressions") if expression else sqls 4789 4790 if not expressions: 4791 return "" 4792 4793 if flat: 4794 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4795 4796 num_sqls = len(expressions) 4797 result_sqls = [] 4798 4799 for i, e in enumerate(expressions): 4800 sql = self.sql(e, comment=False) 4801 if not sql: 4802 continue 4803 4804 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4805 4806 if self.pretty: 4807 if self.leading_comma: 4808 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4809 else: 4810 result_sqls.append( 4811 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4812 ) 4813 else: 4814 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4815 4816 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4817 if new_line: 4818 result_sqls.insert(0, "") 4819 result_sqls.append("") 4820 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4821 else: 4822 result_sql = "".join(result_sqls) 4823 4824 return ( 4825 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4826 if indent 4827 else result_sql 4828 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4830 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4831 flat = flat or isinstance(expression.parent, exp.Properties) 4832 expressions_sql = self.expressions(expression, flat=flat) 4833 if flat: 4834 return f"{op} {expressions_sql}" 4835 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4837 def naked_property(self, expression: exp.Property) -> str: 4838 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4839 if not property_name: 4840 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4841 return f"{property_name} {self.sql(expression, 'this')}"
4849 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4850 this = self.sql(expression, "this") 4851 expressions = self.no_identify(self.expressions, expression) 4852 expressions = ( 4853 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4854 ) 4855 return f"{this}{expressions}" if expressions.strip() != "" else this
4874 def when_sql(self, expression: exp.When) -> str: 4875 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4876 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4877 condition = self.sql(expression, "condition") 4878 condition = f" AND {condition}" if condition else "" 4879 4880 then_expression = expression.args.get("then") 4881 if isinstance(then_expression, exp.Insert): 4882 this = self.sql(then_expression, "this") 4883 this = f"INSERT {this}" if this else "INSERT" 4884 then = self.sql(then_expression, "expression") 4885 then = f"{this} VALUES {then}" if then else this 4886 elif isinstance(then_expression, exp.Update): 4887 if isinstance(then_expression.args.get("expressions"), exp.Star): 4888 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4889 else: 4890 expressions_sql = self.expressions(then_expression) 4891 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4892 else: 4893 then = self.sql(then_expression) 4894 4895 if isinstance(then_expression, (exp.Insert, exp.Update)): 4896 where = self.sql(then_expression, "where") 4897 if where and not self.SUPPORTS_MERGE_WHERE: 4898 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4899 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4900 where = "" 4901 then = f"{then}{where}" 4902 return f"WHEN {matched}{source}{condition} THEN {then}"
4907 def merge_sql(self, expression: exp.Merge) -> str: 4908 table = expression.this 4909 table_alias = "" 4910 4911 hints = table.args.get("hints") 4912 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4913 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4914 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4915 4916 this = self.sql(table) 4917 using = f"USING {self.sql(expression, 'using')}" 4918 whens = self.sql(expression, "whens") 4919 4920 on = self.sql(expression, "on") 4921 on = f"ON {on}" if on else "" 4922 4923 if not on: 4924 on = self.expressions(expression, key="using_cond") 4925 on = f"USING ({on})" if on else "" 4926 4927 returning = self.sql(expression, "returning") 4928 if returning: 4929 whens = f"{whens}{returning}" 4930 4931 sep = self.sep() 4932 4933 return self.prepend_ctes( 4934 expression, 4935 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4936 )
@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:
4942 @unsupported_args("default") 4943 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4944 if not self.SUPPORTS_TO_NUMBER: 4945 self.unsupported("Unsupported TO_NUMBER function") 4946 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4947 4948 fmt = expression.args.get("format") 4949 if not fmt: 4950 self.unsupported("Conversion format is required for TO_NUMBER") 4951 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4952 4953 return self.func("TO_NUMBER", expression.this, fmt)
4955 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4956 this = self.sql(expression, "this") 4957 kind = self.sql(expression, "kind") 4958 settings_sql = self.expressions(expression, key="settings", sep=" ") 4959 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4960 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:
4981 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4982 expressions = self.expressions(expression, flat=True) 4983 expressions = f" {self.wrap(expressions)}" if expressions else "" 4984 buckets = self.sql(expression, "buckets") 4985 kind = self.sql(expression, "kind") 4986 buckets = f" BUCKETS {buckets}" if buckets else "" 4987 order = self.sql(expression, "order") 4988 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4993 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4994 expressions = self.expressions(expression, key="expressions", flat=True) 4995 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4996 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4997 buckets = self.sql(expression, "buckets") 4998 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5000 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5001 this = self.sql(expression, "this") 5002 having = self.sql(expression, "having") 5003 5004 if having: 5005 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5006 5007 return self.func("ANY_VALUE", this)
5009 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5010 transform = self.func("TRANSFORM", *expression.expressions) 5011 row_format_before = self.sql(expression, "row_format_before") 5012 row_format_before = f" {row_format_before}" if row_format_before else "" 5013 record_writer = self.sql(expression, "record_writer") 5014 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5015 using = f" USING {self.sql(expression, 'command_script')}" 5016 schema = self.sql(expression, "schema") 5017 schema = f" AS {schema}" if schema else "" 5018 row_format_after = self.sql(expression, "row_format_after") 5019 row_format_after = f" {row_format_after}" if row_format_after else "" 5020 record_reader = self.sql(expression, "record_reader") 5021 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5022 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:
5024 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5025 key_block_size = self.sql(expression, "key_block_size") 5026 if key_block_size: 5027 return f"KEY_BLOCK_SIZE = {key_block_size}" 5028 5029 using = self.sql(expression, "using") 5030 if using: 5031 return f"USING {using}" 5032 5033 parser = self.sql(expression, "parser") 5034 if parser: 5035 return f"WITH PARSER {parser}" 5036 5037 comment = self.sql(expression, "comment") 5038 if comment: 5039 return f"COMMENT {comment}" 5040 5041 visible = expression.args.get("visible") 5042 if visible is not None: 5043 return "VISIBLE" if visible else "INVISIBLE" 5044 5045 engine_attr = self.sql(expression, "engine_attr") 5046 if engine_attr: 5047 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5048 5049 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5050 if secondary_engine_attr: 5051 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5052 5053 self.unsupported("Unsupported index constraint option.") 5054 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5060 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5061 kind = self.sql(expression, "kind") 5062 kind = f"{kind} INDEX" if kind else "INDEX" 5063 this = self.sql(expression, "this") 5064 this = f" {this}" if this else "" 5065 index_type = self.sql(expression, "index_type") 5066 index_type = f" USING {index_type}" if index_type else "" 5067 expressions = self.expressions(expression, flat=True) 5068 expressions = f" ({expressions})" if expressions else "" 5069 options = self.expressions(expression, key="options", sep=" ") 5070 options = f" {options}" if options else "" 5071 return f"{kind}{this}{index_type}{expressions}{options}"
5073 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5074 if self.NVL2_SUPPORTED: 5075 return self.function_fallback_sql(expression) 5076 5077 case = exp.Case().when( 5078 expression.this.is_(exp.null()).not_(copy=False), 5079 expression.args["true"], 5080 copy=False, 5081 ) 5082 else_cond = expression.args.get("false") 5083 if else_cond: 5084 case.else_(else_cond, copy=False) 5085 5086 return self.sql(case)
5094 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5095 this = self.sql(expression, "this") 5096 expr = self.sql(expression, "expression") 5097 position = self.sql(expression, "position") 5098 position = f", {position}" if position else "" 5099 iterator = self.sql(expression, "iterator") 5100 condition = self.sql(expression, "condition") 5101 condition = f" IF {condition}" if condition else "" 5102 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5152 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5153 this_sql = self.sql(expression, "this") 5154 if isinstance(expression.this, exp.Table): 5155 this_sql = f"TABLE {this_sql}" 5156 5157 return self.func( 5158 "FORECAST", 5159 this_sql, 5160 expression.args.get("data_col"), 5161 expression.args.get("timestamp_col"), 5162 expression.args.get("model"), 5163 expression.args.get("id_cols"), 5164 expression.args.get("horizon"), 5165 expression.args.get("forecast_end_timestamp"), 5166 expression.args.get("confidence_level"), 5167 expression.args.get("output_historical_time_series"), 5168 expression.args.get("context_window"), 5169 )
5171 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5172 this_sql = self.sql(expression, "this") 5173 if isinstance(expression.this, exp.Table): 5174 this_sql = f"TABLE {this_sql}" 5175 5176 return self.func( 5177 "FEATURES_AT_TIME", 5178 this_sql, 5179 expression.args.get("time"), 5180 expression.args.get("num_rows"), 5181 expression.args.get("ignore_feature_nulls"), 5182 )
5184 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5185 this_sql = self.sql(expression, "this") 5186 if isinstance(expression.this, exp.Table): 5187 this_sql = f"TABLE {this_sql}" 5188 5189 query_table = self.sql(expression, "query_table") 5190 if isinstance(expression.args["query_table"], exp.Table): 5191 query_table = f"TABLE {query_table}" 5192 5193 return self.func( 5194 "VECTOR_SEARCH", 5195 this_sql, 5196 expression.args.get("column_to_search"), 5197 query_table, 5198 expression.args.get("query_column_to_search"), 5199 expression.args.get("top_k"), 5200 expression.args.get("distance_type"), 5201 expression.args.get("options"), 5202 )
5214 def toarray_sql(self, expression: exp.ToArray) -> str: 5215 arg = expression.this 5216 if not arg.type: 5217 import sqlglot.optimizer.annotate_types 5218 5219 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5220 5221 if arg.is_type(exp.DType.ARRAY): 5222 return self.sql(arg) 5223 5224 cond_for_null = arg.is_(exp.null()) 5225 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5227 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5228 this = expression.this 5229 time_format = self.format_time(expression) 5230 5231 if time_format: 5232 return self.sql( 5233 exp.cast( 5234 exp.StrToTime(this=this, format=expression.args["format"]), 5235 exp.DType.TIME, 5236 ) 5237 ) 5238 5239 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5240 return self.sql(this) 5241 5242 return self.sql(exp.cast(this, exp.DType.TIME))
5244 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5245 this = expression.this 5246 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5247 return self.sql(this) 5248 5249 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5251 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5252 this = expression.this 5253 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5254 return self.sql(this) 5255 5256 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5258 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5259 this = expression.this 5260 time_format = self.format_time(expression) 5261 safe = expression.args.get("safe") 5262 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5263 return self.sql( 5264 exp.cast( 5265 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5266 exp.DType.DATE, 5267 ) 5268 ) 5269 5270 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5271 return self.sql(this) 5272 5273 if safe: 5274 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5275 5276 return self.sql(exp.cast(this, exp.DType.DATE))
5288 def lastday_sql(self, expression: exp.LastDay) -> str: 5289 if self.LAST_DAY_SUPPORTS_DATE_PART: 5290 return self.function_fallback_sql(expression) 5291 5292 unit = expression.args.get("unit") 5293 if unit and unit.name.upper() != "MONTH": 5294 self.unsupported("Date parts are not supported in LAST_DAY.") 5295 5296 return self.func("LAST_DAY", expression.this)
5308 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5309 if self.CAN_IMPLEMENT_ARRAY_ANY: 5310 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5311 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5312 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5313 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5314 5315 import sqlglot.dialects.dialect 5316 5317 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5318 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5319 self.unsupported("ARRAY_ANY is unsupported") 5320 5321 return self.function_fallback_sql(expression)
5323 def struct_sql(self, expression: exp.Struct) -> str: 5324 expression.set( 5325 "expressions", 5326 [ 5327 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5328 if isinstance(e, exp.PropertyEQ) 5329 else e 5330 for e in expression.expressions 5331 ], 5332 ) 5333 5334 return self.function_fallback_sql(expression)
5342 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5343 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5344 tables = f" {self.expressions(expression)}" 5345 5346 exists = " IF EXISTS" if expression.args.get("exists") else "" 5347 5348 on_cluster = self.sql(expression, "cluster") 5349 on_cluster = f" {on_cluster}" if on_cluster else "" 5350 5351 identity = self.sql(expression, "identity") 5352 identity = f" {identity} IDENTITY" if identity else "" 5353 5354 option = self.sql(expression, "option") 5355 option = f" {option}" if option else "" 5356 5357 partition = self.sql(expression, "partition") 5358 partition = f" {partition}" if partition else "" 5359 5360 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5364 def convert_sql(self, expression: exp.Convert) -> str: 5365 to = expression.this 5366 value = expression.expression 5367 style = expression.args.get("style") 5368 safe = expression.args.get("safe") 5369 strict = expression.args.get("strict") 5370 5371 if not to or not value: 5372 return "" 5373 5374 # Retrieve length of datatype and override to default if not specified 5375 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5376 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5377 5378 transformed: exp.Expr | None = None 5379 cast = exp.Cast if strict else exp.TryCast 5380 5381 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5382 if isinstance(style, exp.Literal) and style.is_int: 5383 import sqlglot.dialects.tsql 5384 5385 style_value = style.name 5386 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5387 if not converted_style: 5388 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5389 5390 fmt = exp.Literal.string(converted_style) 5391 5392 if to.this == exp.DType.DATE: 5393 transformed = exp.StrToDate(this=value, format=fmt) 5394 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5395 transformed = exp.StrToTime(this=value, format=fmt) 5396 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5397 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5398 elif to.this == exp.DType.TEXT: 5399 transformed = exp.TimeToStr(this=value, format=fmt) 5400 5401 if not transformed: 5402 transformed = cast(this=value, to=to, safe=safe) 5403 5404 return self.sql(transformed)
5481 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5482 option = self.sql(expression, "this") 5483 5484 if expression.expressions: 5485 upper = option.upper() 5486 5487 # Snowflake FILE_FORMAT options are separated by whitespace 5488 sep = " " if upper == "FILE_FORMAT" else ", " 5489 5490 # Databricks copy/format options do not set their list of values with EQ 5491 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5492 values = self.expressions(expression, flat=True, sep=sep) 5493 return f"{option}{op}({values})" 5494 5495 value = self.sql(expression, "expression") 5496 5497 if not value: 5498 return option 5499 5500 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5501 5502 return f"{option}{op}{value}"
5504 def credentials_sql(self, expression: exp.Credentials) -> str: 5505 cred_expr = expression.args.get("credentials") 5506 if isinstance(cred_expr, exp.Literal): 5507 # Redshift case: CREDENTIALS <string> 5508 credentials = self.sql(expression, "credentials") 5509 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5510 else: 5511 # Snowflake case: CREDENTIALS = (...) 5512 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5513 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5514 5515 storage = self.sql(expression, "storage") 5516 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5517 5518 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5519 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5520 5521 iam_role = self.sql(expression, "iam_role") 5522 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5523 5524 region = self.sql(expression, "region") 5525 region = f" REGION {region}" if region else "" 5526 5527 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5529 def copy_sql(self, expression: exp.Copy) -> str: 5530 this = self.sql(expression, "this") 5531 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5532 5533 credentials = self.sql(expression, "credentials") 5534 credentials = self.seg(credentials) if credentials else "" 5535 files = self.expressions(expression, key="files", flat=True) 5536 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5537 5538 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5539 params = self.expressions( 5540 expression, 5541 key="params", 5542 sep=sep, 5543 new_line=True, 5544 skip_last=True, 5545 skip_first=True, 5546 indent=self.COPY_PARAMS_ARE_WRAPPED, 5547 ) 5548 5549 if params: 5550 if self.COPY_PARAMS_ARE_WRAPPED: 5551 params = f" WITH ({params})" 5552 elif not self.pretty and (files or credentials): 5553 params = f" {params}" 5554 5555 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5560 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5561 on_sql = "ON" if expression.args.get("on") else "OFF" 5562 filter_col: str | None = self.sql(expression, "filter_column") 5563 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5564 retention_period: str | None = self.sql(expression, "retention_period") 5565 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5566 5567 if filter_col or retention_period: 5568 on_sql = self.func("ON", filter_col, retention_period) 5569 5570 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5572 def maskingpolicycolumnconstraint_sql( 5573 self, expression: exp.MaskingPolicyColumnConstraint 5574 ) -> str: 5575 this = self.sql(expression, "this") 5576 expressions = self.expressions(expression, flat=True) 5577 expressions = f" USING ({expressions})" if expressions else "" 5578 return f"MASKING POLICY {this}{expressions}"
5588 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5589 this = self.sql(expression, "this") 5590 expr = expression.expression 5591 5592 if isinstance(expr, exp.Func): 5593 # T-SQL's CLR functions are case sensitive 5594 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5595 else: 5596 expr = self.sql(expression, "expression") 5597 5598 return self.scope_resolution(expr, this)
5606 def rand_sql(self, expression: exp.Rand) -> str: 5607 lower = self.sql(expression, "lower") 5608 upper = self.sql(expression, "upper") 5609 5610 if lower and upper: 5611 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5612 return self.func("RAND", expression.this)
5614 def changes_sql(self, expression: exp.Changes) -> str: 5615 information = self.sql(expression, "information") 5616 information = f"INFORMATION => {information}" 5617 at_before = self.sql(expression, "at_before") 5618 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5619 end = self.sql(expression, "end") 5620 end = f"{self.seg('')}{end}" if end else "" 5621 5622 return f"CHANGES ({information}){at_before}{end}"
5624 def pad_sql(self, expression: exp.Pad) -> str: 5625 prefix = "L" if expression.args.get("is_left") else "R" 5626 5627 fill_pattern = self.sql(expression, "fill_pattern") or None 5628 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5629 fill_pattern = "' '" 5630 5631 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5637 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5638 generate_series = exp.GenerateSeries(**expression.args) 5639 5640 parent = expression.parent 5641 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5642 parent = parent.parent 5643 5644 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5645 return self.sql(exp.Unnest(expressions=[generate_series])) 5646 5647 if isinstance(parent, exp.Select): 5648 self.unsupported("GenerateSeries projection unnesting is not supported.") 5649 5650 return self.sql(generate_series)
5652 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5653 if self.SUPPORTS_CONVERT_TIMEZONE: 5654 return self.function_fallback_sql(expression) 5655 5656 source_tz = expression.args.get("source_tz") 5657 target_tz = expression.args.get("target_tz") 5658 timestamp = expression.args.get("timestamp") 5659 5660 if source_tz and timestamp: 5661 timestamp = exp.AtTimeZone( 5662 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5663 ) 5664 5665 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5666 5667 return self.sql(expr)
5669 def json_sql(self, expression: exp.JSON) -> str: 5670 this = self.sql(expression, "this") 5671 this = f" {this}" if this else "" 5672 5673 _with = expression.args.get("with_") 5674 5675 if _with is None: 5676 with_sql = "" 5677 elif not _with: 5678 with_sql = " WITHOUT" 5679 else: 5680 with_sql = " WITH" 5681 5682 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5683 5684 return f"JSON{this}{with_sql}{unique_sql}"
5686 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5687 path = self.sql(expression, "path") 5688 returning = self.sql(expression, "returning") 5689 returning = f" RETURNING {returning}" if returning else "" 5690 5691 on_condition = self.sql(expression, "on_condition") 5692 on_condition = f" {on_condition}" if on_condition else "" 5693 5694 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5700 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5701 else_ = "ELSE " if expression.args.get("else_") else "" 5702 condition = self.sql(expression, "expression") 5703 condition = f"WHEN {condition} THEN " if condition else else_ 5704 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5705 return f"{condition}{insert}"
5713 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5714 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5715 empty = expression.args.get("empty") 5716 empty = ( 5717 f"DEFAULT {empty} ON EMPTY" 5718 if isinstance(empty, exp.Expr) 5719 else self.sql(expression, "empty") 5720 ) 5721 5722 error = expression.args.get("error") 5723 error = ( 5724 f"DEFAULT {error} ON ERROR" 5725 if isinstance(error, exp.Expr) 5726 else self.sql(expression, "error") 5727 ) 5728 5729 if error and empty: 5730 error = ( 5731 f"{empty} {error}" 5732 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5733 else f"{error} {empty}" 5734 ) 5735 empty = "" 5736 5737 null = self.sql(expression, "null") 5738 5739 return f"{empty}{error}{null}"
5745 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5746 this = self.sql(expression, "this") 5747 path = self.sql(expression, "path") 5748 5749 passing = self.expressions(expression, "passing") 5750 passing = f" PASSING {passing}" if passing else "" 5751 5752 on_condition = self.sql(expression, "on_condition") 5753 on_condition = f" {on_condition}" if on_condition else "" 5754 5755 path = f"{path}{passing}{on_condition}" 5756 5757 return self.func("JSON_EXISTS", this, path)
5799 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5800 array_agg = self.function_fallback_sql(expression) 5801 column_expr = expression.this 5802 if isinstance(column_expr, exp.Order): 5803 column_expr = column_expr.this 5804 5805 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5886 def overlay_sql(self, expression: exp.Overlay) -> str: 5887 this = self.sql(expression, "this") 5888 expr = self.sql(expression, "expression") 5889 from_sql = self.sql(expression, "from_") 5890 for_sql = self.sql(expression, "for_") 5891 for_sql = f" FOR {for_sql}" if for_sql else "" 5892 5893 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5900 def string_sql(self, expression: exp.String) -> str: 5901 this = expression.this 5902 zone = expression.args.get("zone") 5903 5904 if zone: 5905 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5906 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5907 # set for source_tz to transpile the time conversion before the STRING cast 5908 this = exp.ConvertTimezone( 5909 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5910 ) 5911 5912 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5922 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5923 filler = self.sql(expression, "this") 5924 filler = f" {filler}" if filler else "" 5925 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5926 return f"TRUNCATE{filler} {with_count}"
5928 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5929 if self.SUPPORTS_UNIX_SECONDS: 5930 return self.function_fallback_sql(expression) 5931 5932 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5933 5934 return self.sql( 5935 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5936 )
5938 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5939 dim = expression.expression 5940 5941 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5942 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5943 if not (dim.is_int and dim.name == "1"): 5944 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5945 dim = None 5946 5947 # If dimension is required but not specified, default initialize it 5948 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5949 dim = exp.Literal.number(1) 5950 5951 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5953 def attach_sql(self, expression: exp.Attach) -> str: 5954 this = self.sql(expression, "this") 5955 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5956 expressions = self.expressions(expression) 5957 expressions = f" ({expressions})" if expressions else "" 5958 5959 return f"ATTACH{exists_sql} {this}{expressions}"
5961 def detach_sql(self, expression: exp.Detach) -> str: 5962 kind = self.sql(expression, "kind") 5963 kind = f" {kind}" if kind else "" 5964 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5965 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5966 exists = " IF EXISTS" if expression.args.get("exists") else "" 5967 if exists: 5968 kind = kind or " DATABASE" 5969 5970 this = self.sql(expression, "this") 5971 this = f" {this}" if this else "" 5972 cluster = self.sql(expression, "cluster") 5973 cluster = f" {cluster}" if cluster else "" 5974 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5975 sync = " SYNC" if expression.args.get("sync") else "" 5976 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5989 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5990 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5991 encode = f"{encode} {self.sql(expression, 'this')}" 5992 5993 properties = expression.args.get("properties") 5994 if properties: 5995 encode = f"{encode} {self.properties(properties)}" 5996 5997 return encode
5999 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6000 this = self.sql(expression, "this") 6001 include = f"INCLUDE {this}" 6002 6003 column_def = self.sql(expression, "column_def") 6004 if column_def: 6005 include = f"{include} {column_def}" 6006 6007 alias = self.sql(expression, "alias") 6008 if alias: 6009 include = f"{include} AS {alias}" 6010 6011 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6024 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6025 partitions = self.expressions(expression, "partition_expressions") 6026 create = self.expressions(expression, "create_expressions") 6027 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6029 def partitionbyrangepropertydynamic_sql( 6030 self, expression: exp.PartitionByRangePropertyDynamic 6031 ) -> str: 6032 start = self.sql(expression, "start") 6033 end = self.sql(expression, "end") 6034 6035 every = expression.args["every"] 6036 if isinstance(every, exp.Interval) and every.this.is_string: 6037 every.this.replace(exp.Literal.number(every.name)) 6038 6039 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6052 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6053 kind = self.sql(expression, "kind") 6054 option = self.sql(expression, "option") 6055 option = f" {option}" if option else "" 6056 this = self.sql(expression, "this") 6057 this = f" {this}" if this else "" 6058 columns = self.expressions(expression) 6059 columns = f" {columns}" if columns else "" 6060 return f"{kind}{option} STATISTICS{this}{columns}"
6062 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6063 this = self.sql(expression, "this") 6064 columns = self.expressions(expression) 6065 inner_expression = self.sql(expression, "expression") 6066 inner_expression = f" {inner_expression}" if inner_expression else "" 6067 update_options = self.sql(expression, "update_options") 6068 update_options = f" {update_options} UPDATE" if update_options else "" 6069 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6080 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6081 kind = self.sql(expression, "kind") 6082 this = self.sql(expression, "this") 6083 this = f" {this}" if this else "" 6084 inner_expression = self.sql(expression, "expression") 6085 return f"VALIDATE {kind}{this}{inner_expression}"
6087 def analyze_sql(self, expression: exp.Analyze) -> str: 6088 options = self.expressions(expression, key="options", sep=" ") 6089 options = f" {options}" if options else "" 6090 kind = self.sql(expression, "kind") 6091 kind = f" {kind}" if kind else "" 6092 tables = self.expressions(expression, key="tables", flat=True) 6093 tables = f" {tables}" if tables else "" 6094 mode = self.sql(expression, "mode") 6095 mode = f" {mode}" if mode else "" 6096 properties = self.sql(expression, "properties") 6097 properties = f" {properties}" if properties else "" 6098 partition = self.sql(expression, "partition") 6099 partition = f" {partition}" if partition else "" 6100 inner_expression = self.sql(expression, "expression") 6101 inner_expression = f" {inner_expression}" if inner_expression else "" 6102 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6104 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6105 this = self.sql(expression, "this") 6106 namespaces = self.expressions(expression, key="namespaces") 6107 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6108 passing = self.expressions(expression, key="passing") 6109 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6110 columns = self.expressions(expression, key="columns") 6111 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6112 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6113 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6119 def export_sql(self, expression: exp.Export) -> str: 6120 this = self.sql(expression, "this") 6121 connection = self.sql(expression, "connection") 6122 connection = f"WITH CONNECTION {connection} " if connection else "" 6123 options = self.sql(expression, "options") 6124 return f"EXPORT DATA {connection}{options} AS {this}"
6130 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6131 variables = self.expressions(expression, "this") 6132 default = self.sql(expression, "default") 6133 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6134 6135 kind = self.sql(expression, "kind") 6136 if isinstance(expression.args.get("kind"), exp.Schema): 6137 kind = f"TABLE {kind}" 6138 6139 kind = f" {kind}" if kind else "" 6140 6141 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6143 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6144 kind = self.sql(expression, "kind") 6145 this = self.sql(expression, "this") 6146 set = self.sql(expression, "expression") 6147 using = self.sql(expression, "using") 6148 using = f" USING {using}" if using else "" 6149 6150 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6151 6152 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:
6175 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6176 # Snowflake GET/PUT statements: 6177 # PUT <file> <internalStage> <properties> 6178 # GET <internalStage> <file> <properties> 6179 props = expression.args.get("properties") 6180 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6181 this = self.sql(expression, "this") 6182 target = self.sql(expression, "target") 6183 6184 if isinstance(expression, exp.Put): 6185 return f"PUT {this} {target}{props_sql}" 6186 else: 6187 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6189 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6190 this = self.sql(expression, "this") 6191 expr = self.sql(expression, "expression") 6192 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6193 return f"TRANSLATE({this} USING {expr}{with_error})"
6195 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6196 if self.SUPPORTS_DECODE_CASE: 6197 return self.func("DECODE", *expression.expressions) 6198 6199 decode_expr, *expressions = expression.expressions 6200 6201 ifs = [] 6202 for search, result in zip(expressions[::2], expressions[1::2]): 6203 if isinstance(search, exp.Literal): 6204 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6205 elif isinstance(search, exp.Null): 6206 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6207 else: 6208 if isinstance(search, exp.Binary): 6209 search = exp.paren(search) 6210 6211 cond = exp.or_( 6212 decode_expr.eq(search), 6213 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6214 copy=False, 6215 ) 6216 ifs.append(exp.If(this=cond, true=result)) 6217 6218 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6219 return self.sql(case)
6221 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6222 this = self.sql(expression, "this") 6223 this = self.seg(this, sep="") 6224 dimensions = self.expressions( 6225 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6226 ) 6227 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6228 metrics = self.expressions( 6229 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6230 ) 6231 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6232 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6233 facts = self.seg(f"FACTS {facts}") if facts else "" 6234 where = self.sql(expression, "where") 6235 where = self.seg(f"WHERE {where}") if where else "" 6236 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6237 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6239 def getextract_sql(self, expression: exp.GetExtract) -> str: 6240 this = expression.this 6241 expr = expression.expression 6242 6243 if not this.type or not expression.type: 6244 import sqlglot.optimizer.annotate_types 6245 6246 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6247 6248 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6249 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6250 6251 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6268 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6269 method = self.sql(expression, "method") 6270 kind = expression.args.get("kind") 6271 if not kind: 6272 return f"REFRESH {method}" 6273 6274 every = self.sql(expression, "every") 6275 unit = self.sql(expression, "unit") 6276 every = f" EVERY {every} {unit}" if every else "" 6277 starts = self.sql(expression, "starts") 6278 starts = f" STARTS {starts}" if starts else "" 6279 6280 return f"REFRESH {method} ON {kind}{every}{starts}"
6289 def uuid_sql(self, expression: exp.Uuid) -> str: 6290 is_string = expression.args.get("is_string", False) 6291 uuid_func_sql = self.func("UUID") 6292 6293 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6294 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6295 6296 return uuid_func_sql
6298 def initcap_sql(self, expression: exp.Initcap) -> str: 6299 delimiters = expression.expression 6300 6301 if delimiters: 6302 # do not generate delimiters arg if we are round-tripping from default delimiters 6303 if ( 6304 delimiters.is_string 6305 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6306 ): 6307 delimiters = None 6308 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6309 self.unsupported("INITCAP does not support custom delimiters") 6310 delimiters = None 6311 6312 return self.func("INITCAP", expression.this, delimiters)
6322 def weekstart_name(self, expression: exp.WeekStart) -> str: 6323 import sqlglot.dialects.dialect 6324 6325 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6326 this = expression.this.name.upper() 6327 6328 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6329 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6330 6331 if dow_from_week_start_day != dow_from_week_offset: 6332 self.unsupported( 6333 f"WEEK({this}) is not supported; falling back to the default week start day" 6334 ) 6335 6336 return "WEEK"
6338 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6339 name = self.weekstart_name(expression) 6340 6341 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6342 if isinstance(expression.parent, exp.DateTrunc): 6343 return self.sql(exp.Literal.string(name)) 6344 6345 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: