sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.expressions import apply_index_offset 12from sqlglot.expressions.core import maybe_parse 13from sqlglot.helper import csv, name_sequence, seq_get 14from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 15from sqlglot.time import format_time 16from sqlglot.tokens import TokenType 17 18if t.TYPE_CHECKING: 19 from sqlglot._typing import E 20 from sqlglot.dialects.dialect import DialectType 21 22 G = t.TypeVar("G", bound="Generator") 23 GeneratorMethod = t.Callable[[G, E], str] 24 25logger = logging.getLogger("sqlglot") 26 27ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 28UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 29 30 31def unsupported_args( 32 *args: str | tuple[str, str], 33) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 34 """ 35 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 36 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 37 """ 38 diagnostic_by_arg: dict[str, str | None] = {} 39 for arg in args: 40 if isinstance(arg, str): 41 diagnostic_by_arg[arg] = None 42 else: 43 diagnostic_by_arg[arg[0]] = arg[1] 44 45 def decorator(func: GeneratorMethod) -> GeneratorMethod: 46 @wraps(func) 47 def _func(generator: G, expression: E) -> str: 48 expression_name = expression.__class__.__name__ 49 dialect_name = generator.dialect.__class__.__name__ 50 51 for arg_name, diagnostic in diagnostic_by_arg.items(): 52 if expression.args.get(arg_name): 53 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 54 arg_name, expression_name, dialect_name 55 ) 56 generator.unsupported(diagnostic) 57 58 return func(generator, expression) 59 60 return _func 61 62 return decorator 63 64 65AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 66 "windows": lambda self, e: ( 67 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 68 if e.args.get("windows") 69 else "" 70 ), 71 "qualify": lambda self, e: self.sql(e, "qualify"), 72} 73 74 75_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 76 77 78def _build_dispatch( 79 cls: type[Generator], 80) -> dict[type[exp.Expr], t.Callable[..., str]]: 81 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 82 83 for attr_name in dir(cls): 84 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 85 continue 86 87 expr_key = attr_name[:-4] 88 expr_cls = exp.EXPR_CLASSES.get(expr_key) 89 90 if expr_cls and expr_cls not in dispatch: 91 dispatch[expr_cls] = getattr(cls, attr_name) 92 93 return dispatch 94 95 96class Generator: 97 """ 98 Generator converts a given syntax tree to the corresponding SQL string. 99 100 Args: 101 pretty: Whether to format the produced SQL string. 102 Default: False. 103 identify: Determines when an identifier should be quoted. Possible values are: 104 False (default): Never quote, except in cases where it's mandatory by the dialect. 105 True: Always quote except for specials cases. 106 'safe': Only quote identifiers that are case insensitive. 107 normalize: Whether to normalize identifiers to lowercase. 108 Default: False. 109 pad: The pad size in a formatted string. For example, this affects the indentation of 110 a projection in a query, relative to its nesting level. 111 Default: 2. 112 indent: The indentation size in a formatted string. For example, this affects the 113 indentation of subqueries and filters under a `WHERE` clause. 114 Default: 2. 115 normalize_functions: How to normalize function names. Possible values are: 116 "upper" or True (default): Convert names to uppercase. 117 "lower": Convert names to lowercase. 118 False: Disables function name normalization. 119 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 120 Default ErrorLevel.WARN. 121 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 122 This is only relevant if unsupported_level is ErrorLevel.RAISE. 123 Default: 3 124 leading_comma: Whether the comma is leading or trailing in select expressions. 125 This is only relevant when generating in pretty mode. 126 Default: False 127 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 128 The default is on the smaller end because the length only represents a segment and not the true 129 line length. 130 Default: 80 131 comments: Whether to preserve comments in the output SQL code. 132 Default: True 133 """ 134 135 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 136 **JSON_PATH_PART_TRANSFORMS, 137 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 138 exp.AllowedValuesProperty: lambda self, e: ( 139 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 140 ), 141 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 142 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 143 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 144 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 145 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 146 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 147 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 148 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 149 exp.CaseSpecificColumnConstraint: lambda _, e: ( 150 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 151 ), 152 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 153 exp.Ceil: lambda self, e: self.ceil_floor(e), 154 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 155 exp.CharacterSetProperty: lambda self, e: ( 156 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 157 ), 158 exp.ClusteredColumnConstraint: lambda self, e: ( 159 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 160 ), 161 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 162 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 163 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 164 exp.ConvertToCharset: lambda self, e: self.func( 165 "CONVERT", e.this, e.args["dest"], e.args.get("source") 166 ), 167 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 168 exp.CredentialsProperty: lambda self, e: ( 169 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 170 ), 171 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 172 exp.SessionUser: lambda *_: "SESSION_USER", 173 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 174 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 175 exp.ApiProperty: lambda *_: "API", 176 exp.ApplicationProperty: lambda *_: "APPLICATION", 177 exp.CatalogProperty: lambda *_: "CATALOG", 178 exp.ComputeProperty: lambda *_: "COMPUTE", 179 exp.DatabaseProperty: lambda *_: "DATABASE", 180 exp.DynamicProperty: lambda *_: "DYNAMIC", 181 exp.EmptyProperty: lambda *_: "EMPTY", 182 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 183 exp.EndStatement: lambda *_: "END", 184 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 185 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 186 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 187 exp.EphemeralColumnConstraint: lambda self, e: ( 188 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 189 ), 190 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 191 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 192 exp.Except: lambda self, e: self.set_operations(e), 193 exp.ExternalProperty: lambda *_: "EXTERNAL", 194 exp.Floor: lambda self, e: self.ceil_floor(e), 195 exp.Get: lambda self, e: self.get_put_sql(e), 196 exp.GlobalProperty: lambda *_: "GLOBAL", 197 exp.HeapProperty: lambda *_: "HEAP", 198 exp.HybridProperty: lambda *_: "HYBRID", 199 exp.IcebergProperty: lambda *_: "ICEBERG", 200 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 201 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 202 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 203 exp.Intersect: lambda self, e: self.set_operations(e), 204 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 205 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 206 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 207 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 208 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 209 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 210 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 211 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 212 exp.LanguageProperty: lambda self, e: self.naked_property(e), 213 exp.LocationProperty: lambda self, e: self.naked_property(e), 214 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 215 exp.MaskingProperty: lambda *_: "MASKING", 216 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 217 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 218 exp.NetworkProperty: lambda *_: "NETWORK", 219 exp.NonClusteredColumnConstraint: lambda self, e: ( 220 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 221 ), 222 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 223 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 224 exp.OnCommitProperty: lambda _, e: ( 225 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 226 ), 227 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 228 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 229 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 230 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 231 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 232 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 233 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 234 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 235 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 236 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 237 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 238 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 239 f"PROJECTION POLICY {self.sql(e, 'this')}" 240 ), 241 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 242 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 243 exp.Put: lambda self, e: self.get_put_sql(e), 244 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 245 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 246 ), 247 exp.ReturnsProperty: lambda self, e: ( 248 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 249 ), 250 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 251 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 252 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 253 exp.SecureProperty: lambda *_: "SECURE", 254 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 255 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 256 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 257 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 258 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 259 exp.SqlReadWriteProperty: lambda _, e: e.name, 260 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 261 exp.StabilityProperty: lambda _, e: e.name, 262 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 263 exp.StreamingTableProperty: lambda *_: "STREAMING", 264 exp.StrictProperty: lambda *_: "STRICT", 265 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 266 exp.TableColumn: lambda self, e: self.sql(e.this), 267 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 268 exp.TemporaryProperty: lambda *_: "TEMPORARY", 269 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 270 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 271 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 272 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 273 exp.TransientProperty: lambda *_: "TRANSIENT", 274 exp.VirtualProperty: lambda *_: "VIRTUAL", 275 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 276 exp.Union: lambda self, e: self.set_operations(e), 277 exp.UnloggedProperty: lambda *_: "UNLOGGED", 278 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 279 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 280 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 281 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 282 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 283 exp.UtcTimestamp: lambda self, e: self.sql( 284 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 285 ), 286 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 287 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 288 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 289 exp.VolatileProperty: lambda *_: "VOLATILE", 290 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 291 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 292 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 293 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 294 exp.ForceProperty: lambda *_: "FORCE", 295 } 296 297 # Whether null ordering is supported in order by 298 # True: Full Support, None: No support, False: No support for certain cases 299 # such as window specifications, aggregate functions etc 300 NULL_ORDERING_SUPPORTED: bool | None = True 301 302 # Window functions that support NULLS FIRST/LAST 303 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 304 305 # Whether ignore nulls is inside the agg or outside. 306 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 307 IGNORE_NULLS_IN_FUNC = False 308 309 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 310 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 311 IGNORE_NULLS_BEFORE_ORDER = True 312 313 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 314 LOCKING_READS_SUPPORTED = False 315 316 # Whether the EXCEPT and INTERSECT operations can return duplicates 317 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 318 319 # Wrap derived values in parens, usually standard but spark doesn't support it 320 WRAP_DERIVED_VALUES = True 321 322 # Whether create function uses an AS before the RETURN 323 CREATE_FUNCTION_RETURN_AS = True 324 325 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 326 MATCHED_BY_SOURCE = True 327 328 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 329 SUPPORTS_MERGE_WHERE = False 330 331 # Whether the INTERVAL expression works only with values like '1 day' 332 SINGLE_STRING_INTERVAL = False 333 334 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 335 INTERVAL_ALLOWS_PLURAL_FORM = True 336 337 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 338 LIMIT_FETCH = "ALL" 339 340 # Whether limit and fetch allows expresions or just limits 341 LIMIT_ONLY_LITERALS = False 342 343 # Whether a table is allowed to be renamed with a db 344 RENAME_TABLE_WITH_DB = True 345 346 # The separator for grouping sets and rollups 347 GROUPINGS_SEP = "," 348 349 # The string used for creating an index on a table 350 INDEX_ON = "ON" 351 352 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 353 INOUT_SEPARATOR = " " 354 355 # Whether join hints should be generated 356 JOIN_HINTS = True 357 358 # Whether directed joins are supported 359 DIRECTED_JOINS = False 360 361 # Whether table hints should be generated 362 TABLE_HINTS = True 363 364 # Whether query hints should be generated 365 QUERY_HINTS = True 366 367 # What kind of separator to use for query hints 368 QUERY_HINT_SEP = ", " 369 370 # Whether comparing against booleans (e.g. x IS TRUE) is supported 371 IS_BOOL_ALLOWED = True 372 373 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 374 DUPLICATE_KEY_UPDATE_WITH_SET = True 375 376 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 377 LIMIT_IS_TOP = False 378 379 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 380 RETURNING_END = True 381 382 # Whether to generate an unquoted value for EXTRACT's date part argument 383 EXTRACT_ALLOWS_QUOTES = True 384 385 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 386 TZ_TO_WITH_TIME_ZONE = False 387 388 # Whether the NVL2 function is supported 389 NVL2_SUPPORTED = True 390 391 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 392 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 393 394 # Whether VALUES statements can be used as derived tables. 395 # MySQL 5 and Redshift do not allow this, so when False, it will convert 396 # SELECT * VALUES into SELECT UNION 397 VALUES_AS_TABLE = True 398 399 # Whether the word COLUMN is included when adding a column with ALTER TABLE 400 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 401 402 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 403 UNNEST_WITH_ORDINALITY = True 404 405 # Whether FILTER (WHERE cond) can be used for conditional aggregation 406 AGGREGATE_FILTER_SUPPORTED = True 407 408 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 409 SEMI_ANTI_JOIN_WITH_SIDE = True 410 411 # Whether to include the type of a computed column in the CREATE DDL 412 COMPUTED_COLUMN_WITH_TYPE = True 413 414 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 415 SUPPORTS_TABLE_COPY = True 416 417 # Whether parentheses are required around the table sample's expression 418 TABLESAMPLE_REQUIRES_PARENS = True 419 420 # Whether a table sample clause's size needs to be followed by the ROWS keyword 421 TABLESAMPLE_SIZE_IS_ROWS = True 422 423 # The keyword(s) to use when generating a sample clause 424 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 425 426 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 427 TABLESAMPLE_WITH_METHOD = True 428 429 # The keyword to use when specifying the seed of a sample clause 430 TABLESAMPLE_SEED_KEYWORD = "SEED" 431 432 # Whether COLLATE is a function instead of a binary operator 433 COLLATE_IS_FUNC = False 434 435 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 436 DATA_TYPE_SPECIFIERS_ALLOWED = False 437 438 # Whether conditions require booleans WHERE x = 0 vs WHERE x 439 ENSURE_BOOLS = False 440 441 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 442 CTE_RECURSIVE_KEYWORD_REQUIRED = True 443 444 # Whether CONCAT requires >1 arguments 445 SUPPORTS_SINGLE_ARG_CONCAT = True 446 447 # Whether LAST_DAY function supports a date part argument 448 LAST_DAY_SUPPORTS_DATE_PART = True 449 450 # Whether named columns are allowed in table aliases 451 SUPPORTS_TABLE_ALIAS_COLUMNS = True 452 453 # Whether named columns are allowed in CTE definitions 454 SUPPORTS_NAMED_CTE_COLUMNS = True 455 456 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 457 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 458 459 # What delimiter to use for separating JSON key/value pairs 460 JSON_KEY_VALUE_PAIR_SEP = ":" 461 462 # INSERT OVERWRITE TABLE x override 463 INSERT_OVERWRITE = " OVERWRITE TABLE" 464 465 # Whether the SELECT .. INTO syntax is used instead of CTAS 466 SUPPORTS_SELECT_INTO = False 467 468 # Whether UNLOGGED tables can be created 469 SUPPORTS_UNLOGGED_TABLES = False 470 471 # Whether the CREATE TABLE LIKE statement is supported 472 SUPPORTS_CREATE_TABLE_LIKE = True 473 474 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 475 SUPPORTS_MODIFY_COLUMN = False 476 477 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 478 SUPPORTS_CHANGE_COLUMN = False 479 480 # Whether the LikeProperty needs to be specified inside of the schema clause 481 LIKE_PROPERTY_INSIDE_SCHEMA = False 482 483 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 484 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 485 MULTI_ARG_DISTINCT = True 486 487 # Whether the JSON extraction operators expect a value of type JSON 488 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 489 490 # Whether bracketed keys like ["foo"] are supported in JSON paths 491 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 492 493 # Whether to escape keys using single quotes in JSON paths 494 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 495 496 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 497 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 498 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 499 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 500 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 501 502 # The JSONPathPart expressions supported by this dialect 503 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 504 505 # Whether any(f(x) for x in array) can be implemented by this dialect 506 CAN_IMPLEMENT_ARRAY_ANY = False 507 508 # Whether the function TO_NUMBER is supported 509 SUPPORTS_TO_NUMBER = True 510 511 # Whether EXCLUDE in window specification is supported 512 SUPPORTS_WINDOW_EXCLUDE = False 513 514 # Whether or not set op modifiers apply to the outer set op or select. 515 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 516 # True means limit 1 happens after the set op, False means it it happens on y. 517 SET_OP_MODIFIERS = True 518 519 # Whether parameters from COPY statement are wrapped in parentheses 520 COPY_PARAMS_ARE_WRAPPED = True 521 522 # Whether values of params are set with "=" token or empty space 523 COPY_PARAMS_EQ_REQUIRED = False 524 525 # Whether COPY statement has INTO keyword 526 COPY_HAS_INTO_KEYWORD = True 527 528 # Whether the conditional TRY(expression) function is supported 529 TRY_SUPPORTED = True 530 531 # Whether the UESCAPE syntax in unicode strings is supported 532 SUPPORTS_UESCAPE = True 533 534 # Function used to replace escaped unicode codes in unicode strings 535 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 536 537 # The keyword to use when generating a star projection with excluded columns 538 STAR_EXCEPT = "EXCEPT" 539 540 # The HEX function name 541 HEX_FUNC = "HEX" 542 543 # The keywords to use when prefixing & separating WITH based properties 544 WITH_PROPERTIES_PREFIX = "WITH" 545 546 # Whether to quote the generated expression of exp.JsonPath 547 QUOTE_JSON_PATH = True 548 549 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 550 PAD_FILL_PATTERN_IS_REQUIRED = False 551 552 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 553 SUPPORTS_EXPLODING_PROJECTIONS = True 554 555 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 556 ARRAY_CONCAT_IS_VAR_LEN = True 557 558 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 559 SUPPORTS_CONVERT_TIMEZONE = False 560 561 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 562 SUPPORTS_MEDIAN = True 563 564 # Whether UNIX_SECONDS(timestamp) is supported 565 SUPPORTS_UNIX_SECONDS = False 566 567 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 568 ALTER_SET_WRAPPED = False 569 570 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 571 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 572 # TODO: The normalization should be done by default once we've tested it across all dialects. 573 NORMALIZE_EXTRACT_DATE_PARTS = False 574 575 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 576 PARSE_JSON_NAME: str | None = "PARSE_JSON" 577 578 # The function name of the exp.ArraySize expression 579 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 580 581 # The syntax to use when altering the type of a column 582 ALTER_SET_TYPE = "SET DATA TYPE" 583 584 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 585 # None -> Doesn't support it at all 586 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 587 # True (Postgres) -> Explicitly requires it 588 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 589 590 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 591 SUPPORTS_DECODE_CASE = True 592 593 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 594 SUPPORTS_BETWEEN_FLAGS = False 595 596 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 597 SUPPORTS_LIKE_QUANTIFIERS = True 598 599 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 600 MATCH_AGAINST_TABLE_PREFIX: str | None = None 601 602 # Whether to include the VARIABLE keyword for SET assignments 603 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 604 605 # The keyword to use for default value assignment in DECLARE statements 606 DECLARE_DEFAULT_ASSIGNMENT = "=" 607 608 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 609 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 610 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 611 UPDATE_STATEMENT_SUPPORTS_FROM = True 612 613 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 614 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 615 616 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 617 # - Snowflake: DROP ICEBERG TABLE a.b; 618 # - DuckDB: DROP TABLE a.b; 619 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 620 621 TYPE_MAPPING: t.ClassVar = { 622 exp.DType.DATETIME2: "TIMESTAMP", 623 exp.DType.NCHAR: "CHAR", 624 exp.DType.NVARCHAR: "VARCHAR", 625 exp.DType.MEDIUMTEXT: "TEXT", 626 exp.DType.LONGTEXT: "TEXT", 627 exp.DType.TINYTEXT: "TEXT", 628 exp.DType.BLOB: "VARBINARY", 629 exp.DType.MEDIUMBLOB: "BLOB", 630 exp.DType.LONGBLOB: "BLOB", 631 exp.DType.TINYBLOB: "BLOB", 632 exp.DType.INET: "INET", 633 exp.DType.ROWVERSION: "VARBINARY", 634 exp.DType.SMALLDATETIME: "TIMESTAMP", 635 } 636 637 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 638 639 # mapping of DType to its default parameters, bounds 640 TYPE_PARAM_SETTINGS: t.ClassVar[ 641 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 642 ] = {} 643 644 TIME_PART_SINGULARS: t.ClassVar = { 645 "MICROSECONDS": "MICROSECOND", 646 "SECONDS": "SECOND", 647 "MINUTES": "MINUTE", 648 "HOURS": "HOUR", 649 "DAYS": "DAY", 650 "WEEKS": "WEEK", 651 "MONTHS": "MONTH", 652 "QUARTERS": "QUARTER", 653 "YEARS": "YEAR", 654 } 655 656 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 657 "cluster": lambda self, e: self.sql(e, "cluster"), 658 "distribute": lambda self, e: self.sql(e, "distribute"), 659 "sort": lambda self, e: self.sql(e, "sort"), 660 **AFTER_HAVING_MODIFIER_TRANSFORMS, 661 } 662 663 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 664 665 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 666 667 PARAMETER_TOKEN = "@" 668 NAMED_PLACEHOLDER_TOKEN = ":" 669 670 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 671 672 PROPERTIES_LOCATION: t.ClassVar = { 673 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 675 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 676 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 677 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 678 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 681 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 683 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 685 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 686 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 687 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 688 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 689 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 690 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 694 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 695 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 697 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 698 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 700 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 701 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 702 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 704 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 705 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 710 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 711 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 712 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 713 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 714 exp.HeapProperty: exp.Properties.Location.POST_WITH, 715 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 716 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 718 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 719 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 721 exp.JournalProperty: exp.Properties.Location.POST_NAME, 722 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 727 exp.LogProperty: exp.Properties.Location.POST_NAME, 728 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 729 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 730 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 731 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 733 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 734 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 736 exp.Order: exp.Properties.Location.POST_SCHEMA, 737 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 739 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 741 exp.Property: exp.Properties.Location.POST_WITH, 742 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 746 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 747 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 748 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 753 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 754 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 755 exp.Set: exp.Properties.Location.POST_SCHEMA, 756 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.SetProperty: exp.Properties.Location.POST_CREATE, 758 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 760 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 761 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 762 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 768 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.Tags: exp.Properties.Location.POST_WITH, 770 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 771 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 773 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 775 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 776 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 777 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 780 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 781 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 782 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 783 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 784 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 785 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 787 } 788 789 # Keywords that can't be used as unquoted identifier names 790 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 791 792 # Exprs whose comments are separated from them for better formatting 793 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 794 exp.Command, 795 exp.Create, 796 exp.Describe, 797 exp.Delete, 798 exp.Drop, 799 exp.From, 800 exp.Insert, 801 exp.Join, 802 exp.MultitableInserts, 803 exp.Order, 804 exp.Group, 805 exp.Having, 806 exp.Select, 807 exp.SetOperation, 808 exp.Update, 809 exp.Where, 810 exp.With, 811 ) 812 813 # Exprs that should not have their comments generated in maybe_comment 814 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 815 exp.Binary, 816 exp.SetOperation, 817 ) 818 819 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 820 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 821 exp.Column, 822 exp.Literal, 823 exp.Neg, 824 exp.Paren, 825 ) 826 827 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 828 exp.DType.NVARCHAR, 829 exp.DType.VARCHAR, 830 exp.DType.CHAR, 831 exp.DType.NCHAR, 832 } 833 834 # Exprs that need to have all CTEs under them bubbled up to them 835 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 836 837 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 838 839 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 840 841 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 842 843 __slots__ = ( 844 "pretty", 845 "identify", 846 "normalize", 847 "pad", 848 "_indent", 849 "normalize_functions", 850 "unsupported_level", 851 "max_unsupported", 852 "leading_comma", 853 "max_text_width", 854 "comments", 855 "dialect", 856 "unsupported_messages", 857 "_escaped_quote_end", 858 "_escaped_byte_quote_end", 859 "_escaped_identifier_end", 860 "_next_name", 861 "_identifier_start", 862 "_identifier_end", 863 "_quote_json_path_key_using_brackets", 864 "_dispatch", 865 ) 866 867 def __init__( 868 self, 869 pretty: bool | int | None = None, 870 identify: str | bool = False, 871 normalize: bool = False, 872 pad: int = 2, 873 indent: int = 2, 874 normalize_functions: str | bool | None = None, 875 unsupported_level: ErrorLevel = ErrorLevel.WARN, 876 max_unsupported: int = 3, 877 leading_comma: bool = False, 878 max_text_width: int = 80, 879 comments: bool = True, 880 dialect: DialectType = None, 881 ): 882 import sqlglot 883 import sqlglot.dialects.dialect 884 885 self.pretty = pretty if pretty is not None else sqlglot.pretty 886 self.identify = identify 887 self.normalize = normalize 888 self.pad = pad 889 self._indent = indent 890 self.unsupported_level = unsupported_level 891 self.max_unsupported = max_unsupported 892 self.leading_comma = leading_comma 893 self.max_text_width = max_text_width 894 self.comments = comments 895 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 896 897 # This is both a Dialect property and a Generator argument, so we prioritize the latter 898 self.normalize_functions = ( 899 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 900 ) 901 902 self.unsupported_messages: list[str] = [] 903 self._escaped_quote_end: str = ( 904 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 905 ) 906 self._escaped_byte_quote_end: str = ( 907 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 908 if self.dialect.BYTE_END 909 else "" 910 ) 911 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 912 913 self._next_name = name_sequence("_t") 914 915 self._identifier_start = self.dialect.IDENTIFIER_START 916 self._identifier_end = self.dialect.IDENTIFIER_END 917 918 self._quote_json_path_key_using_brackets = True 919 920 cls = type(self) 921 dispatch = _DISPATCH_CACHE.get(cls) 922 if dispatch is None: 923 dispatch = _build_dispatch(cls) 924 _DISPATCH_CACHE[cls] = dispatch 925 self._dispatch = dispatch 926 927 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 928 """ 929 Generates the SQL string corresponding to the given syntax tree. 930 931 Args: 932 expression: The syntax tree. 933 copy: Whether to copy the expression. The generator performs mutations so 934 it is safer to copy. 935 936 Returns: 937 The SQL string corresponding to `expression`. 938 """ 939 if copy: 940 expression = expression.copy() 941 942 expression = self.preprocess(expression) 943 944 self.unsupported_messages = [] 945 sql = self.sql(expression).strip() 946 947 if self.pretty: 948 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 949 950 if self.unsupported_level == ErrorLevel.IGNORE: 951 return sql 952 953 if self.unsupported_level == ErrorLevel.WARN: 954 for msg in self.unsupported_messages: 955 logger.warning(msg) 956 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 957 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 958 959 return sql 960 961 def preprocess(self, expression: exp.Expr) -> exp.Expr: 962 """Apply generic preprocessing transformations to a given expression.""" 963 expression = self._move_ctes_to_top_level(expression) 964 965 if self.ENSURE_BOOLS: 966 import sqlglot.transforms 967 968 expression = sqlglot.transforms.ensure_bools(expression) 969 970 return expression 971 972 def _move_ctes_to_top_level(self, expression: E) -> E: 973 if ( 974 not expression.parent 975 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 976 and any(node.parent is not expression for node in expression.find_all(exp.With)) 977 ): 978 import sqlglot.transforms 979 980 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 981 return expression 982 983 def unsupported(self, message: str) -> None: 984 if self.unsupported_level == ErrorLevel.IMMEDIATE: 985 raise UnsupportedError(message) 986 self.unsupported_messages.append(message) 987 988 def sep(self, sep: str = " ") -> str: 989 return f"{sep.strip()}\n" if self.pretty else sep 990 991 def seg(self, sql: str, sep: str = " ") -> str: 992 return f"{self.sep(sep)}{sql}" 993 994 def sanitize_comment(self, comment: str) -> str: 995 comment = " " + comment if comment[0].strip() else comment 996 comment = comment + " " if comment[-1].strip() else comment 997 998 # Escape block comment markers to prevent premature closure or unintended nesting. 999 # This is necessary because single-line comments (--) are converted to block comments 1000 # (/* */) on output, and any */ in the original text would close the comment early. 1001 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1002 1003 return comment 1004 1005 def maybe_comment( 1006 self, 1007 sql: str, 1008 expression: exp.Expr | None = None, 1009 comments: list[str] | None = None, 1010 separated: bool = False, 1011 ) -> str: 1012 comments = ( 1013 ((expression and expression.comments) if comments is None else comments) # type: ignore 1014 if self.comments 1015 else None 1016 ) 1017 1018 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1019 return sql 1020 1021 comments_list = [ 1022 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1023 for comment in comments 1024 if comment 1025 ] 1026 1027 if not comments_list: 1028 return sql 1029 1030 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1031 comments_sql = self.sep().join(comments_list) 1032 return ( 1033 f"{self.sep()}{comments_sql}{sql}" 1034 if not sql or sql[0].isspace() 1035 else f"{comments_sql}{self.sep()}{sql}" 1036 ) 1037 1038 return f"{sql} {' '.join(comments_list)}" 1039 1040 def wrap(self, expression: exp.Expr | str) -> str: 1041 this_sql = ( 1042 self.sql(expression) 1043 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1044 else self.sql(expression, "this") 1045 ) 1046 if not this_sql: 1047 return "()" 1048 1049 this_sql = self.indent(this_sql, level=1, pad=0) 1050 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1051 1052 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1053 original = self.identify 1054 self.identify = False 1055 result = func(*args, **kwargs) 1056 self.identify = original 1057 return result 1058 1059 def normalize_func(self, name: str) -> str: 1060 if self.normalize_functions == "upper" or self.normalize_functions is True: 1061 return name.upper() 1062 if self.normalize_functions == "lower": 1063 return name.lower() 1064 return name 1065 1066 def indent( 1067 self, 1068 sql: str, 1069 level: int = 0, 1070 pad: int | None = None, 1071 skip_first: bool = False, 1072 skip_last: bool = False, 1073 ) -> str: 1074 if not self.pretty or not sql: 1075 return sql 1076 1077 pad = self.pad if pad is None else pad 1078 lines = sql.split("\n") 1079 1080 return "\n".join( 1081 ( 1082 line 1083 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1084 else f"{' ' * (level * self._indent + pad)}{line}" 1085 ) 1086 for i, line in enumerate(lines) 1087 ) 1088 1089 def sql( 1090 self, 1091 expression: str | exp.Expr | None, 1092 key: str | None = None, 1093 comment: bool = True, 1094 ) -> str: 1095 if not expression: 1096 return "" 1097 1098 if isinstance(expression, str): 1099 return expression 1100 1101 if key: 1102 value = expression.args.get(key) 1103 if value: 1104 return self.sql(value) 1105 return "" 1106 1107 handler = self._dispatch.get(expression.__class__) 1108 1109 if handler: 1110 sql = handler(self, expression) 1111 elif isinstance(expression, exp.Func): 1112 sql = self.function_fallback_sql(expression) 1113 elif isinstance(expression, exp.Property): 1114 sql = self.property_sql(expression) 1115 else: 1116 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1117 1118 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1119 1120 def uncache_sql(self, expression: exp.Uncache) -> str: 1121 table = self.sql(expression, "this") 1122 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1123 return f"UNCACHE TABLE{exists_sql} {table}" 1124 1125 def cache_sql(self, expression: exp.Cache) -> str: 1126 lazy = " LAZY" if expression.args.get("lazy") else "" 1127 table = self.sql(expression, "this") 1128 options = expression.args.get("options") 1129 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1130 sql = self.sql(expression, "expression") 1131 sql = f" AS{self.sep()}{sql}" if sql else "" 1132 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1133 return self.prepend_ctes(expression, sql) 1134 1135 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1136 default = "DEFAULT " if expression.args.get("default") else "" 1137 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1138 1139 def column_parts(self, expression: exp.Column) -> str: 1140 return ".".join( 1141 self.sql(part) 1142 for part in ( 1143 expression.args.get("catalog"), 1144 expression.args.get("db"), 1145 expression.args.get("table"), 1146 expression.args.get("this"), 1147 ) 1148 if part 1149 ) 1150 1151 def column_sql(self, expression: exp.Column) -> str: 1152 join_mark = " (+)" if expression.args.get("join_mark") else "" 1153 1154 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1155 join_mark = "" 1156 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1157 1158 return f"{self.column_parts(expression)}{join_mark}" 1159 1160 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1161 return self.column_sql(expression) 1162 1163 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1164 this = self.sql(expression, "this") 1165 this = f" {this}" if this else "" 1166 position = self.sql(expression, "position") 1167 return f"{position}{this}" 1168 1169 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1170 column = self.sql(expression, "this") 1171 kind = self.sql(expression, "kind") 1172 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1173 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1174 kind = f"{sep}{kind}" if kind else "" 1175 constraints = f" {constraints}" if constraints else "" 1176 position = self.sql(expression, "position") 1177 position = f" {position}" if position else "" 1178 1179 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1180 kind = "" 1181 1182 return f"{exists}{column}{kind}{constraints}{position}" 1183 1184 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1185 this = self.sql(expression, "this") 1186 kind_sql = self.sql(expression, "kind").strip() 1187 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1188 1189 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1190 this = self.sql(expression, "this") 1191 if expression.args.get("not_null"): 1192 persisted = " PERSISTED NOT NULL" 1193 elif expression.args.get("persisted"): 1194 persisted = " PERSISTED" 1195 else: 1196 persisted = "" 1197 1198 return f"AS {this}{persisted}" 1199 1200 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1201 return self.token_sql(TokenType.AUTO_INCREMENT) 1202 1203 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1204 if isinstance(expression.this, list): 1205 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1206 else: 1207 this = self.sql(expression, "this") 1208 1209 return f"COMPRESS {this}" 1210 1211 def generatedasidentitycolumnconstraint_sql( 1212 self, expression: exp.GeneratedAsIdentityColumnConstraint 1213 ) -> str: 1214 this = "" 1215 if expression.this is not None: 1216 on_null = " ON NULL" if expression.args.get("on_null") else "" 1217 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1218 1219 start = expression.args.get("start") 1220 start = f"START WITH {start}" if start else "" 1221 increment = expression.args.get("increment") 1222 increment = f" INCREMENT BY {increment}" if increment else "" 1223 minvalue = expression.args.get("minvalue") 1224 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1225 maxvalue = expression.args.get("maxvalue") 1226 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1227 cycle = expression.args.get("cycle") 1228 cycle_sql = "" 1229 1230 if cycle is not None: 1231 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1232 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1233 1234 sequence_opts = "" 1235 if start or increment or cycle_sql: 1236 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1237 sequence_opts = f" ({sequence_opts.strip()})" 1238 1239 expr = self.sql(expression, "expression") 1240 expr = f"({expr})" if expr else "IDENTITY" 1241 1242 return f"GENERATED{this} AS {expr}{sequence_opts}" 1243 1244 def generatedasrowcolumnconstraint_sql( 1245 self, expression: exp.GeneratedAsRowColumnConstraint 1246 ) -> str: 1247 start = "START" if expression.args.get("start") else "END" 1248 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1249 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1250 1251 def periodforsystemtimeconstraint_sql( 1252 self, expression: exp.PeriodForSystemTimeConstraint 1253 ) -> str: 1254 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1255 1256 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1257 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1258 1259 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1260 desc = expression.args.get("desc") 1261 if desc is not None: 1262 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1263 options = self.expressions(expression, key="options", flat=True, sep=" ") 1264 options = f" {options}" if options else "" 1265 return f"PRIMARY KEY{options}" 1266 1267 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1268 this = self.sql(expression, "this") 1269 this = f" {this}" if this else "" 1270 index_type = expression.args.get("index_type") 1271 index_type = f" USING {index_type}" if index_type else "" 1272 on_conflict = self.sql(expression, "on_conflict") 1273 on_conflict = f" {on_conflict}" if on_conflict else "" 1274 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1275 options = self.expressions(expression, key="options", flat=True, sep=" ") 1276 options = f" {options}" if options else "" 1277 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1278 1279 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1280 input_ = expression.args.get("input_") 1281 output = expression.args.get("output") 1282 variadic = expression.args.get("variadic") 1283 1284 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1285 if variadic: 1286 return "VARIADIC" 1287 1288 if input_ and output: 1289 return f"IN{self.INOUT_SEPARATOR}OUT" 1290 if input_: 1291 return "IN" 1292 if output: 1293 return "OUT" 1294 1295 return "" 1296 1297 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1298 return self.sql(expression, "this") 1299 1300 def create_sql(self, expression: exp.Create) -> str: 1301 kind = self.sql(expression, "kind") 1302 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1303 1304 properties = expression.args.get("properties") 1305 1306 if ( 1307 kind == "TRIGGER" 1308 and properties 1309 and properties.expressions 1310 and isinstance(properties.expressions[0], exp.TriggerProperties) 1311 and properties.expressions[0].args.get("constraint") 1312 ): 1313 kind = f"CONSTRAINT {kind}" 1314 1315 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1316 1317 this = self.createable_sql(expression, properties_locs) 1318 1319 properties_sql = "" 1320 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1321 exp.Properties.Location.POST_WITH 1322 ): 1323 props_ast = exp.Properties( 1324 expressions=[ 1325 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1326 *properties_locs[exp.Properties.Location.POST_WITH], 1327 ] 1328 ) 1329 props_ast.parent = expression 1330 properties_sql = self.sql(props_ast) 1331 1332 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1333 properties_sql = self.sep() + properties_sql 1334 elif not self.pretty: 1335 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1336 properties_sql = f" {properties_sql}" 1337 1338 begin = " BEGIN" if expression.args.get("begin") else "" 1339 1340 expression_sql = self.sql(expression, "expression") 1341 if expression_sql: 1342 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1343 1344 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1345 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1346 ): 1347 postalias_props_sql = "" 1348 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1349 postalias_props_sql = self.properties( 1350 exp.Properties( 1351 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1352 ), 1353 wrapped=False, 1354 ) 1355 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1356 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1357 1358 postindex_props_sql = "" 1359 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1360 postindex_props_sql = self.properties( 1361 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1362 wrapped=False, 1363 prefix=" ", 1364 ) 1365 1366 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1367 indexes = f" {indexes}" if indexes else "" 1368 index_sql = indexes + postindex_props_sql 1369 1370 replace = " OR REPLACE" if expression.args.get("replace") else "" 1371 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1372 unique = " UNIQUE" if expression.args.get("unique") else "" 1373 1374 clustered = expression.args.get("clustered") 1375 if clustered is None: 1376 clustered_sql = "" 1377 elif clustered: 1378 clustered_sql = " CLUSTERED COLUMNSTORE" 1379 else: 1380 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1381 1382 postcreate_props_sql = "" 1383 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1384 postcreate_props_sql = self.properties( 1385 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1386 sep=" ", 1387 prefix=" ", 1388 wrapped=False, 1389 ) 1390 1391 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1392 1393 postexpression_props_sql = "" 1394 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1395 postexpression_props_sql = self.properties( 1396 exp.Properties( 1397 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1398 ), 1399 sep=" ", 1400 prefix=" ", 1401 wrapped=False, 1402 ) 1403 1404 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1405 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1406 no_schema_binding = ( 1407 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1408 ) 1409 1410 clone = self.sql(expression, "clone") 1411 clone = f" {clone}" if clone else "" 1412 1413 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1414 properties_expression = f"{expression_sql}{properties_sql}" 1415 else: 1416 properties_expression = f"{properties_sql}{expression_sql}" 1417 1418 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1419 return self.prepend_ctes(expression, expression_sql) 1420 1421 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1422 start = self.sql(expression, "start") 1423 start = f"START WITH {start}" if start else "" 1424 increment = self.sql(expression, "increment") 1425 increment = f" INCREMENT BY {increment}" if increment else "" 1426 minvalue = self.sql(expression, "minvalue") 1427 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1428 maxvalue = self.sql(expression, "maxvalue") 1429 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1430 owned = self.sql(expression, "owned") 1431 owned = f" OWNED BY {owned}" if owned else "" 1432 1433 cache = expression.args.get("cache") 1434 if cache is None: 1435 cache_str = "" 1436 elif cache is True: 1437 cache_str = " CACHE" 1438 else: 1439 cache_str = f" CACHE {cache}" 1440 1441 options = self.expressions(expression, key="options", flat=True, sep=" ") 1442 options = f" {options}" if options else "" 1443 1444 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1445 1446 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1447 timing = expression.args.get("timing", "") 1448 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1449 timing_events = f"{timing} {events}".strip() if timing or events else "" 1450 1451 parts = [timing_events, "ON", self.sql(expression, "table")] 1452 1453 if referenced_table := expression.args.get("referenced_table"): 1454 parts.extend(["FROM", self.sql(referenced_table)]) 1455 1456 if deferrable := expression.args.get("deferrable"): 1457 parts.append(deferrable) 1458 1459 if initially := expression.args.get("initially"): 1460 parts.append(f"INITIALLY {initially}") 1461 1462 if referencing := expression.args.get("referencing"): 1463 parts.append(self.sql(referencing)) 1464 1465 if for_each := expression.args.get("for_each"): 1466 parts.append(f"FOR EACH {for_each}") 1467 1468 if when := expression.args.get("when"): 1469 parts.append(f"WHEN ({self.sql(when)})") 1470 1471 parts.append(self.sql(expression, "execute")) 1472 1473 return self.sep().join(parts) 1474 1475 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1476 parts = [] 1477 1478 if old_alias := expression.args.get("old"): 1479 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1480 1481 if new_alias := expression.args.get("new"): 1482 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1483 1484 return f"REFERENCING {' '.join(parts)}" 1485 1486 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1487 columns = expression.args.get("columns") 1488 if columns: 1489 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1490 1491 return self.sql(expression, "this") 1492 1493 def clone_sql(self, expression: exp.Clone) -> str: 1494 this = self.sql(expression, "this") 1495 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1496 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1497 return f"{shallow}{keyword} {this}" 1498 1499 def describe_sql(self, expression: exp.Describe) -> str: 1500 style = expression.args.get("style") 1501 style = f" {style}" if style else "" 1502 partition = self.sql(expression, "partition") 1503 partition = f" {partition}" if partition else "" 1504 format = self.sql(expression, "format") 1505 format = f" {format}" if format else "" 1506 as_json = " AS JSON" if expression.args.get("as_json") else "" 1507 1508 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1509 1510 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1511 tag = self.sql(expression, "tag") 1512 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1513 1514 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1515 with_ = self.sql(expression, "with_") 1516 if with_: 1517 sql = f"{with_}{self.sep()}{sql}" 1518 return sql 1519 1520 def with_sql(self, expression: exp.With) -> str: 1521 sql = self.expressions(expression, flat=True) 1522 recursive = ( 1523 "RECURSIVE " 1524 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1525 else "" 1526 ) 1527 search = self.sql(expression, "search") 1528 search = f" {search}" if search else "" 1529 1530 return f"WITH {recursive}{sql}{search}" 1531 1532 def cte_sql(self, expression: exp.CTE) -> str: 1533 alias = expression.args.get("alias") 1534 if alias: 1535 alias.add_comments(expression.pop_comments()) 1536 1537 alias_sql = self.sql(expression, "alias") 1538 1539 materialized = expression.args.get("materialized") 1540 if materialized is False: 1541 materialized = "NOT MATERIALIZED " 1542 elif materialized: 1543 materialized = "MATERIALIZED " 1544 1545 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1546 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1547 1548 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1549 1550 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1551 alias = self.sql(expression, "this") 1552 columns = self.expressions(expression, key="columns", flat=True) 1553 columns = f"({columns})" if columns else "" 1554 1555 if ( 1556 columns 1557 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1558 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1559 ): 1560 columns = "" 1561 self.unsupported("Named columns are not supported in table alias.") 1562 1563 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1564 alias = self._next_name() 1565 1566 return f"{alias}{columns}" 1567 1568 def bitstring_sql(self, expression: exp.BitString) -> str: 1569 this = self.sql(expression, "this") 1570 if self.dialect.BIT_START: 1571 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1572 return f"{int(this, 2)}" 1573 1574 def hexstring_sql( 1575 self, expression: exp.HexString, binary_function_repr: str | None = None 1576 ) -> str: 1577 this = self.sql(expression, "this") 1578 is_integer_type = expression.args.get("is_integer") 1579 1580 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1581 not self.dialect.HEX_START and not binary_function_repr 1582 ): 1583 # Integer representation will be returned if: 1584 # - The read dialect treats the hex value as integer literal but not the write 1585 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1586 return f"{int(this, 16)}" 1587 1588 if not is_integer_type: 1589 # Read dialect treats the hex value as BINARY/BLOB 1590 if binary_function_repr: 1591 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1592 return self.func(binary_function_repr, exp.Literal.string(this)) 1593 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1594 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1595 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1596 1597 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1598 1599 def bytestring_sql(self, expression: exp.ByteString) -> str: 1600 this = self.sql(expression, "this") 1601 if self.dialect.BYTE_START: 1602 escaped_byte_string = self.escape_str( 1603 this, 1604 escape_backslash=False, 1605 delimiter=self.dialect.BYTE_END, 1606 escaped_delimiter=self._escaped_byte_quote_end, 1607 is_byte_string=True, 1608 ) 1609 is_bytes = expression.args.get("is_bytes", False) 1610 delimited_byte_string = ( 1611 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1612 ) 1613 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1614 return self.sql( 1615 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1616 ) 1617 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1618 return self.sql( 1619 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1620 ) 1621 1622 return delimited_byte_string 1623 1624 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1625 return self.sql(exp.Literal.string(this)) 1626 1627 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1628 return "" 1629 1630 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1631 this = self.sql(expression, "this") 1632 escape = expression.args.get("escape") 1633 1634 if self.dialect.UNICODE_START: 1635 escape_substitute = r"\\\1" 1636 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1637 else: 1638 escape_substitute = r"\\u\1" 1639 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1640 1641 if escape: 1642 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1643 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1644 else: 1645 escape_pattern = ESCAPED_UNICODE_RE 1646 escape_sql = "" 1647 1648 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1649 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1650 1651 return f"{left_quote}{this}{right_quote}{escape_sql}" 1652 1653 def rawstring_sql(self, expression: exp.RawString) -> str: 1654 string = expression.this 1655 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1656 string = string.replace("\\", "\\\\") 1657 1658 string = self.escape_str(string, escape_backslash=False) 1659 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1660 1661 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1662 this = self.sql(expression, "this") 1663 specifier = self.sql(expression, "expression") 1664 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1665 return f"{this}{specifier}" 1666 1667 def datatype_param_bound_limiter( 1668 self, 1669 expression: exp.DataType, 1670 type_value: exp.DType, 1671 defaults: tuple[int, ...], 1672 bounds: tuple[int | None, ...], 1673 ) -> exp.DataType: 1674 params = expression.expressions 1675 1676 if not params: 1677 if defaults: 1678 expression.set( 1679 "expressions", 1680 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1681 ) 1682 return expression 1683 1684 if not bounds: 1685 return expression 1686 1687 for i, param in enumerate(params): 1688 bound = bounds[i] if i < len(bounds) else None 1689 if bound is None: 1690 continue 1691 1692 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1693 if ( 1694 isinstance(param_value, exp.Literal) 1695 and param_value.is_number 1696 and int(param_value.to_py()) > bound 1697 ): 1698 self.unsupported( 1699 f"{type_value.value} parameter {param_value.name} exceeds " 1700 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1701 ) 1702 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1703 1704 return expression 1705 1706 def datatype_sql(self, expression: exp.DataType) -> str: 1707 nested = "" 1708 values = "" 1709 1710 expr_nested = expression.args.get("nested") 1711 type_value = expression.this 1712 1713 if ( 1714 not expr_nested 1715 and isinstance(type_value, exp.DType) 1716 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1717 ): 1718 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1719 1720 interior = ( 1721 self.expressions( 1722 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1723 ) 1724 if expr_nested and self.pretty 1725 else self.expressions(expression, flat=True) 1726 ) 1727 1728 if type_value in self.UNSUPPORTED_TYPES: 1729 self.unsupported( 1730 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1731 ) 1732 1733 type_sql: t.Any = "" 1734 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1735 type_sql = self.sql(expression, "kind") 1736 elif type_value == exp.DType.CHARACTER_SET: 1737 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1738 else: 1739 type_sql = ( 1740 self.TYPE_MAPPING.get(type_value, type_value.value) 1741 if isinstance(type_value, exp.DType) 1742 else type_value 1743 ) 1744 1745 if interior: 1746 if expr_nested: 1747 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1748 if expression.args.get("values") is not None: 1749 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1750 values = self.expressions(expression, key="values", flat=True) 1751 values = f"{delimiters[0]}{values}{delimiters[1]}" 1752 elif type_value == exp.DType.INTERVAL: 1753 nested = f" {interior}" 1754 else: 1755 nested = f"({interior})" 1756 1757 type_sql = f"{type_sql}{nested}{values}" 1758 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1759 exp.DType.TIMETZ, 1760 exp.DType.TIMESTAMPTZ, 1761 ): 1762 type_sql = f"{type_sql} WITH TIME ZONE" 1763 1764 collate = self.sql(expression, "collate") 1765 if collate: 1766 type_sql = f"{type_sql} COLLATE {collate}" 1767 1768 return type_sql 1769 1770 def directory_sql(self, expression: exp.Directory) -> str: 1771 local = "LOCAL " if expression.args.get("local") else "" 1772 row_format = self.sql(expression, "row_format") 1773 row_format = f" {row_format}" if row_format else "" 1774 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1775 1776 def delete_sql(self, expression: exp.Delete) -> str: 1777 hint = self.sql(expression, "hint") 1778 this = self.sql(expression, "this") 1779 this = f" FROM {this}" if this else "" 1780 using = self.expressions(expression, key="using") 1781 using = f" USING {using}" if using else "" 1782 cluster = self.sql(expression, "cluster") 1783 cluster = f" {cluster}" if cluster else "" 1784 where = self.sql(expression, "where") 1785 returning = self.sql(expression, "returning") 1786 order = self.sql(expression, "order") 1787 limit = self.sql(expression, "limit") 1788 tables = self.expressions(expression, key="tables") 1789 tables = f" {tables}" if tables else "" 1790 if self.RETURNING_END: 1791 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1792 else: 1793 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1794 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1795 1796 def drop_sql(self, expression: exp.Drop) -> str: 1797 this = self.sql(expression, "this") 1798 expressions = self.expressions(expression, flat=True) 1799 expressions = f" ({expressions})" if expressions else "" 1800 kind = expression.args["kind"] 1801 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1802 iceberg = ( 1803 " ICEBERG" 1804 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1805 else "" 1806 ) 1807 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1808 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1809 on_cluster = self.sql(expression, "cluster") 1810 on_cluster = f" {on_cluster}" if on_cluster else "" 1811 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1812 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1813 cascade = " CASCADE" if expression.args.get("cascade") else "" 1814 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1815 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1816 purge = " PURGE" if expression.args.get("purge") else "" 1817 sync = " SYNC" if expression.args.get("sync") else "" 1818 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1819 1820 def set_operation(self, expression: exp.SetOperation) -> str: 1821 op_type = type(expression) 1822 op_name = op_type.key.upper() 1823 1824 distinct = expression.args.get("distinct") 1825 if ( 1826 distinct is False 1827 and op_type in (exp.Except, exp.Intersect) 1828 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1829 ): 1830 self.unsupported(f"{op_name} ALL is not supported") 1831 1832 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1833 1834 if distinct is None: 1835 distinct = default_distinct 1836 if distinct is None: 1837 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1838 1839 if distinct is default_distinct: 1840 distinct_or_all = "" 1841 else: 1842 distinct_or_all = " DISTINCT" if distinct else " ALL" 1843 1844 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1845 side_kind = f"{side_kind} " if side_kind else "" 1846 1847 by_name = " BY NAME" if expression.args.get("by_name") else "" 1848 on = self.expressions(expression, key="on", flat=True) 1849 on = f" ON ({on})" if on else "" 1850 1851 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1852 1853 def set_operations(self, expression: exp.SetOperation) -> str: 1854 if not self.SET_OP_MODIFIERS: 1855 limit = expression.args.get("limit") 1856 order = expression.args.get("order") 1857 1858 if limit or order: 1859 select = self._move_ctes_to_top_level( 1860 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1861 ) 1862 1863 if limit: 1864 select = select.limit(limit.pop(), copy=False) 1865 if order: 1866 select = select.order_by(order.pop(), copy=False) 1867 return self.sql(select) 1868 1869 sqls: list[str] = [] 1870 stack: list[str | exp.Expr] = [expression] 1871 1872 while stack: 1873 node = stack.pop() 1874 1875 if isinstance(node, exp.SetOperation): 1876 stack.append(node.expression) 1877 stack.append( 1878 self.maybe_comment( 1879 self.set_operation(node), comments=node.comments, separated=True 1880 ) 1881 ) 1882 stack.append(node.this) 1883 else: 1884 sqls.append(self.sql(node)) 1885 1886 this = self.sep().join(sqls) 1887 this = self.query_modifiers(expression, this) 1888 return self.prepend_ctes(expression, this) 1889 1890 def fetch_sql(self, expression: exp.Fetch) -> str: 1891 direction = expression.args.get("direction") 1892 direction = f" {direction}" if direction else "" 1893 count = self.sql(expression, "count") 1894 count = f" {count}" if count else "" 1895 limit_options = self.sql(expression, "limit_options") 1896 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1897 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1898 1899 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1900 percent = " PERCENT" if expression.args.get("percent") else "" 1901 rows = " ROWS" if expression.args.get("rows") else "" 1902 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1903 if not with_ties and rows: 1904 with_ties = " ONLY" 1905 return f"{percent}{rows}{with_ties}" 1906 1907 def filter_sql(self, expression: exp.Filter) -> str: 1908 if self.AGGREGATE_FILTER_SUPPORTED: 1909 this = self.sql(expression, "this") 1910 where = self.sql(expression, "expression").strip() 1911 return f"{this} FILTER({where})" 1912 1913 agg = expression.this 1914 agg_arg = agg.this 1915 cond = expression.expression.this 1916 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1917 return self.sql(agg) 1918 1919 def hint_sql(self, expression: exp.Hint) -> str: 1920 if not self.QUERY_HINTS: 1921 self.unsupported("Hints are not supported") 1922 return "" 1923 1924 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1925 1926 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1927 using = self.sql(expression, "using") 1928 using = f" USING {using}" if using else "" 1929 columns = self.expressions(expression, key="columns", flat=True) 1930 columns = f"({columns})" if columns else "" 1931 partition_by = self.expressions(expression, key="partition_by", flat=True) 1932 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1933 where = self.sql(expression, "where") 1934 include = self.expressions(expression, key="include", flat=True) 1935 if include: 1936 include = f" INCLUDE ({include})" 1937 with_storage = self.expressions(expression, key="with_storage", flat=True) 1938 with_storage = f" WITH ({with_storage})" if with_storage else "" 1939 tablespace = self.sql(expression, "tablespace") 1940 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1941 on = self.sql(expression, "on") 1942 on = f" ON {on}" if on else "" 1943 1944 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1945 1946 def index_sql(self, expression: exp.Index) -> str: 1947 unique = "UNIQUE " if expression.args.get("unique") else "" 1948 primary = "PRIMARY " if expression.args.get("primary") else "" 1949 amp = "AMP " if expression.args.get("amp") else "" 1950 name = self.sql(expression, "this") 1951 name = f"{name} " if name else "" 1952 table = self.sql(expression, "table") 1953 table = f"{self.INDEX_ON} {table}" if table else "" 1954 1955 index = "INDEX " if not table else "" 1956 1957 params = self.sql(expression, "params") 1958 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1959 1960 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1961 this = expression.this 1962 if this and this.is_string: 1963 resolved = maybe_parse(this.name).sql(self.dialect) 1964 if "expressions" in expression.args: 1965 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1966 # We can't safely emit the call to other dialects since name/arg semantics may differ 1967 self.unsupported( 1968 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1969 ) 1970 return resolved 1971 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1972 return self.func("IDENTIFIER", this) 1973 1974 def identifier_sql(self, expression: exp.Identifier) -> str: 1975 text = expression.name 1976 lower = text.lower() 1977 quoted = expression.quoted 1978 text = lower if self.normalize and not quoted else text 1979 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1980 if ( 1981 quoted 1982 or self.dialect.can_quote(expression, self.identify) 1983 or lower in self.RESERVED_KEYWORDS 1984 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1985 ): 1986 text = ( 1987 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1988 ) 1989 return text 1990 1991 def hex_sql(self, expression: exp.Hex) -> str: 1992 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1993 if self.dialect.HEX_LOWERCASE: 1994 text = self.func("LOWER", text) 1995 1996 return text 1997 1998 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1999 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2000 if not self.dialect.HEX_LOWERCASE: 2001 text = self.func("LOWER", text) 2002 return text 2003 2004 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2005 input_format = self.sql(expression, "input_format") 2006 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2007 output_format = self.sql(expression, "output_format") 2008 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2009 return self.sep().join((input_format, output_format)) 2010 2011 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2012 string = self.sql(exp.Literal.string(expression.name)) 2013 return f"{prefix}{string}" 2014 2015 def partition_sql(self, expression: exp.Partition) -> str: 2016 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2017 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2018 2019 def properties_sql(self, expression: exp.Properties) -> str: 2020 root_properties = [] 2021 with_properties = [] 2022 2023 for p in expression.expressions: 2024 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2025 if p_loc == exp.Properties.Location.POST_WITH: 2026 with_properties.append(p) 2027 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2028 root_properties.append(p) 2029 2030 root_props_ast = exp.Properties(expressions=root_properties) 2031 root_props_ast.parent = expression.parent 2032 2033 with_props_ast = exp.Properties(expressions=with_properties) 2034 with_props_ast.parent = expression.parent 2035 2036 root_props = self.root_properties(root_props_ast) 2037 with_props = self.with_properties(with_props_ast) 2038 2039 if root_props and with_props and not self.pretty: 2040 with_props = " " + with_props 2041 2042 return root_props + with_props 2043 2044 def root_properties(self, properties: exp.Properties) -> str: 2045 if properties.expressions: 2046 return self.expressions(properties, indent=False, sep=" ") 2047 return "" 2048 2049 def properties( 2050 self, 2051 properties: exp.Properties, 2052 prefix: str = "", 2053 sep: str = ", ", 2054 suffix: str = "", 2055 wrapped: bool = True, 2056 ) -> str: 2057 if properties.expressions: 2058 expressions = self.expressions(properties, sep=sep, indent=False) 2059 if expressions: 2060 expressions = self.wrap(expressions) if wrapped else expressions 2061 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2062 return "" 2063 2064 def with_properties(self, properties: exp.Properties) -> str: 2065 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2066 2067 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2068 properties_locs = defaultdict(list) 2069 for p in properties.expressions: 2070 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2071 if p_loc != exp.Properties.Location.UNSUPPORTED: 2072 properties_locs[p_loc].append(p) 2073 else: 2074 self.unsupported(f"Unsupported property {p.key}") 2075 2076 return properties_locs 2077 2078 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2079 if isinstance(expression.this, exp.Dot): 2080 return self.sql(expression, "this") 2081 return f"'{expression.name}'" if string_key else expression.name 2082 2083 def property_sql(self, expression: exp.Property) -> str: 2084 property_cls = expression.__class__ 2085 if property_cls == exp.Property: 2086 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2087 2088 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2089 if not property_name: 2090 self.unsupported(f"Unsupported property {expression.key}") 2091 2092 return f"{property_name}={self.sql(expression, 'this')}" 2093 2094 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2095 return f"UUID {self.sql(expression, 'this')}" 2096 2097 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2098 if self.SUPPORTS_CREATE_TABLE_LIKE: 2099 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2100 options = f" {options}" if options else "" 2101 2102 like = f"LIKE {self.sql(expression, 'this')}{options}" 2103 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2104 like = f"({like})" 2105 2106 return like 2107 2108 if expression.expressions: 2109 self.unsupported("Transpilation of LIKE property options is unsupported") 2110 2111 select = exp.select("*").from_(expression.this).limit(0) 2112 return f"AS {self.sql(select)}" 2113 2114 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2115 no = "NO " if expression.args.get("no") else "" 2116 protection = " PROTECTION" if expression.args.get("protection") else "" 2117 return f"{no}FALLBACK{protection}" 2118 2119 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2120 no = "NO " if expression.args.get("no") else "" 2121 local = expression.args.get("local") 2122 local = f"{local} " if local else "" 2123 dual = "DUAL " if expression.args.get("dual") else "" 2124 before = "BEFORE " if expression.args.get("before") else "" 2125 after = "AFTER " if expression.args.get("after") else "" 2126 return f"{no}{local}{dual}{before}{after}JOURNAL" 2127 2128 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2129 freespace = self.sql(expression, "this") 2130 percent = " PERCENT" if expression.args.get("percent") else "" 2131 return f"FREESPACE={freespace}{percent}" 2132 2133 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2134 if expression.args.get("default"): 2135 property = "DEFAULT" 2136 elif expression.args.get("on"): 2137 property = "ON" 2138 else: 2139 property = "OFF" 2140 return f"CHECKSUM={property}" 2141 2142 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2143 if expression.args.get("no"): 2144 return "NO MERGEBLOCKRATIO" 2145 if expression.args.get("default"): 2146 return "DEFAULT MERGEBLOCKRATIO" 2147 2148 percent = " PERCENT" if expression.args.get("percent") else "" 2149 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2150 2151 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2152 expressions = self.expressions(expression, flat=True) 2153 expressions = f"({expressions})" if expressions else "" 2154 return f"USING {self.sql(expression, 'this')}{expressions}" 2155 2156 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2157 default = expression.args.get("default") 2158 minimum = expression.args.get("minimum") 2159 maximum = expression.args.get("maximum") 2160 if default or minimum or maximum: 2161 if default: 2162 prop = "DEFAULT" 2163 elif minimum: 2164 prop = "MINIMUM" 2165 else: 2166 prop = "MAXIMUM" 2167 return f"{prop} DATABLOCKSIZE" 2168 units = expression.args.get("units") 2169 units = f" {units}" if units else "" 2170 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2171 2172 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2173 autotemp = expression.args.get("autotemp") 2174 always = expression.args.get("always") 2175 default = expression.args.get("default") 2176 manual = expression.args.get("manual") 2177 never = expression.args.get("never") 2178 2179 if autotemp is not None: 2180 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2181 elif always: 2182 prop = "ALWAYS" 2183 elif default: 2184 prop = "DEFAULT" 2185 elif manual: 2186 prop = "MANUAL" 2187 elif never: 2188 prop = "NEVER" 2189 return f"BLOCKCOMPRESSION={prop}" 2190 2191 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2192 no = expression.args.get("no") 2193 no = " NO" if no else "" 2194 concurrent = expression.args.get("concurrent") 2195 concurrent = " CONCURRENT" if concurrent else "" 2196 target = self.sql(expression, "target") 2197 target = f" {target}" if target else "" 2198 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2199 2200 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2201 if isinstance(expression.this, list): 2202 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2203 if expression.this: 2204 modulus = self.sql(expression, "this") 2205 remainder = self.sql(expression, "expression") 2206 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2207 2208 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2209 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2210 return f"FROM ({from_expressions}) TO ({to_expressions})" 2211 2212 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2213 this = self.sql(expression, "this") 2214 2215 for_values_or_default = expression.expression 2216 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2217 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2218 else: 2219 for_values_or_default = " DEFAULT" 2220 2221 return f"PARTITION OF {this}{for_values_or_default}" 2222 2223 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2224 kind = expression.args.get("kind") 2225 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2226 for_or_in = expression.args.get("for_or_in") 2227 for_or_in = f" {for_or_in}" if for_or_in else "" 2228 lock_type = expression.args.get("lock_type") 2229 override = " OVERRIDE" if expression.args.get("override") else "" 2230 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2231 2232 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2233 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2234 statistics = expression.args.get("statistics") 2235 statistics_sql = "" 2236 if statistics is not None: 2237 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2238 return f"{data_sql}{statistics_sql}" 2239 2240 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2241 this = self.sql(expression, "this") 2242 this = f"HISTORY_TABLE={this}" if this else "" 2243 data_consistency: str | None = self.sql(expression, "data_consistency") 2244 data_consistency = ( 2245 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2246 ) 2247 retention_period: str | None = self.sql(expression, "retention_period") 2248 retention_period = ( 2249 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2250 ) 2251 2252 if this: 2253 on_sql = self.func("ON", this, data_consistency, retention_period) 2254 else: 2255 on_sql = "ON" if expression.args.get("on") else "OFF" 2256 2257 sql = f"SYSTEM_VERSIONING={on_sql}" 2258 2259 return f"WITH({sql})" if expression.args.get("with_") else sql 2260 2261 def insert_sql(self, expression: exp.Insert) -> str: 2262 hint = self.sql(expression, "hint") 2263 overwrite = expression.args.get("overwrite") 2264 2265 if isinstance(expression.this, exp.Directory): 2266 this = " OVERWRITE" if overwrite else " INTO" 2267 else: 2268 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2269 2270 stored = self.sql(expression, "stored") 2271 stored = f" {stored}" if stored else "" 2272 alternative = expression.args.get("alternative") 2273 alternative = f" OR {alternative}" if alternative else "" 2274 ignore = " IGNORE" if expression.args.get("ignore") else "" 2275 is_function = expression.args.get("is_function") 2276 if is_function: 2277 this = f"{this} FUNCTION" 2278 this = f"{this} {self.sql(expression, 'this')}" 2279 2280 exists = " IF EXISTS" if expression.args.get("exists") else "" 2281 where = self.sql(expression, "where") 2282 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2283 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2284 on_conflict = self.sql(expression, "conflict") 2285 on_conflict = f" {on_conflict}" if on_conflict else "" 2286 by_name = " BY NAME" if expression.args.get("by_name") else "" 2287 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2288 returning = self.sql(expression, "returning") 2289 2290 if self.RETURNING_END: 2291 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2292 else: 2293 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2294 2295 partition_by = self.sql(expression, "partition") 2296 partition_by = f" {partition_by}" if partition_by else "" 2297 settings = self.sql(expression, "settings") 2298 settings = f" {settings}" if settings else "" 2299 2300 source = self.sql(expression, "source") 2301 source = f"TABLE {source}" if source else "" 2302 2303 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2304 return self.prepend_ctes(expression, sql) 2305 2306 def introducer_sql(self, expression: exp.Introducer) -> str: 2307 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2308 2309 def kill_sql(self, expression: exp.Kill) -> str: 2310 kind = self.sql(expression, "kind") 2311 kind = f" {kind}" if kind else "" 2312 this = self.sql(expression, "this") 2313 this = f" {this}" if this else "" 2314 return f"KILL{kind}{this}" 2315 2316 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2317 return expression.name 2318 2319 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2320 return expression.name 2321 2322 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2323 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2324 2325 constraint = self.sql(expression, "constraint") 2326 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2327 2328 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2329 if conflict_keys: 2330 conflict_keys = f"({conflict_keys})" 2331 2332 index_predicate = self.sql(expression, "index_predicate") 2333 conflict_keys = f"{conflict_keys}{index_predicate} " 2334 2335 action = self.sql(expression, "action") 2336 2337 expressions = self.expressions(expression, flat=True) 2338 if expressions: 2339 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2340 expressions = f" {set_keyword}{expressions}" 2341 2342 where = self.sql(expression, "where") 2343 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2344 2345 def returning_sql(self, expression: exp.Returning) -> str: 2346 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2347 2348 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2349 fields = self.sql(expression, "fields") 2350 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2351 escaped = self.sql(expression, "escaped") 2352 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2353 items = self.sql(expression, "collection_items") 2354 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2355 keys = self.sql(expression, "map_keys") 2356 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2357 lines = self.sql(expression, "lines") 2358 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2359 null = self.sql(expression, "null") 2360 null = f" NULL DEFINED AS {null}" if null else "" 2361 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2362 2363 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2364 return f"WITH ({self.expressions(expression, flat=True)})" 2365 2366 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2367 this = f"{self.sql(expression, 'this')} INDEX" 2368 target = self.sql(expression, "target") 2369 target = f" FOR {target}" if target else "" 2370 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2371 2372 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2373 this = self.sql(expression, "this") 2374 kind = self.sql(expression, "kind") 2375 expr = self.sql(expression, "expression") 2376 return f"{this} ({kind} => {expr})" 2377 2378 def table_parts(self, expression: exp.Table) -> str: 2379 return ".".join( 2380 self.sql(part) 2381 for part in ( 2382 expression.args.get("catalog"), 2383 expression.args.get("db"), 2384 expression.args.get("this"), 2385 ) 2386 if part is not None 2387 ) 2388 2389 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2390 table = self.table_parts(expression) 2391 only = "ONLY " if expression.args.get("only") else "" 2392 partition = self.sql(expression, "partition") 2393 partition = f" {partition}" if partition else "" 2394 version = self.sql(expression, "version") 2395 version = f" {version}" if version else "" 2396 alias = self.sql(expression, "alias") 2397 alias = f"{sep}{alias}" if alias else "" 2398 2399 sample = self.sql(expression, "sample") 2400 post_alias = "" 2401 pre_alias = "" 2402 2403 if self.dialect.ALIAS_POST_TABLESAMPLE: 2404 pre_alias = sample 2405 else: 2406 post_alias = sample 2407 2408 if self.dialect.ALIAS_POST_VERSION: 2409 pre_alias = f"{pre_alias}{version}" 2410 else: 2411 post_alias = f"{post_alias}{version}" 2412 2413 hints = self.expressions(expression, key="hints", sep=" ") 2414 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2415 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2416 joins = self.indent( 2417 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2418 ) 2419 laterals = self.expressions(expression, key="laterals", sep="") 2420 2421 file_format = self.sql(expression, "format") 2422 pattern = self.sql(expression, "pattern") 2423 if file_format: 2424 pattern = f", PATTERN => {pattern}" if pattern else "" 2425 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2426 elif pattern: 2427 file_format = f" (PATTERN => {pattern})" 2428 2429 ordinality = expression.args.get("ordinality") or "" 2430 if ordinality: 2431 ordinality = f" WITH ORDINALITY{alias}" 2432 alias = "" 2433 2434 when = self.sql(expression, "when") 2435 if when: 2436 table = f"{table} {when}" 2437 2438 changes = self.sql(expression, "changes") 2439 changes = f" {changes}" if changes else "" 2440 2441 rows_from = self.expressions(expression, key="rows_from") 2442 if rows_from: 2443 table = f"ROWS FROM {self.wrap(rows_from)}" 2444 2445 indexed = expression.args.get("indexed") 2446 if indexed is not None: 2447 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2448 else: 2449 indexed = "" 2450 2451 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2452 2453 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2454 table = self.func("TABLE", expression.this) 2455 alias = self.sql(expression, "alias") 2456 alias = f" AS {alias}" if alias else "" 2457 sample = self.sql(expression, "sample") 2458 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2459 joins = self.indent( 2460 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2461 ) 2462 return f"{table}{alias}{pivots}{sample}{joins}" 2463 2464 def tablesample_sql( 2465 self, 2466 expression: exp.TableSample, 2467 tablesample_keyword: str | None = None, 2468 ) -> str: 2469 method = self.sql(expression, "method") 2470 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2471 numerator = self.sql(expression, "bucket_numerator") 2472 denominator = self.sql(expression, "bucket_denominator") 2473 field = self.sql(expression, "bucket_field") 2474 field = f" ON {field}" if field else "" 2475 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2476 seed = self.sql(expression, "seed") 2477 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2478 2479 size = self.sql(expression, "size") 2480 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2481 size = f"{size} ROWS" 2482 2483 percent = self.sql(expression, "percent") 2484 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2485 percent = f"{percent} PERCENT" 2486 2487 expr = f"{bucket}{percent}{size}" 2488 if self.TABLESAMPLE_REQUIRES_PARENS: 2489 expr = f"({expr})" 2490 2491 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2492 2493 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2494 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2495 # the stored column name differs from the target dialect's natural output. 2496 columns = expression.args.get("columns") 2497 if not columns or len(expression.fields) != 1: 2498 return None 2499 2500 args = expression.args 2501 parser_cls = self.dialect.parser_class 2502 2503 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2504 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2505 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2506 2507 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2508 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2509 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2510 2511 if ( 2512 src_identify_pivot_strings == tgt_identify_pivot_strings 2513 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2514 and src_pivot_column_naming == tgt_pivot_column_naming 2515 ): 2516 return None 2517 2518 in_exprs = expression.fields[0].expressions 2519 step = len(columns) // len(in_exprs) 2520 2521 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2522 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2523 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2524 first_stored = columns[0].name 2525 2526 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2527 if not first_stored.startswith(first_base): 2528 return None 2529 2530 suffix = first_stored[len(first_base) :] 2531 2532 # Whether the target dialect would append an agg-name suffix for this pivot. 2533 # Spark single-agg uniquely drops the agg alias entirely. 2534 target_has_suffix = ( 2535 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2536 ) and any(a.alias for a in expression.expressions) 2537 source_has_suffix = suffix != "" 2538 2539 new_exprs: list[exp.Expression] = [] 2540 modified = False 2541 for val_idx, e in enumerate(in_exprs): 2542 if isinstance(e, exp.PivotAlias): 2543 new_exprs.append(e) 2544 continue 2545 2546 i = val_idx * step 2547 stored_full = columns[i].name 2548 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2549 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2550 2551 # Source had a suffix, but target won't apply one 2552 if source_has_suffix and not target_has_suffix: 2553 new_exprs.append( 2554 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2555 ) 2556 modified = True 2557 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2558 elif stored_value != target_value: 2559 new_exprs.append( 2560 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2561 ) 2562 modified = True 2563 else: 2564 new_exprs.append(e) 2565 2566 return new_exprs if modified else None 2567 2568 def pivot_sql(self, expression: exp.Pivot) -> str: 2569 expressions = self.expressions(expression, flat=True) 2570 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2571 2572 group = self.sql(expression, "group") 2573 2574 if expression.this: 2575 this = self.sql(expression, "this") 2576 if not expressions: 2577 sql = f"UNPIVOT {this}" 2578 else: 2579 on = f"{self.seg('ON')} {expressions}" 2580 into = self.sql(expression, "into") 2581 into = f"{self.seg('INTO')} {into}" if into else "" 2582 using = self.expressions(expression, key="using", flat=True) 2583 using = f"{self.seg('USING')} {using}" if using else "" 2584 sql = f"{direction} {this}{on}{into}{using}{group}" 2585 return self.prepend_ctes(expression, sql) 2586 2587 if not expression.unpivot: 2588 # Wrap IN-list values with explicit aliases where the target dialect would differ 2589 new_field_exprs = self._pivot_in_value_aliases(expression) 2590 if new_field_exprs is not None: 2591 expression.fields[0].set("expressions", new_field_exprs) 2592 2593 alias = self.sql(expression, "alias") 2594 alias = f" AS {alias}" if alias else "" 2595 2596 fields = self.expressions( 2597 expression, 2598 "fields", 2599 sep=" ", 2600 dynamic=True, 2601 new_line=True, 2602 skip_first=True, 2603 skip_last=True, 2604 ) 2605 2606 include_nulls = expression.args.get("include_nulls") 2607 if include_nulls is not None: 2608 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2609 else: 2610 nulls = "" 2611 2612 default_on_null = self.sql(expression, "default_on_null") 2613 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2614 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2615 return self.prepend_ctes(expression, sql) 2616 2617 def version_sql(self, expression: exp.Version) -> str: 2618 this = f"FOR {expression.name}" 2619 kind = expression.text("kind") 2620 expr = self.sql(expression, "expression") 2621 return f"{this} {kind} {expr}" 2622 2623 def tuple_sql(self, expression: exp.Tuple) -> str: 2624 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2625 2626 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2627 """ 2628 Returns (join_sql, from_sql) for UPDATE statements. 2629 - join_sql: placed after UPDATE table, before SET 2630 - from_sql: placed after SET clause (standard position) 2631 Dialects like MySQL need to convert FROM to JOIN syntax. 2632 """ 2633 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2634 return ("", self.sql(expression, "from_")) 2635 2636 # Qualify unqualified columns in SET clause with the target table 2637 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2638 target_table = expression.this 2639 if isinstance(target_table, exp.Table): 2640 target_name = exp.to_identifier(target_table.alias_or_name) 2641 for eq in expression.expressions: 2642 col = eq.this 2643 if isinstance(col, exp.Column) and not col.table: 2644 col.set("table", target_name) 2645 2646 table = from_expr.this 2647 if nested_joins := table.args.get("joins", []): 2648 table.set("joins", None) 2649 2650 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2651 for nested in nested_joins: 2652 if not nested.args.get("on") and not nested.args.get("using"): 2653 nested.set("on", exp.true()) 2654 join_sql += self.sql(nested) 2655 2656 return (join_sql, "") 2657 2658 def update_sql(self, expression: exp.Update) -> str: 2659 hint = self.sql(expression, "hint") 2660 this = self.sql(expression, "this") 2661 join_sql, from_sql = self._update_from_joins_sql(expression) 2662 set_sql = self.expressions(expression, flat=True) 2663 where_sql = self.sql(expression, "where") 2664 returning = self.sql(expression, "returning") 2665 order = self.sql(expression, "order") 2666 limit = self.sql(expression, "limit") 2667 if self.RETURNING_END: 2668 expression_sql = f"{from_sql}{where_sql}{returning}" 2669 else: 2670 expression_sql = f"{returning}{from_sql}{where_sql}" 2671 options = self.expressions(expression, key="options") 2672 options = f" OPTION({options})" if options else "" 2673 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2674 return self.prepend_ctes(expression, sql) 2675 2676 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2677 values_as_table = values_as_table and self.VALUES_AS_TABLE 2678 2679 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2680 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2681 args = self.expressions(expression) 2682 alias = self.sql(expression, "alias") 2683 values = f"VALUES{self.seg('')}{args}" 2684 values = ( 2685 f"({values})" 2686 if self.WRAP_DERIVED_VALUES 2687 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2688 else values 2689 ) 2690 values = self.query_modifiers(expression, values) 2691 return f"{values} AS {alias}" if alias else values 2692 2693 # Converts `VALUES...` expression into a series of select unions. 2694 alias_node = expression.args.get("alias") 2695 column_names = alias_node and alias_node.columns 2696 2697 selects: list[exp.Query] = [] 2698 2699 for i, tup in enumerate(expression.expressions): 2700 row = tup.expressions 2701 2702 if i == 0 and column_names: 2703 row = [ 2704 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2705 ] 2706 2707 selects.append(exp.Select(expressions=row)) 2708 2709 if self.pretty: 2710 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2711 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2712 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2713 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2714 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2715 2716 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2717 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2718 return f"({unions}){alias}" 2719 2720 def var_sql(self, expression: exp.Var) -> str: 2721 return self.sql(expression, "this") 2722 2723 @unsupported_args("expressions") 2724 def into_sql(self, expression: exp.Into) -> str: 2725 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2726 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2727 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2728 2729 def from_sql(self, expression: exp.From) -> str: 2730 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2731 2732 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2733 grouping_sets = self.expressions(expression, indent=False) 2734 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2735 2736 def rollup_sql(self, expression: exp.Rollup) -> str: 2737 expressions = self.expressions(expression, indent=False) 2738 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2739 2740 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2741 this = self.sql(expression, "this") 2742 2743 columns = self.expressions(expression, flat=True) 2744 2745 from_sql = self.sql(expression, "from_index") 2746 from_sql = f" FROM {from_sql}" if from_sql else "" 2747 2748 properties = expression.args.get("properties") 2749 properties_sql = ( 2750 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2751 ) 2752 2753 return f"{this}({columns}){from_sql}{properties_sql}" 2754 2755 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2756 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2757 2758 def cube_sql(self, expression: exp.Cube) -> str: 2759 expressions = self.expressions(expression, indent=False) 2760 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2761 2762 def group_sql(self, expression: exp.Group) -> str: 2763 group_by_all = expression.args.get("all") 2764 if group_by_all is True: 2765 modifier = " ALL" 2766 elif group_by_all is False: 2767 modifier = " DISTINCT" 2768 else: 2769 modifier = "" 2770 2771 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2772 2773 grouping_sets = self.expressions(expression, key="grouping_sets") 2774 cube = self.expressions(expression, key="cube") 2775 rollup = self.expressions(expression, key="rollup") 2776 2777 groupings = csv( 2778 self.seg(grouping_sets) if grouping_sets else "", 2779 self.seg(cube) if cube else "", 2780 self.seg(rollup) if rollup else "", 2781 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2782 sep=self.GROUPINGS_SEP, 2783 ) 2784 2785 if ( 2786 expression.expressions 2787 and groupings 2788 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2789 ): 2790 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2791 2792 return f"{group_by}{groupings}" 2793 2794 def having_sql(self, expression: exp.Having) -> str: 2795 this = self.indent(self.sql(expression, "this")) 2796 return f"{self.seg('HAVING')}{self.sep()}{this}" 2797 2798 def connect_sql(self, expression: exp.Connect) -> str: 2799 start = self.sql(expression, "start") 2800 start = self.seg(f"START WITH {start}") if start else "" 2801 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2802 connect = self.sql(expression, "connect") 2803 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2804 return start + connect 2805 2806 def prior_sql(self, expression: exp.Prior) -> str: 2807 return f"PRIOR {self.sql(expression, 'this')}" 2808 2809 def join_sql(self, expression: exp.Join) -> str: 2810 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2811 side = None 2812 else: 2813 side = expression.side 2814 2815 op_sql = " ".join( 2816 op 2817 for op in ( 2818 expression.method, 2819 "GLOBAL" if expression.args.get("global_") else None, 2820 side, 2821 expression.kind, 2822 expression.hint if self.JOIN_HINTS else None, 2823 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2824 ) 2825 if op 2826 ) 2827 match_cond = self.sql(expression, "match_condition") 2828 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2829 on_sql = self.sql(expression, "on") 2830 using = expression.args.get("using") 2831 2832 if not on_sql and using: 2833 on_sql = csv(*(self.sql(column) for column in using)) 2834 2835 this = expression.this 2836 this_sql = self.sql(this) 2837 2838 exprs = self.expressions(expression) 2839 if exprs: 2840 this_sql = f"{this_sql},{self.seg(exprs)}" 2841 2842 if on_sql: 2843 on_sql = self.indent(on_sql, skip_first=True) 2844 space = self.seg(" " * self.pad) if self.pretty else " " 2845 if using: 2846 on_sql = f"{space}USING ({on_sql})" 2847 else: 2848 on_sql = f"{space}ON {on_sql}" 2849 elif not op_sql: 2850 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2851 return f" {this_sql}" 2852 2853 return f", {this_sql}" 2854 2855 if op_sql != "STRAIGHT_JOIN": 2856 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2857 2858 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2859 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2860 2861 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2862 args = self.expressions(expression, flat=True) 2863 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2864 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2865 2866 def lateral_op(self, expression: exp.Lateral) -> str: 2867 cross_apply = expression.args.get("cross_apply") 2868 2869 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2870 if cross_apply is True: 2871 op = "INNER JOIN " 2872 elif cross_apply is False: 2873 op = "LEFT JOIN " 2874 else: 2875 op = "" 2876 2877 return f"{op}LATERAL" 2878 2879 def lateral_sql(self, expression: exp.Lateral) -> str: 2880 this = self.sql(expression, "this") 2881 2882 if expression.args.get("view"): 2883 alias = expression.args["alias"] 2884 columns = self.expressions(alias, key="columns", flat=True) 2885 table = f" {alias.name}" if alias.name else "" 2886 columns = f" AS {columns}" if columns else "" 2887 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2888 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2889 2890 alias = self.sql(expression, "alias") 2891 alias = f" AS {alias}" if alias else "" 2892 2893 ordinality = expression.args.get("ordinality") or "" 2894 if ordinality: 2895 ordinality = f" WITH ORDINALITY{alias}" 2896 alias = "" 2897 2898 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2899 2900 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2901 this = self.sql(expression, "this") 2902 2903 args = [ 2904 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2905 for e in (expression.args.get(k) for k in ("offset", "expression")) 2906 if e 2907 ] 2908 2909 args_sql = ", ".join(self.sql(e) for e in args) 2910 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2911 expressions = self.expressions(expression, flat=True) 2912 limit_options = self.sql(expression, "limit_options") 2913 expressions = f" BY {expressions}" if expressions else "" 2914 2915 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2916 2917 def offset_sql(self, expression: exp.Offset) -> str: 2918 this = self.sql(expression, "this") 2919 value = expression.expression 2920 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2921 expressions = self.expressions(expression, flat=True) 2922 expressions = f" BY {expressions}" if expressions else "" 2923 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2924 2925 def setitem_sql(self, expression: exp.SetItem) -> str: 2926 kind = self.sql(expression, "kind") 2927 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2928 kind = "" 2929 else: 2930 kind = f"{kind} " if kind else "" 2931 this = self.sql(expression, "this") 2932 expressions = self.expressions(expression) 2933 collate = self.sql(expression, "collate") 2934 collate = f" COLLATE {collate}" if collate else "" 2935 global_ = "GLOBAL " if expression.args.get("global_") else "" 2936 return f"{global_}{kind}{this}{expressions}{collate}" 2937 2938 def set_sql(self, expression: exp.Set) -> str: 2939 expressions = f" {self.expressions(expression, flat=True)}" 2940 tag = " TAG" if expression.args.get("tag") else "" 2941 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2942 2943 def queryband_sql(self, expression: exp.QueryBand) -> str: 2944 this = self.sql(expression, "this") 2945 update = " UPDATE" if expression.args.get("update") else "" 2946 scope = self.sql(expression, "scope") 2947 scope = f" FOR {scope}" if scope else "" 2948 2949 return f"QUERY_BAND = {this}{update}{scope}" 2950 2951 def pragma_sql(self, expression: exp.Pragma) -> str: 2952 return f"PRAGMA {self.sql(expression, 'this')}" 2953 2954 def lock_sql(self, expression: exp.Lock) -> str: 2955 if not self.LOCKING_READS_SUPPORTED: 2956 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2957 return "" 2958 2959 update = expression.args["update"] 2960 key = expression.args.get("key") 2961 if update: 2962 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2963 else: 2964 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2965 expressions = self.expressions(expression, flat=True) 2966 expressions = f" OF {expressions}" if expressions else "" 2967 wait = expression.args.get("wait") 2968 2969 if wait is not None: 2970 if isinstance(wait, exp.Literal): 2971 wait = f" WAIT {self.sql(wait)}" 2972 else: 2973 wait = " NOWAIT" if wait else " SKIP LOCKED" 2974 2975 return f"{lock_type}{expressions}{wait or ''}" 2976 2977 def literal_sql(self, expression: exp.Literal) -> str: 2978 text = expression.this or "" 2979 if expression.is_string: 2980 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2981 return text 2982 2983 def escape_str( 2984 self, 2985 text: str, 2986 escape_backslash: bool = True, 2987 delimiter: str | None = None, 2988 escaped_delimiter: str | None = None, 2989 is_byte_string: bool = False, 2990 ) -> str: 2991 if is_byte_string: 2992 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2993 else: 2994 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2995 2996 if supports_escape_sequences: 2997 text = "".join( 2998 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2999 for ch in text 3000 ) 3001 3002 delimiter = delimiter or self.dialect.QUOTE_END 3003 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3004 3005 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3006 3007 def loaddata_sql(self, expression: exp.LoadData) -> str: 3008 is_overwrite = expression.args.get("overwrite") 3009 overwrite = " OVERWRITE" if is_overwrite else "" 3010 this = self.sql(expression, "this") 3011 3012 files = expression.args.get("files") 3013 if files: 3014 files_sql = self.expressions(files, flat=True) 3015 files_sql = f"FILES{self.wrap(files_sql)}" 3016 if is_overwrite: 3017 this = f" {this}" 3018 elif expression.args.get("temp"): 3019 this = f" INTO TEMP TABLE {this}" 3020 else: 3021 this = f" INTO TABLE {this}" 3022 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3023 3024 local = " LOCAL" if expression.args.get("local") else "" 3025 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3026 this = f" INTO TABLE {this}" 3027 partition = self.sql(expression, "partition") 3028 partition = f" {partition}" if partition else "" 3029 input_format = self.sql(expression, "input_format") 3030 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3031 serde = self.sql(expression, "serde") 3032 serde = f" SERDE {serde}" if serde else "" 3033 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3034 3035 def null_sql(self, *_) -> str: 3036 return "NULL" 3037 3038 def boolean_sql(self, expression: exp.Boolean) -> str: 3039 return "TRUE" if expression.this else "FALSE" 3040 3041 def booland_sql(self, expression: exp.Booland) -> str: 3042 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3043 3044 def boolor_sql(self, expression: exp.Boolor) -> str: 3045 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3046 3047 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3048 this = self.sql(expression, "this") 3049 this = f"{this} " if this else this 3050 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3051 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3052 3053 def withfill_sql(self, expression: exp.WithFill) -> str: 3054 from_sql = self.sql(expression, "from_") 3055 from_sql = f" FROM {from_sql}" if from_sql else "" 3056 to_sql = self.sql(expression, "to") 3057 to_sql = f" TO {to_sql}" if to_sql else "" 3058 step_sql = self.sql(expression, "step") 3059 step_sql = f" STEP {step_sql}" if step_sql else "" 3060 interpolated_values = [ 3061 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3062 if isinstance(e, exp.Alias) 3063 else self.sql(e, "this") 3064 for e in expression.args.get("interpolate") or [] 3065 ] 3066 interpolate = ( 3067 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3068 ) 3069 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3070 3071 def cluster_sql(self, expression: exp.Cluster) -> str: 3072 return self.op_expressions("CLUSTER BY", expression) 3073 3074 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3075 if expression.this: 3076 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3077 return "" 3078 expressions = self.expressions(expression, flat=True) 3079 return f"CLUSTER BY ({expressions})" 3080 3081 def distribute_sql(self, expression: exp.Distribute) -> str: 3082 return self.op_expressions("DISTRIBUTE BY", expression) 3083 3084 def sort_sql(self, expression: exp.Sort) -> str: 3085 return self.op_expressions("SORT BY", expression) 3086 3087 def _resolve_ordered_for_null_ordering_simulation( 3088 self, expression: exp.Ordered 3089 ) -> exp.Expr | None: 3090 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3091 3092 Returns the underlying expression of the uniquely-matching projection 3093 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3094 simulation, since the CASE is evaluated in FROM-clause scope rather 3095 than alias scope (MySQL error 1052). Returns None if no safe 3096 substitution applies, leaving the original behaviour unchanged. 3097 """ 3098 this = expression.this 3099 if not (isinstance(this, exp.Column) and not this.table): 3100 return None 3101 3102 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3103 if not isinstance(ancestor, exp.Select): 3104 return None 3105 3106 column_name = this.name 3107 matched: list[exp.Expr] = [ 3108 p.this if isinstance(p, exp.Alias) else p 3109 for p in ancestor.selects 3110 if p.output_name == column_name 3111 ] 3112 match = matched[0] if len(matched) == 1 else None 3113 3114 # Skip the substitution when it would be identical to the existing 3115 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3116 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3117 return None 3118 3119 return match 3120 3121 def ordered_sql(self, expression: exp.Ordered) -> str: 3122 desc = expression.args.get("desc") 3123 asc = not desc 3124 3125 nulls_first = expression.args.get("nulls_first") 3126 nulls_last = not nulls_first 3127 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3128 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3129 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3130 3131 this = self.sql(expression, "this") 3132 3133 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3134 nulls_sort_change = "" 3135 if nulls_first and ( 3136 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3137 ): 3138 nulls_sort_change = " NULLS FIRST" 3139 elif ( 3140 nulls_last 3141 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3142 and not nulls_are_last 3143 ): 3144 nulls_sort_change = " NULLS LAST" 3145 3146 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3147 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3148 window = expression.find_ancestor(exp.Window, exp.Select) 3149 3150 if isinstance(window, exp.Window): 3151 window_this = window.this 3152 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3153 window_this = window_this.this 3154 spec = window.args.get("spec") 3155 else: 3156 window_this = None 3157 spec = None 3158 3159 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3160 # without a spec or with a ROWS spec, but not with RANGE 3161 if not ( 3162 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3163 and (not spec or spec.text("kind").upper() == "ROWS") 3164 ): 3165 if window_this and spec: 3166 self.unsupported( 3167 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3168 ) 3169 nulls_sort_change = "" 3170 elif self.NULL_ORDERING_SUPPORTED is False and ( 3171 (asc and nulls_sort_change == " NULLS LAST") 3172 or (desc and nulls_sort_change == " NULLS FIRST") 3173 ): 3174 # BigQuery does not allow these ordering/nulls combinations when used under 3175 # an aggregation func or under a window containing one 3176 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3177 3178 if isinstance(ancestor, exp.Window): 3179 ancestor = ancestor.this 3180 if isinstance(ancestor, exp.AggFunc): 3181 self.unsupported( 3182 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3183 ) 3184 nulls_sort_change = "" 3185 elif self.NULL_ORDERING_SUPPORTED is None: 3186 if expression.this.is_int: 3187 self.unsupported( 3188 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3189 ) 3190 elif not isinstance(expression.this, exp.Rand): 3191 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3192 target = self.sql(resolved) if resolved is not None else this 3193 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3194 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3195 nulls_sort_change = "" 3196 3197 with_fill = self.sql(expression, "with_fill") 3198 with_fill = f" {with_fill}" if with_fill else "" 3199 3200 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3201 3202 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3203 window_frame = self.sql(expression, "window_frame") 3204 window_frame = f"{window_frame} " if window_frame else "" 3205 3206 this = self.sql(expression, "this") 3207 3208 return f"{window_frame}{this}" 3209 3210 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3211 partition = self.partition_by_sql(expression) 3212 order = self.sql(expression, "order") 3213 measures = self.expressions(expression, key="measures") 3214 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3215 rows = self.sql(expression, "rows") 3216 rows = self.seg(rows) if rows else "" 3217 after = self.sql(expression, "after") 3218 after = self.seg(after) if after else "" 3219 pattern = self.sql(expression, "pattern") 3220 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3221 definition_sqls = [ 3222 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3223 for definition in expression.args.get("define", []) 3224 ] 3225 definitions = self.expressions(sqls=definition_sqls) 3226 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3227 body = "".join( 3228 ( 3229 partition, 3230 order, 3231 measures, 3232 rows, 3233 after, 3234 pattern, 3235 define, 3236 ) 3237 ) 3238 alias = self.sql(expression, "alias") 3239 alias = f" {alias}" if alias else "" 3240 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3241 3242 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3243 limit = expression.args.get("limit") 3244 3245 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3246 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3247 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3248 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3249 3250 return csv( 3251 *sqls, 3252 *[self.sql(join) for join in expression.args.get("joins") or []], 3253 self.sql(expression, "match"), 3254 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3255 self.sql(expression, "prewhere"), 3256 self.sql(expression, "where"), 3257 self.sql(expression, "connect"), 3258 self.sql(expression, "group"), 3259 self.sql(expression, "having"), 3260 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3261 self.sql(expression, "order"), 3262 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3263 *self.after_limit_modifiers(expression), 3264 self.options_modifier(expression), 3265 self.sql(expression, "for_"), 3266 sep="", 3267 ) 3268 3269 def options_modifier(self, expression: exp.Expr) -> str: 3270 options = self.expressions(expression, key="options") 3271 return f" {options}" if options else "" 3272 3273 def forclause_sql(self, expression: exp.ForClause) -> str: 3274 kind = expression.args["kind"] 3275 if kind == "BROWSE": 3276 return f"{self.sep()}FOR BROWSE" 3277 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3278 # the target dialect doesn't support QueryOption, so we drop the clause. 3279 options = self.expressions(expression, key="expressions") 3280 if not options: 3281 return "" 3282 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3283 3284 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3285 self.unsupported("Unsupported query option.") 3286 return "" 3287 3288 def offset_limit_modifiers( 3289 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3290 ) -> list[str]: 3291 return [ 3292 self.sql(expression, "offset") if fetch else self.sql(limit), 3293 self.sql(limit) if fetch else self.sql(expression, "offset"), 3294 ] 3295 3296 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3297 locks = self.expressions(expression, key="locks", sep=" ") 3298 locks = f" {locks}" if locks else "" 3299 return [locks, self.sql(expression, "sample")] 3300 3301 def select_sql(self, expression: exp.Select) -> str: 3302 into = expression.args.get("into") 3303 if not self.SUPPORTS_SELECT_INTO and into: 3304 into.pop() 3305 3306 hint = self.sql(expression, "hint") 3307 distinct = self.sql(expression, "distinct") 3308 distinct = f" {distinct}" if distinct else "" 3309 kind = self.sql(expression, "kind") 3310 3311 limit = expression.args.get("limit") 3312 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3313 top = self.limit_sql(limit, top=True) 3314 limit.pop() 3315 else: 3316 top = "" 3317 3318 expressions = self.expressions(expression) 3319 3320 if kind: 3321 if kind in self.SELECT_KINDS: 3322 kind = f" AS {kind}" 3323 else: 3324 if kind == "STRUCT": 3325 expressions = self.expressions( 3326 sqls=[ 3327 self.sql( 3328 exp.Struct( 3329 expressions=[ 3330 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3331 if isinstance(e, exp.Alias) 3332 else e 3333 for e in expression.expressions 3334 ] 3335 ) 3336 ) 3337 ] 3338 ) 3339 kind = "" 3340 3341 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3342 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3343 3344 exclude = expression.args.get("exclude") 3345 3346 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3347 exclude_sql = self.expressions(sqls=exclude, flat=True) 3348 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3349 3350 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3351 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3352 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3353 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3354 sql = self.query_modifiers( 3355 expression, 3356 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3357 self.sql(expression, "into", comment=False), 3358 self.sql(expression, "from_", comment=False), 3359 ) 3360 3361 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3362 if expression.args.get("with_"): 3363 sql = self.maybe_comment(sql, expression) 3364 expression.pop_comments() 3365 3366 sql = self.prepend_ctes(expression, sql) 3367 3368 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3369 expression.set("exclude", None) 3370 subquery = expression.subquery(copy=False) 3371 star = exp.Star(except_=exclude) 3372 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3373 3374 if not self.SUPPORTS_SELECT_INTO and into: 3375 if into.args.get("temporary"): 3376 table_kind = " TEMPORARY" 3377 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3378 table_kind = " UNLOGGED" 3379 else: 3380 table_kind = "" 3381 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3382 3383 return sql 3384 3385 def schema_sql(self, expression: exp.Schema) -> str: 3386 this = self.sql(expression, "this") 3387 sql = self.schema_columns_sql(expression) 3388 return f"{this} {sql}" if this and sql else this or sql 3389 3390 def schema_columns_sql(self, expression: exp.Expr) -> str: 3391 if expression.expressions: 3392 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3393 return "" 3394 3395 def star_sql(self, expression: exp.Star) -> str: 3396 except_ = self.expressions(expression, key="except_", flat=True) 3397 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3398 replace = self.expressions(expression, key="replace", flat=True) 3399 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3400 rename = self.expressions(expression, key="rename", flat=True) 3401 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3402 ilike = self.sql(expression, "ilike") 3403 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3404 return f"*{ilike}{except_}{replace}{rename}" 3405 3406 def parameter_sql(self, expression: exp.Parameter) -> str: 3407 this = self.sql(expression, "this") 3408 return f"{self.PARAMETER_TOKEN}{this}" 3409 3410 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3411 this = self.sql(expression, "this") 3412 kind = expression.text("kind") 3413 if kind: 3414 kind = f"{kind}." 3415 return f"@@{kind}{this}" 3416 3417 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3418 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3419 3420 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3421 alias = self.sql(expression, "alias") 3422 alias = f"{sep}{alias}" if alias else "" 3423 sample = self.sql(expression, "sample") 3424 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3425 alias = f"{sample}{alias}" 3426 3427 # Set to None so it's not generated again by self.query_modifiers() 3428 expression.set("sample", None) 3429 3430 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3431 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3432 return self.prepend_ctes(expression, sql) 3433 3434 def qualify_sql(self, expression: exp.Qualify) -> str: 3435 this = self.indent(self.sql(expression, "this")) 3436 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3437 3438 def unnest_sql(self, expression: exp.Unnest) -> str: 3439 args = self.expressions(expression, flat=True) 3440 3441 alias = expression.args.get("alias") 3442 offset = expression.args.get("offset") 3443 3444 if self.UNNEST_WITH_ORDINALITY: 3445 if alias and isinstance(offset, exp.Expr): 3446 alias.append("columns", offset) 3447 expression.set("offset", None) 3448 3449 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3450 columns = alias.columns 3451 alias = self.sql(columns[0]) if columns else "" 3452 else: 3453 alias = self.sql(alias) 3454 3455 alias = f" AS {alias}" if alias else alias 3456 if self.UNNEST_WITH_ORDINALITY: 3457 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3458 else: 3459 if isinstance(offset, exp.Expr): 3460 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3461 elif offset: 3462 suffix = f"{alias} WITH OFFSET" 3463 else: 3464 suffix = alias 3465 3466 return f"UNNEST({args}){suffix}" 3467 3468 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3469 return "" 3470 3471 def where_sql(self, expression: exp.Where) -> str: 3472 this = self.indent(self.sql(expression, "this")) 3473 return f"{self.seg('WHERE')}{self.sep()}{this}" 3474 3475 def window_sql(self, expression: exp.Window) -> str: 3476 this = self.sql(expression, "this") 3477 partition = self.partition_by_sql(expression) 3478 order = expression.args.get("order") 3479 order = self.order_sql(order, flat=True) if order else "" 3480 spec = self.sql(expression, "spec") 3481 alias = self.sql(expression, "alias") 3482 over = self.sql(expression, "over") or "OVER" 3483 3484 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3485 3486 first = expression.args.get("first") 3487 if first is None: 3488 first = "" 3489 else: 3490 first = "FIRST" if first else "LAST" 3491 3492 if not partition and not order and not spec and alias: 3493 return f"{this} {alias}" 3494 3495 args = self.format_args( 3496 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3497 ) 3498 return f"{this} ({args})" 3499 3500 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3501 partition = self.expressions(expression, key="partition_by", flat=True) 3502 return f"PARTITION BY {partition}" if partition else "" 3503 3504 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3505 kind = self.sql(expression, "kind") 3506 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3507 end = ( 3508 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3509 or "CURRENT ROW" 3510 ) 3511 3512 window_spec = f"{kind} BETWEEN {start} AND {end}" 3513 3514 exclude = self.sql(expression, "exclude") 3515 if exclude: 3516 if self.SUPPORTS_WINDOW_EXCLUDE: 3517 window_spec += f" EXCLUDE {exclude}" 3518 else: 3519 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3520 3521 return window_spec 3522 3523 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3524 this = self.sql(expression, "this") 3525 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3526 return f"{this} WITHIN GROUP ({expression_sql})" 3527 3528 def between_sql(self, expression: exp.Between) -> str: 3529 this = self.sql(expression, "this") 3530 low = self.sql(expression, "low") 3531 high = self.sql(expression, "high") 3532 symmetric = expression.args.get("symmetric") 3533 3534 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3535 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3536 3537 flag = ( 3538 " SYMMETRIC" 3539 if symmetric 3540 else " ASYMMETRIC" 3541 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3542 else "" # silently drop ASYMMETRIC – semantics identical 3543 ) 3544 return f"{this} BETWEEN{flag} {low} AND {high}" 3545 3546 def bracket_offset_expressions( 3547 self, expression: exp.Bracket, index_offset: int | None = None 3548 ) -> list[exp.Expr]: 3549 if expression.args.get("json_access"): 3550 return expression.expressions 3551 3552 return apply_index_offset( 3553 expression.this, 3554 expression.expressions, 3555 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3556 dialect=self.dialect, 3557 ) 3558 3559 def bracket_sql(self, expression: exp.Bracket) -> str: 3560 expressions = self.bracket_offset_expressions(expression) 3561 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3562 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3563 3564 def all_sql(self, expression: exp.All) -> str: 3565 this = self.sql(expression, "this") 3566 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3567 this = self.wrap(this) 3568 return f"ALL {this}" 3569 3570 def any_sql(self, expression: exp.Any) -> str: 3571 this = self.sql(expression, "this") 3572 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3573 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3574 this = self.wrap(this) 3575 return f"ANY{this}" 3576 return f"ANY {this}" 3577 3578 def exists_sql(self, expression: exp.Exists) -> str: 3579 return f"EXISTS{self.wrap(expression)}" 3580 3581 def case_sql(self, expression: exp.Case) -> str: 3582 this = self.sql(expression, "this") 3583 statements = [f"CASE {this}" if this else "CASE"] 3584 3585 for e in expression.args["ifs"]: 3586 statements.append(f"WHEN {self.sql(e, 'this')}") 3587 statements.append(f"THEN {self.sql(e, 'true')}") 3588 3589 default = self.sql(expression, "default") 3590 3591 if default: 3592 statements.append(f"ELSE {default}") 3593 3594 statements.append("END") 3595 3596 if self.pretty and self.too_wide(statements): 3597 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3598 3599 return " ".join(statements) 3600 3601 def constraint_sql(self, expression: exp.Constraint) -> str: 3602 this = self.sql(expression, "this") 3603 expressions = self.expressions(expression, flat=True) 3604 return f"CONSTRAINT {this} {expressions}" 3605 3606 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3607 order = expression.args.get("order") 3608 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3609 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3610 3611 def extract_sql(self, expression: exp.Extract) -> str: 3612 import sqlglot.dialects.dialect 3613 3614 this = ( 3615 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3616 if self.NORMALIZE_EXTRACT_DATE_PARTS 3617 else expression.this 3618 ) 3619 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3620 expression_sql = self.sql(expression, "expression") 3621 3622 return f"EXTRACT({this_sql} FROM {expression_sql})" 3623 3624 def trim_sql(self, expression: exp.Trim) -> str: 3625 trim_type = self.sql(expression, "position") 3626 3627 if trim_type == "LEADING": 3628 func_name = "LTRIM" 3629 elif trim_type == "TRAILING": 3630 func_name = "RTRIM" 3631 else: 3632 func_name = "TRIM" 3633 3634 return self.func(func_name, expression.this, expression.expression) 3635 3636 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3637 args = expression.expressions 3638 if isinstance(expression, exp.ConcatWs): 3639 args = args[1:] # Skip the delimiter 3640 3641 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3642 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3643 3644 concat_coalesce = ( 3645 self.dialect.CONCAT_WS_COALESCE 3646 if isinstance(expression, exp.ConcatWs) 3647 else self.dialect.CONCAT_COALESCE 3648 ) 3649 3650 if not concat_coalesce and expression.args.get("coalesce"): 3651 3652 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3653 if not e.type: 3654 import sqlglot.optimizer.annotate_types 3655 3656 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3657 3658 if e.is_string or e.is_type(exp.DType.ARRAY): 3659 return e 3660 3661 return exp.func("coalesce", e, exp.Literal.string("")) 3662 3663 args = [_wrap_with_coalesce(e) for e in args] 3664 3665 return args 3666 3667 def concat_sql(self, expression: exp.Concat) -> str: 3668 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3669 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3670 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3671 # instead of coalescing them to empty string. 3672 import sqlglot.dialects.dialect 3673 3674 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3675 3676 expressions = self.convert_concat_args(expression) 3677 3678 # Some dialects don't allow a single-argument CONCAT call 3679 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3680 return self.sql(expressions[0]) 3681 3682 return self.func("CONCAT", *expressions) 3683 3684 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3685 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3686 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3687 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3688 all_args = expression.expressions 3689 expression.set("coalesce", True) 3690 return self.sql( 3691 exp.case() 3692 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3693 .else_(expression) 3694 ) 3695 3696 return self.func( 3697 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3698 ) 3699 3700 def check_sql(self, expression: exp.Check) -> str: 3701 this = self.sql(expression, key="this") 3702 return f"CHECK ({this})" 3703 3704 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3705 expressions = self.expressions(expression, flat=True) 3706 expressions = f" ({expressions})" if expressions else "" 3707 reference = self.sql(expression, "reference") 3708 reference = f" {reference}" if reference else "" 3709 delete = self.sql(expression, "delete") 3710 delete = f" ON DELETE {delete}" if delete else "" 3711 update = self.sql(expression, "update") 3712 update = f" ON UPDATE {update}" if update else "" 3713 options = self.expressions(expression, key="options", flat=True, sep=" ") 3714 options = f" {options}" if options else "" 3715 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3716 3717 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3718 this = self.sql(expression, "this") 3719 this = f" {this}" if this else "" 3720 expressions = self.expressions(expression, flat=True) 3721 include = self.sql(expression, "include") 3722 options = self.expressions(expression, key="options", flat=True, sep=" ") 3723 options = f" {options}" if options else "" 3724 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3725 3726 def if_sql(self, expression: exp.If) -> str: 3727 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3728 3729 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3730 if self.MATCH_AGAINST_TABLE_PREFIX: 3731 expressions = [] 3732 for expr in expression.expressions: 3733 if isinstance(expr, exp.Table): 3734 expressions.append(f"TABLE {self.sql(expr)}") 3735 else: 3736 expressions.append(expr) 3737 else: 3738 expressions = expression.expressions 3739 3740 modifier = expression.args.get("modifier") 3741 modifier = f" {modifier}" if modifier else "" 3742 return ( 3743 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3744 ) 3745 3746 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3747 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3748 3749 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3750 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3751 3752 if self.QUOTE_JSON_PATH: 3753 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3754 3755 return path 3756 3757 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3758 if isinstance(expression, exp.JSONPathPart): 3759 transform = self.TRANSFORMS.get(expression.__class__) 3760 if not callable(transform): 3761 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3762 return "" 3763 3764 return transform(self, expression) 3765 3766 if isinstance(expression, int): 3767 return str(expression) 3768 3769 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3770 escaped = expression.replace("'", "\\'") 3771 escaped = f"\\'{expression}\\'" 3772 else: 3773 escaped = expression.replace('"', '\\"') 3774 escaped = f'"{escaped}"' 3775 3776 return escaped 3777 3778 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3779 return f"{self.sql(expression, 'this')} FORMAT JSON" 3780 3781 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3782 # Output the Teradata column FORMAT override. 3783 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3784 this = self.sql(expression, "this") 3785 fmt = self.sql(expression, "format") 3786 return f"{this} (FORMAT {fmt})" 3787 3788 def _jsonobject_sql( 3789 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3790 ) -> str: 3791 null_handling = expression.args.get("null_handling") 3792 null_handling = f" {null_handling}" if null_handling else "" 3793 3794 unique_keys = expression.args.get("unique_keys") 3795 if unique_keys is not None: 3796 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3797 else: 3798 unique_keys = "" 3799 3800 return_type = self.sql(expression, "return_type") 3801 return_type = f" RETURNING {return_type}" if return_type else "" 3802 encoding = self.sql(expression, "encoding") 3803 encoding = f" ENCODING {encoding}" if encoding else "" 3804 3805 if not name: 3806 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3807 3808 return self.func( 3809 name, 3810 *expression.expressions, 3811 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3812 ) 3813 3814 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3815 null_handling = expression.args.get("null_handling") 3816 null_handling = f" {null_handling}" if null_handling else "" 3817 return_type = self.sql(expression, "return_type") 3818 return_type = f" RETURNING {return_type}" if return_type else "" 3819 strict = " STRICT" if expression.args.get("strict") else "" 3820 return self.func( 3821 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3822 ) 3823 3824 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3825 this = self.sql(expression, "this") 3826 order = self.sql(expression, "order") 3827 null_handling = expression.args.get("null_handling") 3828 null_handling = f" {null_handling}" if null_handling else "" 3829 return_type = self.sql(expression, "return_type") 3830 return_type = f" RETURNING {return_type}" if return_type else "" 3831 strict = " STRICT" if expression.args.get("strict") else "" 3832 return self.func( 3833 "JSON_ARRAYAGG", 3834 this, 3835 suffix=f"{order}{null_handling}{return_type}{strict})", 3836 ) 3837 3838 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3839 path = self.sql(expression, "path") 3840 path = f" PATH {path}" if path else "" 3841 nested_schema = self.sql(expression, "nested_schema") 3842 3843 if nested_schema: 3844 return f"NESTED{path} {nested_schema}" 3845 3846 this = self.sql(expression, "this") 3847 kind = self.sql(expression, "kind") 3848 kind = f" {kind}" if kind else "" 3849 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3850 3851 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3852 return f"{this}{kind}{format_json}{path}{ordinality}" 3853 3854 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3855 return self.func("COLUMNS", *expression.expressions) 3856 3857 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3858 this = self.sql(expression, "this") 3859 path = self.sql(expression, "path") 3860 path = f", {path}" if path else "" 3861 error_handling = expression.args.get("error_handling") 3862 error_handling = f" {error_handling}" if error_handling else "" 3863 empty_handling = expression.args.get("empty_handling") 3864 empty_handling = f" {empty_handling}" if empty_handling else "" 3865 schema = self.sql(expression, "schema") 3866 return self.func( 3867 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3868 ) 3869 3870 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3871 this = self.sql(expression, "this") 3872 kind = self.sql(expression, "kind") 3873 path = self.sql(expression, "path") 3874 path = f" {path}" if path else "" 3875 as_json = " AS JSON" if expression.args.get("as_json") else "" 3876 return f"{this} {kind}{path}{as_json}" 3877 3878 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3879 this = self.sql(expression, "this") 3880 path = self.sql(expression, "path") 3881 path = f", {path}" if path else "" 3882 expressions = self.expressions(expression) 3883 with_ = ( 3884 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3885 if expressions 3886 else "" 3887 ) 3888 return f"OPENJSON({this}{path}){with_}" 3889 3890 def in_sql(self, expression: exp.In) -> str: 3891 query = expression.args.get("query") 3892 unnest = expression.args.get("unnest") 3893 field = expression.args.get("field") 3894 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3895 3896 if query: 3897 in_sql = self.sql(query) 3898 elif unnest: 3899 in_sql = self.in_unnest_op(unnest) 3900 elif field: 3901 in_sql = self.sql(field) 3902 else: 3903 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3904 3905 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3906 3907 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3908 return f"(SELECT {self.sql(unnest)})" 3909 3910 def interval_sql(self, expression: exp.Interval) -> str: 3911 unit_expression = expression.args.get("unit") 3912 unit = self.sql(unit_expression) if unit_expression else "" 3913 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3914 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3915 unit = f" {unit}" if unit else "" 3916 3917 if self.SINGLE_STRING_INTERVAL: 3918 this = expression.this.name if expression.this else "" 3919 if this: 3920 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3921 return f"INTERVAL '{this}'{unit}" 3922 return f"INTERVAL '{this}{unit}'" 3923 return f"INTERVAL{unit}" 3924 3925 this = self.sql(expression, "this") 3926 if this: 3927 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3928 this = f" {this}" if unwrapped else f" ({this})" 3929 3930 return f"INTERVAL{this}{unit}" 3931 3932 def return_sql(self, expression: exp.Return) -> str: 3933 return f"RETURN {self.sql(expression, 'this')}" 3934 3935 def reference_sql(self, expression: exp.Reference) -> str: 3936 this = self.sql(expression, "this") 3937 expressions = self.expressions(expression, flat=True) 3938 expressions = f"({expressions})" if expressions else "" 3939 options = self.expressions(expression, key="options", flat=True, sep=" ") 3940 options = f" {options}" if options else "" 3941 return f"REFERENCES {this}{expressions}{options}" 3942 3943 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3944 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3945 parent = expression.parent 3946 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3947 3948 return self.func( 3949 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3950 ) 3951 3952 def paren_sql(self, expression: exp.Paren) -> str: 3953 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3954 return f"({sql}{self.seg(')', sep='')}" 3955 3956 def neg_sql(self, expression: exp.Neg) -> str: 3957 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3958 this_sql = self.sql(expression, "this") 3959 sep = " " if this_sql[0] == "-" else "" 3960 return f"-{sep}{this_sql}" 3961 3962 def not_sql(self, expression: exp.Not) -> str: 3963 return f"NOT {self.sql(expression, 'this')}" 3964 3965 def alias_sql(self, expression: exp.Alias) -> str: 3966 alias = self.sql(expression, "alias") 3967 alias = f" AS {alias}" if alias else "" 3968 return f"{self.sql(expression, 'this')}{alias}" 3969 3970 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3971 alias = expression.args["alias"] 3972 3973 parent = expression.parent 3974 pivot = parent and parent.parent 3975 3976 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3977 identifier_alias = isinstance(alias, exp.Identifier) 3978 literal_alias = isinstance(alias, exp.Literal) 3979 3980 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3981 alias.replace(exp.Literal.string(alias.output_name)) 3982 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3983 alias.replace(exp.to_identifier(alias.output_name)) 3984 3985 return self.alias_sql(expression) 3986 3987 def aliases_sql(self, expression: exp.Aliases) -> str: 3988 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3989 3990 def atindex_sql(self, expression: exp.AtIndex) -> str: 3991 this = self.sql(expression, "this") 3992 index = self.sql(expression, "expression") 3993 return f"{this} AT {index}" 3994 3995 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3996 this = self.sql(expression, "this") 3997 zone = self.sql(expression, "zone") 3998 return f"{this} AT TIME ZONE {zone}" 3999 4000 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4001 this = self.sql(expression, "this") 4002 zone = self.sql(expression, "zone") 4003 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4004 4005 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4006 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4007 4008 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4009 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4010 4011 def add_sql(self, expression: exp.Add) -> str: 4012 return self.binary(expression, "+") 4013 4014 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4015 return self.connector_sql(expression, "AND", stack) 4016 4017 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4018 return self.connector_sql(expression, "OR", stack) 4019 4020 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4021 return self.connector_sql(expression, "XOR", stack) 4022 4023 def connector_sql( 4024 self, 4025 expression: exp.Connector, 4026 op: str, 4027 stack: list[str | exp.Expr] | None = None, 4028 ) -> str: 4029 if stack is not None: 4030 if expression.expressions: 4031 stack.append(self.expressions(expression, sep=f" {op} ")) 4032 else: 4033 stack.append(expression.right) 4034 if expression.comments and self.comments: 4035 op = self.maybe_comment(op, comments=expression.comments) 4036 stack.extend((op, expression.left)) 4037 return op 4038 4039 stack = [expression] 4040 sqls: list[str] = [] 4041 ops = set() 4042 4043 while stack: 4044 node = stack.pop() 4045 if isinstance(node, exp.Connector): 4046 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4047 else: 4048 sql = self.sql(node) 4049 if sqls and sqls[-1] in ops: 4050 sqls[-1] += f" {sql}" 4051 else: 4052 sqls.append(sql) 4053 4054 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4055 return sep.join(sqls) 4056 4057 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4058 return self.binary(expression, "&") 4059 4060 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4061 return self.binary(expression, "<<") 4062 4063 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4064 return f"~{self.sql(expression, 'this')}" 4065 4066 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4067 return self.binary(expression, "|") 4068 4069 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4070 return self.binary(expression, ">>") 4071 4072 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4073 return self.binary(expression, "^") 4074 4075 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4076 format_sql = self.sql(expression, "format") 4077 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4078 to_sql = self.sql(expression, "to") 4079 to_sql = f" {to_sql}" if to_sql else "" 4080 action = self.sql(expression, "action") 4081 action = f" {action}" if action else "" 4082 default = self.sql(expression, "default") 4083 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4084 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4085 4086 # Base implementation that excludes safe, zone, and target_type metadata args 4087 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4088 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4089 4090 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4091 return self.func( 4092 "PARSE_DATETIME", 4093 expression.this, 4094 expression.args.get("format"), 4095 expression.args.get("zone"), 4096 ) 4097 4098 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4099 zone = self.sql(expression, "this") 4100 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4101 4102 def collate_sql(self, expression: exp.Collate) -> str: 4103 if self.COLLATE_IS_FUNC: 4104 return self.function_fallback_sql(expression) 4105 return self.binary(expression, "COLLATE") 4106 4107 def command_sql(self, expression: exp.Command) -> str: 4108 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4109 4110 def comment_sql(self, expression: exp.Comment) -> str: 4111 this = self.sql(expression, "this") 4112 kind = expression.args["kind"] 4113 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4114 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4115 expression_sql = self.sql(expression, "expression") 4116 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4117 4118 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4119 this = self.sql(expression, "this") 4120 delete = " DELETE" if expression.args.get("delete") else "" 4121 recompress = self.sql(expression, "recompress") 4122 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4123 to_disk = self.sql(expression, "to_disk") 4124 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4125 to_volume = self.sql(expression, "to_volume") 4126 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4127 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4128 4129 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4130 where = self.sql(expression, "where") 4131 group = self.sql(expression, "group") 4132 aggregates = self.expressions(expression, key="aggregates") 4133 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4134 4135 if not (where or group or aggregates) and len(expression.expressions) == 1: 4136 return f"TTL {self.expressions(expression, flat=True)}" 4137 4138 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4139 4140 def transaction_sql(self, expression: exp.Transaction) -> str: 4141 modes = self.expressions(expression, key="modes") 4142 modes = f" {modes}" if modes else "" 4143 return f"BEGIN{modes}" 4144 4145 def commit_sql(self, expression: exp.Commit) -> str: 4146 chain = expression.args.get("chain") 4147 if chain is not None: 4148 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4149 4150 return f"COMMIT{chain or ''}" 4151 4152 def rollback_sql(self, expression: exp.Rollback) -> str: 4153 savepoint = expression.args.get("savepoint") 4154 savepoint = f" TO {savepoint}" if savepoint else "" 4155 return f"ROLLBACK{savepoint}" 4156 4157 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4158 this = self.sql(expression, "this") 4159 4160 dtype = self.sql(expression, "dtype") 4161 if dtype: 4162 collate = self.sql(expression, "collate") 4163 collate = f" COLLATE {collate}" if collate else "" 4164 using = self.sql(expression, "using") 4165 using = f" USING {using}" if using else "" 4166 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4167 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4168 4169 default = self.sql(expression, "default") 4170 if default: 4171 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4172 4173 comment = self.sql(expression, "comment") 4174 if comment: 4175 return f"ALTER COLUMN {this} COMMENT {comment}" 4176 4177 visible = expression.args.get("visible") 4178 if visible: 4179 return f"ALTER COLUMN {this} SET {visible}" 4180 4181 allow_null = expression.args.get("allow_null") 4182 drop = expression.args.get("drop") 4183 4184 if not drop and not allow_null: 4185 self.unsupported("Unsupported ALTER COLUMN syntax") 4186 4187 if allow_null is not None: 4188 keyword = "DROP" if drop else "SET" 4189 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4190 4191 return f"ALTER COLUMN {this} DROP DEFAULT" 4192 4193 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4194 this = self.sql(expression, "this") 4195 rename_from = self.sql(expression, "rename_from") 4196 if rename_from: 4197 if not self.SUPPORTS_CHANGE_COLUMN: 4198 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4199 return f"CHANGE COLUMN {rename_from} {this}" 4200 if not self.SUPPORTS_MODIFY_COLUMN: 4201 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4202 return f"MODIFY COLUMN {this}" 4203 4204 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4205 this = self.sql(expression, "this") 4206 4207 visible = expression.args.get("visible") 4208 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4209 4210 return f"ALTER INDEX {this} {visible_sql}" 4211 4212 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4213 this = self.sql(expression, "this") 4214 if not isinstance(expression.this, exp.Var): 4215 this = f"KEY DISTKEY {this}" 4216 return f"ALTER DISTSTYLE {this}" 4217 4218 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4219 compound = " COMPOUND" if expression.args.get("compound") else "" 4220 this = self.sql(expression, "this") 4221 expressions = self.expressions(expression, flat=True) 4222 expressions = f"({expressions})" if expressions else "" 4223 return f"ALTER{compound} SORTKEY {this or expressions}" 4224 4225 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4226 if not self.RENAME_TABLE_WITH_DB: 4227 # Remove db from tables 4228 expression = expression.transform( 4229 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4230 ).assert_is(exp.AlterRename) 4231 this = self.sql(expression, "this") 4232 to_kw = " TO" if include_to else "" 4233 return f"RENAME{to_kw} {this}" 4234 4235 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4236 exists = " IF EXISTS" if expression.args.get("exists") else "" 4237 old_column = self.sql(expression, "this") 4238 new_column = self.sql(expression, "to") 4239 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4240 4241 def alterset_sql(self, expression: exp.AlterSet) -> str: 4242 exprs = self.expressions(expression, flat=True) 4243 if self.ALTER_SET_WRAPPED: 4244 exprs = f"({exprs})" 4245 4246 return f"SET {exprs}" 4247 4248 def alter_sql(self, expression: exp.Alter) -> str: 4249 actions = expression.args["actions"] 4250 4251 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4252 actions[0], exp.ColumnDef 4253 ): 4254 actions_sql = self.expressions(expression, key="actions", flat=True) 4255 actions_sql = f"ADD {actions_sql}" 4256 else: 4257 actions_list = [] 4258 for action in actions: 4259 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4260 action_sql = self.add_column_sql(action) 4261 else: 4262 action_sql = self.sql(action) 4263 if isinstance(action, exp.Query): 4264 action_sql = f"AS {action_sql}" 4265 4266 actions_list.append(action_sql) 4267 4268 actions_sql = self.format_args(*actions_list).lstrip("\n") 4269 4270 iceberg = ( 4271 "ICEBERG " 4272 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4273 else "" 4274 ) 4275 exists = " IF EXISTS" if expression.args.get("exists") else "" 4276 on_cluster = self.sql(expression, "cluster") 4277 on_cluster = f" {on_cluster}" if on_cluster else "" 4278 only = " ONLY" if expression.args.get("only") else "" 4279 options = self.expressions(expression, key="options") 4280 options = f", {options}" if options else "" 4281 kind = self.sql(expression, "kind") 4282 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4283 check = " WITH CHECK" if expression.args.get("check") else "" 4284 cascade = ( 4285 " CASCADE" 4286 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4287 else "" 4288 ) 4289 this = self.sql(expression, "this") 4290 this = f" {this}" if this else "" 4291 4292 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4293 4294 def altersession_sql(self, expression: exp.AlterSession) -> str: 4295 items_sql = self.expressions(expression, flat=True) 4296 keyword = "UNSET" if expression.args.get("unset") else "SET" 4297 return f"{keyword} {items_sql}" 4298 4299 def add_column_sql(self, expression: exp.Expr) -> str: 4300 sql = self.sql(expression) 4301 if isinstance(expression, exp.Schema): 4302 column_text = " COLUMNS" 4303 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4304 column_text = " COLUMN" 4305 else: 4306 column_text = "" 4307 4308 return f"ADD{column_text} {sql}" 4309 4310 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4311 expressions = self.expressions(expression) 4312 exists = " IF EXISTS " if expression.args.get("exists") else " " 4313 return f"DROP{exists}{expressions}" 4314 4315 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4316 return "DROP PRIMARY KEY" 4317 4318 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4319 return f"ADD {self.expressions(expression, indent=False)}" 4320 4321 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4322 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4323 location = self.sql(expression, "location") 4324 location = f" {location}" if location else "" 4325 return f"ADD {exists}{self.sql(expression.this)}{location}" 4326 4327 def distinct_sql(self, expression: exp.Distinct) -> str: 4328 this = self.expressions(expression, flat=True) 4329 4330 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4331 case = exp.case() 4332 for arg in expression.expressions: 4333 case = case.when(arg.is_(exp.null()), exp.null()) 4334 this = self.sql(case.else_(f"({this})")) 4335 4336 this = f" {this}" if this else "" 4337 4338 on = self.sql(expression, "on") 4339 on = f" ON {on}" if on else "" 4340 return f"DISTINCT{this}{on}" 4341 4342 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4343 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4344 4345 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4346 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4347 4348 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4349 this_sql = self.sql(expression, "this") 4350 expression_sql = self.sql(expression, "expression") 4351 kind = "MAX" if expression.args.get("max") else "MIN" 4352 return f"{this_sql} HAVING {kind} {expression_sql}" 4353 4354 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4355 return self.sql( 4356 exp.Cast( 4357 this=exp.Div(this=expression.this, expression=expression.expression), 4358 to=exp.DataType(this=exp.DType.INT), 4359 ) 4360 ) 4361 4362 def dpipe_sql(self, expression: exp.DPipe) -> str: 4363 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4364 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4365 return self.binary(expression, "||") 4366 4367 def div_sql(self, expression: exp.Div) -> str: 4368 l, r = expression.left, expression.right 4369 4370 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4371 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4372 4373 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4374 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4375 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4376 4377 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4378 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4379 return self.sql( 4380 exp.cast( 4381 l / r, 4382 to=exp.DType.BIGINT, 4383 ) 4384 ) 4385 4386 return self.binary(expression, "/") 4387 4388 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4389 n = exp._wrap(expression.this, exp.Binary) 4390 d = exp._wrap(expression.expression, exp.Binary) 4391 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4392 4393 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4394 return self.binary(expression, "OVERLAPS") 4395 4396 def distance_sql(self, expression: exp.Distance) -> str: 4397 return self.binary(expression, "<->") 4398 4399 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4400 return self.binary(expression, "<<->>") 4401 4402 def dot_sql(self, expression: exp.Dot) -> str: 4403 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4404 4405 def eq_sql(self, expression: exp.EQ) -> str: 4406 return self.binary(expression, "=") 4407 4408 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4409 return self.binary(expression, ":=") 4410 4411 def escape_sql(self, expression: exp.Escape) -> str: 4412 this = expression.this 4413 if ( 4414 isinstance(this, (exp.Like, exp.ILike)) 4415 and isinstance(this.expression, (exp.All, exp.Any)) 4416 and not self.SUPPORTS_LIKE_QUANTIFIERS 4417 ): 4418 return self._like_sql(this, escape=expression) 4419 return self.binary(expression, "ESCAPE") 4420 4421 def glob_sql(self, expression: exp.Glob) -> str: 4422 return self.binary(expression, "GLOB") 4423 4424 def gt_sql(self, expression: exp.GT) -> str: 4425 return self.binary(expression, ">") 4426 4427 def gte_sql(self, expression: exp.GTE) -> str: 4428 return self.binary(expression, ">=") 4429 4430 def is_sql(self, expression: exp.Is) -> str: 4431 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4432 return self.sql( 4433 expression.this if expression.expression.this else exp.not_(expression.this) 4434 ) 4435 return self.binary(expression, "IS") 4436 4437 def _like_sql( 4438 self, 4439 expression: exp.Like | exp.ILike, 4440 escape: exp.Escape | None = None, 4441 ) -> str: 4442 this = expression.this 4443 rhs = expression.expression 4444 4445 if isinstance(expression, exp.Like): 4446 exp_class: type[exp.Like | exp.ILike] = exp.Like 4447 op = "LIKE" 4448 else: 4449 exp_class = exp.ILike 4450 op = "ILIKE" 4451 4452 if expression.args.get("negate"): 4453 op = f"NOT {op}" 4454 4455 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4456 exprs = rhs.this.unnest() 4457 4458 if isinstance(exprs, exp.Tuple): 4459 exprs = exprs.expressions 4460 else: 4461 exprs = [exprs] 4462 4463 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4464 4465 def _make_like(expr: exp.Expression) -> exp.Expression: 4466 like: exp.Expression = exp_class( 4467 this=this, expression=expr, negate=expression.args.get("negate") 4468 ) 4469 if escape: 4470 like = exp.Escape(this=like, expression=escape.expression.copy()) 4471 return like 4472 4473 like_expr: exp.Expr = _make_like(exprs[0]) 4474 for expr in exprs[1:]: 4475 like_expr = connective(like_expr, _make_like(expr), copy=False) 4476 4477 parent = escape.parent if escape else expression.parent 4478 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4479 parent, exp.Condition 4480 ): 4481 like_expr = exp.paren(like_expr, copy=False) 4482 4483 return self.sql(like_expr) 4484 4485 return self.binary(expression, op) 4486 4487 def like_sql(self, expression: exp.Like) -> str: 4488 return self._like_sql(expression) 4489 4490 def ilike_sql(self, expression: exp.ILike) -> str: 4491 return self._like_sql(expression) 4492 4493 def match_sql(self, expression: exp.Match) -> str: 4494 return self.binary(expression, "MATCH") 4495 4496 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4497 return self.binary(expression, "SIMILAR TO") 4498 4499 def lt_sql(self, expression: exp.LT) -> str: 4500 return self.binary(expression, "<") 4501 4502 def lte_sql(self, expression: exp.LTE) -> str: 4503 return self.binary(expression, "<=") 4504 4505 def mod_sql(self, expression: exp.Mod) -> str: 4506 return self.binary(expression, "%") 4507 4508 def mul_sql(self, expression: exp.Mul) -> str: 4509 return self.binary(expression, "*") 4510 4511 def neq_sql(self, expression: exp.NEQ) -> str: 4512 return self.binary(expression, "<>") 4513 4514 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4515 return self.binary(expression, "IS NOT DISTINCT FROM") 4516 4517 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4518 return self.binary(expression, "IS DISTINCT FROM") 4519 4520 def sub_sql(self, expression: exp.Sub) -> str: 4521 return self.binary(expression, "-") 4522 4523 def trycast_sql(self, expression: exp.TryCast) -> str: 4524 return self.cast_sql(expression, safe_prefix="TRY_") 4525 4526 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4527 return self.cast_sql(expression) 4528 4529 def try_sql(self, expression: exp.Try) -> str: 4530 if not self.TRY_SUPPORTED: 4531 self.unsupported("Unsupported TRY function") 4532 return self.sql(expression, "this") 4533 4534 return self.func("TRY", expression.this) 4535 4536 def log_sql(self, expression: exp.Log) -> str: 4537 this = expression.this 4538 expr = expression.expression 4539 4540 if self.dialect.LOG_BASE_FIRST is False: 4541 this, expr = expr, this 4542 elif self.dialect.LOG_BASE_FIRST is None and expr: 4543 if this.name in ("2", "10"): 4544 return self.func(f"LOG{this.name}", expr) 4545 4546 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4547 4548 return self.func("LOG", this, expr) 4549 4550 def use_sql(self, expression: exp.Use) -> str: 4551 kind = self.sql(expression, "kind") 4552 kind = f" {kind}" if kind else "" 4553 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4554 this = f" {this}" if this else "" 4555 return f"USE{kind}{this}" 4556 4557 def binary(self, expression: exp.Binary, op: str) -> str: 4558 sqls: list[str] = [] 4559 stack: list[None | str | exp.Expr] = [expression] 4560 binary_type = type(expression) 4561 4562 while stack: 4563 node = stack.pop() 4564 4565 if type(node) is binary_type: 4566 op_func = node.args.get("operator") 4567 if op_func: 4568 op = f"OPERATOR({self.sql(op_func)})" 4569 4570 stack.append(node.args.get("expression")) 4571 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4572 stack.append(node.args.get("this")) 4573 else: 4574 sqls.append(self.sql(node)) 4575 4576 return "".join(sqls) 4577 4578 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4579 to_clause = self.sql(expression, "to") 4580 if to_clause: 4581 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4582 4583 return self.function_fallback_sql(expression) 4584 4585 def function_fallback_sql(self, expression: exp.Func) -> str: 4586 args = [] 4587 4588 for key in expression.arg_types: 4589 arg_value = expression.args.get(key) 4590 4591 if isinstance(arg_value, list): 4592 for value in arg_value: 4593 args.append(value) 4594 elif arg_value is not None: 4595 args.append(arg_value) 4596 4597 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4598 name = expression.meta_get("name") or expression.sql_name() 4599 else: 4600 name = expression.sql_name() 4601 4602 return self.func(name, *args) 4603 4604 def func( 4605 self, 4606 name: str, 4607 *args: t.Any, 4608 prefix: str = "(", 4609 suffix: str = ")", 4610 normalize: bool = True, 4611 ) -> str: 4612 name = self.normalize_func(name) if normalize else name 4613 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4614 4615 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4616 arg_sqls = tuple( 4617 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4618 ) 4619 if self.pretty and self.too_wide(arg_sqls): 4620 return self.indent( 4621 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4622 ) 4623 return sep.join(arg_sqls) 4624 4625 def too_wide(self, args: t.Iterable) -> bool: 4626 return sum(len(arg) for arg in args) > self.max_text_width 4627 4628 def format_time( 4629 self, 4630 expression: exp.Expr, 4631 inverse_time_mapping: dict[str, str] | None = None, 4632 inverse_time_trie: dict | None = None, 4633 ) -> str | None: 4634 return format_time( 4635 self.sql(expression, "format"), 4636 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4637 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4638 ) 4639 4640 def expressions( 4641 self, 4642 expression: exp.Expr | None = None, 4643 key: str | None = None, 4644 sqls: t.Collection[str | exp.Expr] | None = None, 4645 flat: bool = False, 4646 indent: bool = True, 4647 skip_first: bool = False, 4648 skip_last: bool = False, 4649 sep: str = ", ", 4650 prefix: str = "", 4651 dynamic: bool = False, 4652 new_line: bool = False, 4653 ) -> str: 4654 expressions = expression.args.get(key or "expressions") if expression else sqls 4655 4656 if not expressions: 4657 return "" 4658 4659 if flat: 4660 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4661 4662 num_sqls = len(expressions) 4663 result_sqls = [] 4664 4665 for i, e in enumerate(expressions): 4666 sql = self.sql(e, comment=False) 4667 if not sql: 4668 continue 4669 4670 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4671 4672 if self.pretty: 4673 if self.leading_comma: 4674 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4675 else: 4676 result_sqls.append( 4677 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4678 ) 4679 else: 4680 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4681 4682 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4683 if new_line: 4684 result_sqls.insert(0, "") 4685 result_sqls.append("") 4686 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4687 else: 4688 result_sql = "".join(result_sqls) 4689 4690 return ( 4691 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4692 if indent 4693 else result_sql 4694 ) 4695 4696 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4697 flat = flat or isinstance(expression.parent, exp.Properties) 4698 expressions_sql = self.expressions(expression, flat=flat) 4699 if flat: 4700 return f"{op} {expressions_sql}" 4701 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4702 4703 def naked_property(self, expression: exp.Property) -> str: 4704 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4705 if not property_name: 4706 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4707 return f"{property_name} {self.sql(expression, 'this')}" 4708 4709 def tag_sql(self, expression: exp.Tag) -> str: 4710 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4711 4712 def token_sql(self, token_type: TokenType) -> str: 4713 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4714 4715 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4716 this = self.sql(expression, "this") 4717 expressions = self.no_identify(self.expressions, expression) 4718 expressions = ( 4719 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4720 ) 4721 return f"{this}{expressions}" if expressions.strip() != "" else this 4722 4723 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4724 return self.expressions(expression, flat=True) 4725 4726 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4727 params = self.no_identify(self.expressions, expression, flat=True) 4728 body = self.sql(expression, "this") 4729 prefix = "TABLE " if expression.args.get("is_table") else "" 4730 return f"({params}) AS {prefix}{body}" 4731 4732 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4733 this = self.sql(expression, "this") 4734 expressions = self.expressions(expression, flat=True) 4735 return f"{this}({expressions})" 4736 4737 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4738 return self.binary(expression, "=>") 4739 4740 def when_sql(self, expression: exp.When) -> str: 4741 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4742 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4743 condition = self.sql(expression, "condition") 4744 condition = f" AND {condition}" if condition else "" 4745 4746 then_expression = expression.args.get("then") 4747 if isinstance(then_expression, exp.Insert): 4748 this = self.sql(then_expression, "this") 4749 this = f"INSERT {this}" if this else "INSERT" 4750 then = self.sql(then_expression, "expression") 4751 then = f"{this} VALUES {then}" if then else this 4752 elif isinstance(then_expression, exp.Update): 4753 if isinstance(then_expression.args.get("expressions"), exp.Star): 4754 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4755 else: 4756 expressions_sql = self.expressions(then_expression) 4757 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4758 else: 4759 then = self.sql(then_expression) 4760 4761 if isinstance(then_expression, (exp.Insert, exp.Update)): 4762 where = self.sql(then_expression, "where") 4763 if where and not self.SUPPORTS_MERGE_WHERE: 4764 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4765 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4766 where = "" 4767 then = f"{then}{where}" 4768 return f"WHEN {matched}{source}{condition} THEN {then}" 4769 4770 def whens_sql(self, expression: exp.Whens) -> str: 4771 return self.expressions(expression, sep=" ", indent=False) 4772 4773 def merge_sql(self, expression: exp.Merge) -> str: 4774 table = expression.this 4775 table_alias = "" 4776 4777 hints = table.args.get("hints") 4778 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4779 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4780 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4781 4782 this = self.sql(table) 4783 using = f"USING {self.sql(expression, 'using')}" 4784 whens = self.sql(expression, "whens") 4785 4786 on = self.sql(expression, "on") 4787 on = f"ON {on}" if on else "" 4788 4789 if not on: 4790 on = self.expressions(expression, key="using_cond") 4791 on = f"USING ({on})" if on else "" 4792 4793 returning = self.sql(expression, "returning") 4794 if returning: 4795 whens = f"{whens}{returning}" 4796 4797 sep = self.sep() 4798 4799 return self.prepend_ctes( 4800 expression, 4801 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4802 ) 4803 4804 @unsupported_args("format") 4805 def tochar_sql(self, expression: exp.ToChar) -> str: 4806 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4807 4808 @unsupported_args("default") 4809 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4810 if not self.SUPPORTS_TO_NUMBER: 4811 self.unsupported("Unsupported TO_NUMBER function") 4812 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4813 4814 fmt = expression.args.get("format") 4815 if not fmt: 4816 self.unsupported("Conversion format is required for TO_NUMBER") 4817 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4818 4819 return self.func("TO_NUMBER", expression.this, fmt) 4820 4821 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4822 this = self.sql(expression, "this") 4823 kind = self.sql(expression, "kind") 4824 settings_sql = self.expressions(expression, key="settings", sep=" ") 4825 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4826 return f"{this}({kind}{args})" 4827 4828 def dictrange_sql(self, expression: exp.DictRange) -> str: 4829 this = self.sql(expression, "this") 4830 max = self.sql(expression, "max") 4831 min = self.sql(expression, "min") 4832 return f"{this}(MIN {min} MAX {max})" 4833 4834 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4835 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4836 4837 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4838 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4839 4840 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4841 def uniquekeyproperty_sql( 4842 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4843 ) -> str: 4844 return f"{prefix} ({self.expressions(expression, flat=True)})" 4845 4846 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4847 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4848 expressions = self.expressions(expression, flat=True) 4849 expressions = f" {self.wrap(expressions)}" if expressions else "" 4850 buckets = self.sql(expression, "buckets") 4851 kind = self.sql(expression, "kind") 4852 buckets = f" BUCKETS {buckets}" if buckets else "" 4853 order = self.sql(expression, "order") 4854 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4855 4856 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4857 return "" 4858 4859 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4860 expressions = self.expressions(expression, key="expressions", flat=True) 4861 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4862 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4863 buckets = self.sql(expression, "buckets") 4864 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4865 4866 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4867 this = self.sql(expression, "this") 4868 having = self.sql(expression, "having") 4869 4870 if having: 4871 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4872 4873 return self.func("ANY_VALUE", this) 4874 4875 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4876 transform = self.func("TRANSFORM", *expression.expressions) 4877 row_format_before = self.sql(expression, "row_format_before") 4878 row_format_before = f" {row_format_before}" if row_format_before else "" 4879 record_writer = self.sql(expression, "record_writer") 4880 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4881 using = f" USING {self.sql(expression, 'command_script')}" 4882 schema = self.sql(expression, "schema") 4883 schema = f" AS {schema}" if schema else "" 4884 row_format_after = self.sql(expression, "row_format_after") 4885 row_format_after = f" {row_format_after}" if row_format_after else "" 4886 record_reader = self.sql(expression, "record_reader") 4887 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4888 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4889 4890 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4891 key_block_size = self.sql(expression, "key_block_size") 4892 if key_block_size: 4893 return f"KEY_BLOCK_SIZE = {key_block_size}" 4894 4895 using = self.sql(expression, "using") 4896 if using: 4897 return f"USING {using}" 4898 4899 parser = self.sql(expression, "parser") 4900 if parser: 4901 return f"WITH PARSER {parser}" 4902 4903 comment = self.sql(expression, "comment") 4904 if comment: 4905 return f"COMMENT {comment}" 4906 4907 visible = expression.args.get("visible") 4908 if visible is not None: 4909 return "VISIBLE" if visible else "INVISIBLE" 4910 4911 engine_attr = self.sql(expression, "engine_attr") 4912 if engine_attr: 4913 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4914 4915 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4916 if secondary_engine_attr: 4917 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4918 4919 self.unsupported("Unsupported index constraint option.") 4920 return "" 4921 4922 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4923 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4924 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4925 4926 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4927 kind = self.sql(expression, "kind") 4928 kind = f"{kind} INDEX" if kind else "INDEX" 4929 this = self.sql(expression, "this") 4930 this = f" {this}" if this else "" 4931 index_type = self.sql(expression, "index_type") 4932 index_type = f" USING {index_type}" if index_type else "" 4933 expressions = self.expressions(expression, flat=True) 4934 expressions = f" ({expressions})" if expressions else "" 4935 options = self.expressions(expression, key="options", sep=" ") 4936 options = f" {options}" if options else "" 4937 return f"{kind}{this}{index_type}{expressions}{options}" 4938 4939 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4940 if self.NVL2_SUPPORTED: 4941 return self.function_fallback_sql(expression) 4942 4943 case = exp.Case().when( 4944 expression.this.is_(exp.null()).not_(copy=False), 4945 expression.args["true"], 4946 copy=False, 4947 ) 4948 else_cond = expression.args.get("false") 4949 if else_cond: 4950 case.else_(else_cond, copy=False) 4951 4952 return self.sql(case) 4953 4954 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4955 this = self.sql(expression, "this") 4956 expr = self.sql(expression, "expression") 4957 position = self.sql(expression, "position") 4958 position = f", {position}" if position else "" 4959 iterator = self.sql(expression, "iterator") 4960 condition = self.sql(expression, "condition") 4961 condition = f" IF {condition}" if condition else "" 4962 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4963 4964 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4965 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4966 4967 def opclass_sql(self, expression: exp.Opclass) -> str: 4968 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4969 4970 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4971 model = self.sql(expression, "this") 4972 model = f"MODEL {model}" 4973 expr = expression.expression 4974 if expr: 4975 expr_sql = self.sql(expression, "expression") 4976 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4977 else: 4978 expr_sql = None 4979 4980 parameters = self.sql(expression, "params_struct") or None 4981 4982 return self.func(name, model, expr_sql, parameters) 4983 4984 def predict_sql(self, expression: exp.Predict) -> str: 4985 return self._ml_sql(expression, "PREDICT") 4986 4987 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4988 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4989 return self._ml_sql(expression, name) 4990 4991 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4992 return self._ml_sql(expression, "GENERATE_TEXT") 4993 4994 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4995 return self._ml_sql(expression, "GENERATE_TABLE") 4996 4997 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4998 return self._ml_sql(expression, "GENERATE_BOOL") 4999 5000 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5001 return self._ml_sql(expression, "GENERATE_INT") 5002 5003 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5004 return self._ml_sql(expression, "GENERATE_DOUBLE") 5005 5006 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5007 return self._ml_sql(expression, "TRANSLATE") 5008 5009 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5010 return self._ml_sql(expression, "FORECAST") 5011 5012 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5013 this_sql = self.sql(expression, "this") 5014 if isinstance(expression.this, exp.Table): 5015 this_sql = f"TABLE {this_sql}" 5016 5017 return self.func( 5018 "FORECAST", 5019 this_sql, 5020 expression.args.get("data_col"), 5021 expression.args.get("timestamp_col"), 5022 expression.args.get("model"), 5023 expression.args.get("id_cols"), 5024 expression.args.get("horizon"), 5025 expression.args.get("forecast_end_timestamp"), 5026 expression.args.get("confidence_level"), 5027 expression.args.get("output_historical_time_series"), 5028 expression.args.get("context_window"), 5029 ) 5030 5031 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5032 this_sql = self.sql(expression, "this") 5033 if isinstance(expression.this, exp.Table): 5034 this_sql = f"TABLE {this_sql}" 5035 5036 return self.func( 5037 "FEATURES_AT_TIME", 5038 this_sql, 5039 expression.args.get("time"), 5040 expression.args.get("num_rows"), 5041 expression.args.get("ignore_feature_nulls"), 5042 ) 5043 5044 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5045 this_sql = self.sql(expression, "this") 5046 if isinstance(expression.this, exp.Table): 5047 this_sql = f"TABLE {this_sql}" 5048 5049 query_table = self.sql(expression, "query_table") 5050 if isinstance(expression.args["query_table"], exp.Table): 5051 query_table = f"TABLE {query_table}" 5052 5053 return self.func( 5054 "VECTOR_SEARCH", 5055 this_sql, 5056 expression.args.get("column_to_search"), 5057 query_table, 5058 expression.args.get("query_column_to_search"), 5059 expression.args.get("top_k"), 5060 expression.args.get("distance_type"), 5061 expression.args.get("options"), 5062 ) 5063 5064 def forin_sql(self, expression: exp.ForIn) -> str: 5065 this = self.sql(expression, "this") 5066 expression_sql = self.sql(expression, "expression") 5067 return f"FOR {this} DO {expression_sql}" 5068 5069 def refresh_sql(self, expression: exp.Refresh) -> str: 5070 this = self.sql(expression, "this") 5071 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5072 return f"REFRESH {kind}{this}" 5073 5074 def toarray_sql(self, expression: exp.ToArray) -> str: 5075 arg = expression.this 5076 if not arg.type: 5077 import sqlglot.optimizer.annotate_types 5078 5079 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5080 5081 if arg.is_type(exp.DType.ARRAY): 5082 return self.sql(arg) 5083 5084 cond_for_null = arg.is_(exp.null()) 5085 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5086 5087 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5088 this = expression.this 5089 time_format = self.format_time(expression) 5090 5091 if time_format: 5092 return self.sql( 5093 exp.cast( 5094 exp.StrToTime(this=this, format=expression.args["format"]), 5095 exp.DType.TIME, 5096 ) 5097 ) 5098 5099 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5100 return self.sql(this) 5101 5102 return self.sql(exp.cast(this, exp.DType.TIME)) 5103 5104 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5105 this = expression.this 5106 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5107 return self.sql(this) 5108 5109 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5110 5111 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5112 this = expression.this 5113 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5114 return self.sql(this) 5115 5116 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5117 5118 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5119 this = expression.this 5120 time_format = self.format_time(expression) 5121 safe = expression.args.get("safe") 5122 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5123 return self.sql( 5124 exp.cast( 5125 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5126 exp.DType.DATE, 5127 ) 5128 ) 5129 5130 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5131 return self.sql(this) 5132 5133 if safe: 5134 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5135 5136 return self.sql(exp.cast(this, exp.DType.DATE)) 5137 5138 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5139 return self.sql( 5140 exp.func( 5141 "DATEDIFF", 5142 expression.this, 5143 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5144 "day", 5145 ) 5146 ) 5147 5148 def lastday_sql(self, expression: exp.LastDay) -> str: 5149 if self.LAST_DAY_SUPPORTS_DATE_PART: 5150 return self.function_fallback_sql(expression) 5151 5152 unit = expression.text("unit") 5153 if unit and unit != "MONTH": 5154 self.unsupported("Date parts are not supported in LAST_DAY.") 5155 5156 return self.func("LAST_DAY", expression.this) 5157 5158 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5159 import sqlglot.dialects.dialect 5160 5161 return self.func( 5162 "DATE_ADD", 5163 expression.this, 5164 expression.expression, 5165 sqlglot.dialects.dialect.unit_to_str(expression), 5166 ) 5167 5168 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5169 if self.CAN_IMPLEMENT_ARRAY_ANY: 5170 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5171 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5172 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5173 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5174 5175 import sqlglot.dialects.dialect 5176 5177 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5178 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5179 self.unsupported("ARRAY_ANY is unsupported") 5180 5181 return self.function_fallback_sql(expression) 5182 5183 def struct_sql(self, expression: exp.Struct) -> str: 5184 expression.set( 5185 "expressions", 5186 [ 5187 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5188 if isinstance(e, exp.PropertyEQ) 5189 else e 5190 for e in expression.expressions 5191 ], 5192 ) 5193 5194 return self.function_fallback_sql(expression) 5195 5196 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5197 low = self.sql(expression, "this") 5198 high = self.sql(expression, "expression") 5199 5200 return f"{low} TO {high}" 5201 5202 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5203 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5204 tables = f" {self.expressions(expression)}" 5205 5206 exists = " IF EXISTS" if expression.args.get("exists") else "" 5207 5208 on_cluster = self.sql(expression, "cluster") 5209 on_cluster = f" {on_cluster}" if on_cluster else "" 5210 5211 identity = self.sql(expression, "identity") 5212 identity = f" {identity} IDENTITY" if identity else "" 5213 5214 option = self.sql(expression, "option") 5215 option = f" {option}" if option else "" 5216 5217 partition = self.sql(expression, "partition") 5218 partition = f" {partition}" if partition else "" 5219 5220 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5221 5222 # This transpiles T-SQL's CONVERT function 5223 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5224 def convert_sql(self, expression: exp.Convert) -> str: 5225 to = expression.this 5226 value = expression.expression 5227 style = expression.args.get("style") 5228 safe = expression.args.get("safe") 5229 strict = expression.args.get("strict") 5230 5231 if not to or not value: 5232 return "" 5233 5234 # Retrieve length of datatype and override to default if not specified 5235 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5236 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5237 5238 transformed: exp.Expr | None = None 5239 cast = exp.Cast if strict else exp.TryCast 5240 5241 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5242 if isinstance(style, exp.Literal) and style.is_int: 5243 import sqlglot.dialects.tsql 5244 5245 style_value = style.name 5246 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5247 if not converted_style: 5248 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5249 5250 fmt = exp.Literal.string(converted_style) 5251 5252 if to.this == exp.DType.DATE: 5253 transformed = exp.StrToDate(this=value, format=fmt) 5254 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5255 transformed = exp.StrToTime(this=value, format=fmt) 5256 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5257 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5258 elif to.this == exp.DType.TEXT: 5259 transformed = exp.TimeToStr(this=value, format=fmt) 5260 5261 if not transformed: 5262 transformed = cast(this=value, to=to, safe=safe) 5263 5264 return self.sql(transformed) 5265 5266 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5267 this = expression.this 5268 if isinstance(this, exp.JSONPathWildcard): 5269 this = self.json_path_part(this) 5270 return f".{this}" if this else "" 5271 5272 quoted = expression.args.get("quoted") 5273 if not ( 5274 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5275 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5276 return f".{this}" 5277 5278 this = self.json_path_part(this) 5279 5280 if quoted and self.QUOTE_JSON_PATH: 5281 # The whole path is rendered as a single quoted string literal, so the bracketed key 5282 # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be 5283 # escaped again for the outer string literal (-> ["x \\"y\\"z"]). 5284 this = self.escape_str(this) 5285 5286 return ( 5287 f"[{this}]" 5288 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5289 else f".{this}" 5290 ) 5291 5292 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5293 this = self.json_path_part(expression.this) 5294 return f"[{this}]" if this else "" 5295 5296 def _simplify_unless_literal(self, expression: E) -> E: 5297 if not isinstance(expression, exp.Literal): 5298 import sqlglot.optimizer.simplify 5299 5300 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5301 5302 return expression 5303 5304 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5305 this = expression.this 5306 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5307 self.unsupported( 5308 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5309 ) 5310 return self.sql(this) 5311 5312 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5313 if self.IGNORE_NULLS_BEFORE_ORDER: 5314 # The first modifier here will be the one closest to the AggFunc's arg 5315 mods = sorted( 5316 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5317 key=lambda x: ( 5318 0 5319 if isinstance(x, exp.HavingMax) 5320 else (1 if isinstance(x, exp.Order) else 2) 5321 ), 5322 ) 5323 5324 if mods: 5325 mod = mods[0] 5326 this = expression.__class__(this=mod.this.copy()) 5327 this.meta["inline"] = True 5328 mod.this.replace(this) 5329 return self.sql(expression.this) 5330 5331 agg_func = expression.find(exp.AggFunc) 5332 5333 if agg_func: 5334 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5335 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5336 5337 return f"{self.sql(expression, 'this')} {text}" 5338 5339 def _replace_line_breaks(self, string: str) -> str: 5340 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5341 if self.pretty: 5342 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5343 return string 5344 5345 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5346 option = self.sql(expression, "this") 5347 5348 if expression.expressions: 5349 upper = option.upper() 5350 5351 # Snowflake FILE_FORMAT options are separated by whitespace 5352 sep = " " if upper == "FILE_FORMAT" else ", " 5353 5354 # Databricks copy/format options do not set their list of values with EQ 5355 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5356 values = self.expressions(expression, flat=True, sep=sep) 5357 return f"{option}{op}({values})" 5358 5359 value = self.sql(expression, "expression") 5360 5361 if not value: 5362 return option 5363 5364 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5365 5366 return f"{option}{op}{value}" 5367 5368 def credentials_sql(self, expression: exp.Credentials) -> str: 5369 cred_expr = expression.args.get("credentials") 5370 if isinstance(cred_expr, exp.Literal): 5371 # Redshift case: CREDENTIALS <string> 5372 credentials = self.sql(expression, "credentials") 5373 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5374 else: 5375 # Snowflake case: CREDENTIALS = (...) 5376 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5377 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5378 5379 storage = self.sql(expression, "storage") 5380 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5381 5382 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5383 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5384 5385 iam_role = self.sql(expression, "iam_role") 5386 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5387 5388 region = self.sql(expression, "region") 5389 region = f" REGION {region}" if region else "" 5390 5391 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5392 5393 def copy_sql(self, expression: exp.Copy) -> str: 5394 this = self.sql(expression, "this") 5395 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5396 5397 credentials = self.sql(expression, "credentials") 5398 credentials = self.seg(credentials) if credentials else "" 5399 files = self.expressions(expression, key="files", flat=True) 5400 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5401 5402 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5403 params = self.expressions( 5404 expression, 5405 key="params", 5406 sep=sep, 5407 new_line=True, 5408 skip_last=True, 5409 skip_first=True, 5410 indent=self.COPY_PARAMS_ARE_WRAPPED, 5411 ) 5412 5413 if params: 5414 if self.COPY_PARAMS_ARE_WRAPPED: 5415 params = f" WITH ({params})" 5416 elif not self.pretty and (files or credentials): 5417 params = f" {params}" 5418 5419 return f"COPY{this}{kind} {files}{credentials}{params}" 5420 5421 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5422 return "" 5423 5424 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5425 on_sql = "ON" if expression.args.get("on") else "OFF" 5426 filter_col: str | None = self.sql(expression, "filter_column") 5427 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5428 retention_period: str | None = self.sql(expression, "retention_period") 5429 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5430 5431 if filter_col or retention_period: 5432 on_sql = self.func("ON", filter_col, retention_period) 5433 5434 return f"DATA_DELETION={on_sql}" 5435 5436 def maskingpolicycolumnconstraint_sql( 5437 self, expression: exp.MaskingPolicyColumnConstraint 5438 ) -> str: 5439 this = self.sql(expression, "this") 5440 expressions = self.expressions(expression, flat=True) 5441 expressions = f" USING ({expressions})" if expressions else "" 5442 return f"MASKING POLICY {this}{expressions}" 5443 5444 def gapfill_sql(self, expression: exp.GapFill) -> str: 5445 this = self.sql(expression, "this") 5446 this = f"TABLE {this}" 5447 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5448 5449 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5450 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5451 5452 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5453 this = self.sql(expression, "this") 5454 expr = expression.expression 5455 5456 if isinstance(expr, exp.Func): 5457 # T-SQL's CLR functions are case sensitive 5458 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5459 else: 5460 expr = self.sql(expression, "expression") 5461 5462 return self.scope_resolution(expr, this) 5463 5464 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5465 if self.PARSE_JSON_NAME is None: 5466 return self.sql(expression.this) 5467 5468 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5469 5470 def rand_sql(self, expression: exp.Rand) -> str: 5471 lower = self.sql(expression, "lower") 5472 upper = self.sql(expression, "upper") 5473 5474 if lower and upper: 5475 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5476 return self.func("RAND", expression.this) 5477 5478 def changes_sql(self, expression: exp.Changes) -> str: 5479 information = self.sql(expression, "information") 5480 information = f"INFORMATION => {information}" 5481 at_before = self.sql(expression, "at_before") 5482 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5483 end = self.sql(expression, "end") 5484 end = f"{self.seg('')}{end}" if end else "" 5485 5486 return f"CHANGES ({information}){at_before}{end}" 5487 5488 def pad_sql(self, expression: exp.Pad) -> str: 5489 prefix = "L" if expression.args.get("is_left") else "R" 5490 5491 fill_pattern = self.sql(expression, "fill_pattern") or None 5492 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5493 fill_pattern = "' '" 5494 5495 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5496 5497 def summarize_sql(self, expression: exp.Summarize) -> str: 5498 table = " TABLE" if expression.args.get("table") else "" 5499 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5500 5501 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5502 generate_series = exp.GenerateSeries(**expression.args) 5503 5504 parent = expression.parent 5505 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5506 parent = parent.parent 5507 5508 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5509 return self.sql(exp.Unnest(expressions=[generate_series])) 5510 5511 if isinstance(parent, exp.Select): 5512 self.unsupported("GenerateSeries projection unnesting is not supported.") 5513 5514 return self.sql(generate_series) 5515 5516 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5517 if self.SUPPORTS_CONVERT_TIMEZONE: 5518 return self.function_fallback_sql(expression) 5519 5520 source_tz = expression.args.get("source_tz") 5521 target_tz = expression.args.get("target_tz") 5522 timestamp = expression.args.get("timestamp") 5523 5524 if source_tz and timestamp: 5525 timestamp = exp.AtTimeZone( 5526 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5527 ) 5528 5529 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5530 5531 return self.sql(expr) 5532 5533 def json_sql(self, expression: exp.JSON) -> str: 5534 this = self.sql(expression, "this") 5535 this = f" {this}" if this else "" 5536 5537 _with = expression.args.get("with_") 5538 5539 if _with is None: 5540 with_sql = "" 5541 elif not _with: 5542 with_sql = " WITHOUT" 5543 else: 5544 with_sql = " WITH" 5545 5546 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5547 5548 return f"JSON{this}{with_sql}{unique_sql}" 5549 5550 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5551 path = self.sql(expression, "path") 5552 returning = self.sql(expression, "returning") 5553 returning = f" RETURNING {returning}" if returning else "" 5554 5555 on_condition = self.sql(expression, "on_condition") 5556 on_condition = f" {on_condition}" if on_condition else "" 5557 5558 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5559 5560 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5561 regexp = " REGEXP" if expression.args.get("regexp") else "" 5562 return f"SKIP{regexp} {self.sql(expression.expression)}" 5563 5564 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5565 else_ = "ELSE " if expression.args.get("else_") else "" 5566 condition = self.sql(expression, "expression") 5567 condition = f"WHEN {condition} THEN " if condition else else_ 5568 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5569 return f"{condition}{insert}" 5570 5571 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5572 kind = self.sql(expression, "kind") 5573 expressions = self.seg(self.expressions(expression, sep=" ")) 5574 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5575 return res 5576 5577 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5578 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5579 empty = expression.args.get("empty") 5580 empty = ( 5581 f"DEFAULT {empty} ON EMPTY" 5582 if isinstance(empty, exp.Expr) 5583 else self.sql(expression, "empty") 5584 ) 5585 5586 error = expression.args.get("error") 5587 error = ( 5588 f"DEFAULT {error} ON ERROR" 5589 if isinstance(error, exp.Expr) 5590 else self.sql(expression, "error") 5591 ) 5592 5593 if error and empty: 5594 error = ( 5595 f"{empty} {error}" 5596 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5597 else f"{error} {empty}" 5598 ) 5599 empty = "" 5600 5601 null = self.sql(expression, "null") 5602 5603 return f"{empty}{error}{null}" 5604 5605 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5606 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5607 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5608 5609 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5610 this = self.sql(expression, "this") 5611 path = self.sql(expression, "path") 5612 5613 passing = self.expressions(expression, "passing") 5614 passing = f" PASSING {passing}" if passing else "" 5615 5616 on_condition = self.sql(expression, "on_condition") 5617 on_condition = f" {on_condition}" if on_condition else "" 5618 5619 path = f"{path}{passing}{on_condition}" 5620 5621 return self.func("JSON_EXISTS", this, path) 5622 5623 def _add_arrayagg_null_filter( 5624 self, 5625 array_agg_sql: str, 5626 array_agg_expr: exp.ArrayAgg, 5627 column_expr: exp.Expr, 5628 ) -> str: 5629 """ 5630 Add NULL filter to ARRAY_AGG if dialect requires it. 5631 5632 Args: 5633 array_agg_sql: The generated ARRAY_AGG SQL string 5634 array_agg_expr: The ArrayAgg expression node 5635 column_expr: The column/expression to filter (before ORDER BY wrapping) 5636 5637 Returns: 5638 SQL string with FILTER clause added if needed 5639 """ 5640 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5641 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5642 if not ( 5643 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5644 ): 5645 return array_agg_sql 5646 5647 parent = array_agg_expr.parent 5648 if isinstance(parent, exp.Filter): 5649 parent_cond = parent.expression.this 5650 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5651 elif column_expr.find(exp.Column): 5652 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5653 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5654 this_sql = ( 5655 self.expressions(column_expr) 5656 if isinstance(column_expr, exp.Distinct) 5657 else self.sql(column_expr) 5658 ) 5659 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5660 5661 return array_agg_sql 5662 5663 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5664 array_agg = self.function_fallback_sql(expression) 5665 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5666 5667 def slice_sql(self, expression: exp.Slice) -> str: 5668 step = self.sql(expression, "step") 5669 end = self.sql(expression.expression) 5670 begin = self.sql(expression.this) 5671 5672 sql = f"{end}:{step}" if step else end 5673 return f"{begin}:{sql}" if sql else f"{begin}:" 5674 5675 def apply_sql(self, expression: exp.Apply) -> str: 5676 this = self.sql(expression, "this") 5677 expr = self.sql(expression, "expression") 5678 5679 return f"{this} APPLY({expr})" 5680 5681 def _grant_or_revoke_sql( 5682 self, 5683 expression: exp.Grant | exp.Revoke, 5684 keyword: str, 5685 preposition: str, 5686 grant_option_prefix: str = "", 5687 grant_option_suffix: str = "", 5688 ) -> str: 5689 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5690 5691 kind = self.sql(expression, "kind") 5692 kind = f" {kind}" if kind else "" 5693 5694 securable = self.sql(expression, "securable") 5695 securable = f" {securable}" if securable else "" 5696 5697 principals = self.expressions(expression, key="principals", flat=True) 5698 5699 if not expression.args.get("grant_option"): 5700 grant_option_prefix = grant_option_suffix = "" 5701 5702 # cascade for revoke only 5703 cascade = self.sql(expression, "cascade") 5704 cascade = f" {cascade}" if cascade else "" 5705 5706 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5707 5708 def grant_sql(self, expression: exp.Grant) -> str: 5709 return self._grant_or_revoke_sql( 5710 expression, 5711 keyword="GRANT", 5712 preposition="TO", 5713 grant_option_suffix=" WITH GRANT OPTION", 5714 ) 5715 5716 def revoke_sql(self, expression: exp.Revoke) -> str: 5717 return self._grant_or_revoke_sql( 5718 expression, 5719 keyword="REVOKE", 5720 preposition="FROM", 5721 grant_option_prefix="GRANT OPTION FOR ", 5722 ) 5723 5724 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5725 this = self.sql(expression, "this") 5726 columns = self.expressions(expression, flat=True) 5727 columns = f"({columns})" if columns else "" 5728 5729 return f"{this}{columns}" 5730 5731 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5732 this = self.sql(expression, "this") 5733 5734 kind = self.sql(expression, "kind") 5735 kind = f"{kind} " if kind else "" 5736 5737 return f"{kind}{this}" 5738 5739 def columns_sql(self, expression: exp.Columns) -> str: 5740 func = self.function_fallback_sql(expression) 5741 if expression.args.get("unpack"): 5742 func = f"*{func}" 5743 5744 return func 5745 5746 def overlay_sql(self, expression: exp.Overlay) -> str: 5747 this = self.sql(expression, "this") 5748 expr = self.sql(expression, "expression") 5749 from_sql = self.sql(expression, "from_") 5750 for_sql = self.sql(expression, "for_") 5751 for_sql = f" FOR {for_sql}" if for_sql else "" 5752 5753 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5754 5755 @unsupported_args("format") 5756 def todouble_sql(self, expression: exp.ToDouble) -> str: 5757 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5758 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5759 5760 def string_sql(self, expression: exp.String) -> str: 5761 this = expression.this 5762 zone = expression.args.get("zone") 5763 5764 if zone: 5765 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5766 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5767 # set for source_tz to transpile the time conversion before the STRING cast 5768 this = exp.ConvertTimezone( 5769 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5770 ) 5771 5772 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5773 5774 def median_sql(self, expression: exp.Median) -> str: 5775 if not self.SUPPORTS_MEDIAN: 5776 return self.sql( 5777 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5778 ) 5779 5780 return self.function_fallback_sql(expression) 5781 5782 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5783 filler = self.sql(expression, "this") 5784 filler = f" {filler}" if filler else "" 5785 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5786 return f"TRUNCATE{filler} {with_count}" 5787 5788 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5789 if self.SUPPORTS_UNIX_SECONDS: 5790 return self.function_fallback_sql(expression) 5791 5792 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5793 5794 return self.sql( 5795 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5796 ) 5797 5798 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5799 dim = expression.expression 5800 5801 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5802 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5803 if not (dim.is_int and dim.name == "1"): 5804 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5805 dim = None 5806 5807 # If dimension is required but not specified, default initialize it 5808 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5809 dim = exp.Literal.number(1) 5810 5811 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5812 5813 def attach_sql(self, expression: exp.Attach) -> str: 5814 this = self.sql(expression, "this") 5815 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5816 expressions = self.expressions(expression) 5817 expressions = f" ({expressions})" if expressions else "" 5818 5819 return f"ATTACH{exists_sql} {this}{expressions}" 5820 5821 def detach_sql(self, expression: exp.Detach) -> str: 5822 kind = self.sql(expression, "kind") 5823 kind = f" {kind}" if kind else "" 5824 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5825 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5826 exists = " IF EXISTS" if expression.args.get("exists") else "" 5827 if exists: 5828 kind = kind or " DATABASE" 5829 5830 this = self.sql(expression, "this") 5831 this = f" {this}" if this else "" 5832 cluster = self.sql(expression, "cluster") 5833 cluster = f" {cluster}" if cluster else "" 5834 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5835 sync = " SYNC" if expression.args.get("sync") else "" 5836 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5837 5838 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5839 this = self.sql(expression, "this") 5840 value = self.sql(expression, "expression") 5841 value = f" {value}" if value else "" 5842 return f"{this}{value}" 5843 5844 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5845 return ( 5846 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5847 ) 5848 5849 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5850 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5851 encode = f"{encode} {self.sql(expression, 'this')}" 5852 5853 properties = expression.args.get("properties") 5854 if properties: 5855 encode = f"{encode} {self.properties(properties)}" 5856 5857 return encode 5858 5859 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5860 this = self.sql(expression, "this") 5861 include = f"INCLUDE {this}" 5862 5863 column_def = self.sql(expression, "column_def") 5864 if column_def: 5865 include = f"{include} {column_def}" 5866 5867 alias = self.sql(expression, "alias") 5868 if alias: 5869 include = f"{include} AS {alias}" 5870 5871 return include 5872 5873 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5874 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5875 name = f"{prefix} {self.sql(expression, 'this')}" 5876 return self.func("XMLELEMENT", name, *expression.expressions) 5877 5878 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5879 this = self.sql(expression, "this") 5880 expr = self.sql(expression, "expression") 5881 expr = f"({expr})" if expr else "" 5882 return f"{this}{expr}" 5883 5884 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5885 partitions = self.expressions(expression, "partition_expressions") 5886 create = self.expressions(expression, "create_expressions") 5887 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5888 5889 def partitionbyrangepropertydynamic_sql( 5890 self, expression: exp.PartitionByRangePropertyDynamic 5891 ) -> str: 5892 start = self.sql(expression, "start") 5893 end = self.sql(expression, "end") 5894 5895 every = expression.args["every"] 5896 if isinstance(every, exp.Interval) and every.this.is_string: 5897 every.this.replace(exp.Literal.number(every.name)) 5898 5899 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5900 5901 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5902 name = self.sql(expression, "this") 5903 values = self.expressions(expression, flat=True) 5904 5905 return f"NAME {name} VALUE {values}" 5906 5907 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5908 kind = self.sql(expression, "kind") 5909 sample = self.sql(expression, "sample") 5910 return f"SAMPLE {sample} {kind}" 5911 5912 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5913 kind = self.sql(expression, "kind") 5914 option = self.sql(expression, "option") 5915 option = f" {option}" if option else "" 5916 this = self.sql(expression, "this") 5917 this = f" {this}" if this else "" 5918 columns = self.expressions(expression) 5919 columns = f" {columns}" if columns else "" 5920 return f"{kind}{option} STATISTICS{this}{columns}" 5921 5922 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5923 this = self.sql(expression, "this") 5924 columns = self.expressions(expression) 5925 inner_expression = self.sql(expression, "expression") 5926 inner_expression = f" {inner_expression}" if inner_expression else "" 5927 update_options = self.sql(expression, "update_options") 5928 update_options = f" {update_options} UPDATE" if update_options else "" 5929 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5930 5931 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5932 kind = self.sql(expression, "kind") 5933 kind = f" {kind}" if kind else "" 5934 return f"DELETE{kind} STATISTICS" 5935 5936 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5937 inner_expression = self.sql(expression, "expression") 5938 return f"LIST CHAINED ROWS{inner_expression}" 5939 5940 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5941 kind = self.sql(expression, "kind") 5942 this = self.sql(expression, "this") 5943 this = f" {this}" if this else "" 5944 inner_expression = self.sql(expression, "expression") 5945 return f"VALIDATE {kind}{this}{inner_expression}" 5946 5947 def analyze_sql(self, expression: exp.Analyze) -> str: 5948 options = self.expressions(expression, key="options", sep=" ") 5949 options = f" {options}" if options else "" 5950 kind = self.sql(expression, "kind") 5951 kind = f" {kind}" if kind else "" 5952 this = self.sql(expression, "this") 5953 this = f" {this}" if this else "" 5954 mode = self.sql(expression, "mode") 5955 mode = f" {mode}" if mode else "" 5956 properties = self.sql(expression, "properties") 5957 properties = f" {properties}" if properties else "" 5958 partition = self.sql(expression, "partition") 5959 partition = f" {partition}" if partition else "" 5960 inner_expression = self.sql(expression, "expression") 5961 inner_expression = f" {inner_expression}" if inner_expression else "" 5962 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5963 5964 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5965 this = self.sql(expression, "this") 5966 namespaces = self.expressions(expression, key="namespaces") 5967 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5968 passing = self.expressions(expression, key="passing") 5969 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5970 columns = self.expressions(expression, key="columns") 5971 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5972 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5973 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5974 5975 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5976 this = self.sql(expression, "this") 5977 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5978 5979 def export_sql(self, expression: exp.Export) -> str: 5980 this = self.sql(expression, "this") 5981 connection = self.sql(expression, "connection") 5982 connection = f"WITH CONNECTION {connection} " if connection else "" 5983 options = self.sql(expression, "options") 5984 return f"EXPORT DATA {connection}{options} AS {this}" 5985 5986 def declare_sql(self, expression: exp.Declare) -> str: 5987 replace = "OR REPLACE " if expression.args.get("replace") else "" 5988 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5989 5990 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5991 variables = self.expressions(expression, "this") 5992 default = self.sql(expression, "default") 5993 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5994 5995 kind = self.sql(expression, "kind") 5996 if isinstance(expression.args.get("kind"), exp.Schema): 5997 kind = f"TABLE {kind}" 5998 5999 kind = f" {kind}" if kind else "" 6000 6001 return f"{variables}{kind}{default}" 6002 6003 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6004 kind = self.sql(expression, "kind") 6005 this = self.sql(expression, "this") 6006 set = self.sql(expression, "expression") 6007 using = self.sql(expression, "using") 6008 using = f" USING {using}" if using else "" 6009 6010 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6011 6012 return f"{kind_sql} {this} SET {set}{using}" 6013 6014 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6015 params = self.expressions(expression, key="params", flat=True) 6016 return self.func(expression.name, *expression.expressions) + f"({params})" 6017 6018 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6019 return self.func(expression.name, *expression.expressions) 6020 6021 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6022 return self.anonymousaggfunc_sql(expression) 6023 6024 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6025 return self.parameterizedagg_sql(expression) 6026 6027 def show_sql(self, expression: exp.Show) -> str: 6028 self.unsupported("Unsupported SHOW statement") 6029 return "" 6030 6031 def install_sql(self, expression: exp.Install) -> str: 6032 self.unsupported("Unsupported INSTALL statement") 6033 return "" 6034 6035 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6036 # Snowflake GET/PUT statements: 6037 # PUT <file> <internalStage> <properties> 6038 # GET <internalStage> <file> <properties> 6039 props = expression.args.get("properties") 6040 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6041 this = self.sql(expression, "this") 6042 target = self.sql(expression, "target") 6043 6044 if isinstance(expression, exp.Put): 6045 return f"PUT {this} {target}{props_sql}" 6046 else: 6047 return f"GET {target} {this}{props_sql}" 6048 6049 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6050 this = self.sql(expression, "this") 6051 expr = self.sql(expression, "expression") 6052 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6053 return f"TRANSLATE({this} USING {expr}{with_error})" 6054 6055 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6056 if self.SUPPORTS_DECODE_CASE: 6057 return self.func("DECODE", *expression.expressions) 6058 6059 decode_expr, *expressions = expression.expressions 6060 6061 ifs = [] 6062 for search, result in zip(expressions[::2], expressions[1::2]): 6063 if isinstance(search, exp.Literal): 6064 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6065 elif isinstance(search, exp.Null): 6066 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6067 else: 6068 if isinstance(search, exp.Binary): 6069 search = exp.paren(search) 6070 6071 cond = exp.or_( 6072 decode_expr.eq(search), 6073 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6074 copy=False, 6075 ) 6076 ifs.append(exp.If(this=cond, true=result)) 6077 6078 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6079 return self.sql(case) 6080 6081 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6082 this = self.sql(expression, "this") 6083 this = self.seg(this, sep="") 6084 dimensions = self.expressions( 6085 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6086 ) 6087 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6088 metrics = self.expressions( 6089 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6090 ) 6091 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6092 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6093 facts = self.seg(f"FACTS {facts}") if facts else "" 6094 where = self.sql(expression, "where") 6095 where = self.seg(f"WHERE {where}") if where else "" 6096 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6097 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6098 6099 def getextract_sql(self, expression: exp.GetExtract) -> str: 6100 this = expression.this 6101 expr = expression.expression 6102 6103 if not this.type or not expression.type: 6104 import sqlglot.optimizer.annotate_types 6105 6106 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6107 6108 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6109 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6110 6111 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6112 6113 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6114 return self.sql( 6115 exp.DateAdd( 6116 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6117 expression=expression.this, 6118 unit=exp.var("DAY"), 6119 ) 6120 ) 6121 6122 def space_sql(self: Generator, expression: exp.Space) -> str: 6123 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6124 6125 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6126 return f"BUILD {self.sql(expression, 'this')}" 6127 6128 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6129 method = self.sql(expression, "method") 6130 kind = expression.args.get("kind") 6131 if not kind: 6132 return f"REFRESH {method}" 6133 6134 every = self.sql(expression, "every") 6135 unit = self.sql(expression, "unit") 6136 every = f" EVERY {every} {unit}" if every else "" 6137 starts = self.sql(expression, "starts") 6138 starts = f" STARTS {starts}" if starts else "" 6139 6140 return f"REFRESH {method} ON {kind}{every}{starts}" 6141 6142 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6143 self.unsupported("The model!attribute syntax is not supported") 6144 return "" 6145 6146 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6147 return self.func("DIRECTORY", expression.this) 6148 6149 def uuid_sql(self, expression: exp.Uuid) -> str: 6150 is_string = expression.args.get("is_string", False) 6151 uuid_func_sql = self.func("UUID") 6152 6153 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6154 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6155 6156 return uuid_func_sql 6157 6158 def initcap_sql(self, expression: exp.Initcap) -> str: 6159 delimiters = expression.expression 6160 6161 if delimiters: 6162 # do not generate delimiters arg if we are round-tripping from default delimiters 6163 if ( 6164 delimiters.is_string 6165 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6166 ): 6167 delimiters = None 6168 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6169 self.unsupported("INITCAP does not support custom delimiters") 6170 delimiters = None 6171 6172 return self.func("INITCAP", expression.this, delimiters) 6173 6174 def localtime_sql(self, expression: exp.Localtime) -> str: 6175 this = expression.this 6176 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6177 6178 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6179 this = expression.this 6180 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6181 6182 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6183 this = expression.this.name.upper() 6184 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 6185 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 6186 return "WEEK" 6187 6188 return self.func("WEEK", expression.this) 6189 6190 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6191 this = self.expressions(expression) 6192 charset = self.sql(expression, "charset") 6193 using = f" USING {charset}" if charset else "" 6194 return self.func(name, this + using) 6195 6196 def block_sql(self, expression: exp.Block) -> str: 6197 expressions = self.expressions(expression, sep="; ", flat=True) 6198 return f"{expressions}" if expressions else "" 6199 6200 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6201 self.unsupported("Unsupported Stored Procedure syntax") 6202 return "" 6203 6204 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6205 self.unsupported("Unsupported If block syntax") 6206 return "" 6207 6208 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6209 self.unsupported("Unsupported While block syntax") 6210 return "" 6211 6212 def execute_sql(self, expression: exp.Execute) -> str: 6213 self.unsupported("Unsupported Execute syntax") 6214 return "" 6215 6216 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6217 self.unsupported("Unsupported Execute syntax") 6218 return "" 6219 6220 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6221 props = self.expressions(expression, sep=" ") 6222 return f"MODIFY {props}" 6223 6224 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6225 kind = expression.args.get("kind") 6226 return f"USING {kind} {self.sql(expression, 'this')}" 6227 6228 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6229 this = self.sql(expression, "this") 6230 to = self.sql(expression, "to") 6231 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]]:
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
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:
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.CaseSpecificColumnConstraint: lambda _, e: ( 151 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 152 ), 153 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 154 exp.Ceil: lambda self, e: self.ceil_floor(e), 155 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 156 exp.CharacterSetProperty: lambda self, e: ( 157 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 158 ), 159 exp.ClusteredColumnConstraint: lambda self, e: ( 160 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 161 ), 162 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 163 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 164 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 165 exp.ConvertToCharset: lambda self, e: self.func( 166 "CONVERT", e.this, e.args["dest"], e.args.get("source") 167 ), 168 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 169 exp.CredentialsProperty: lambda self, e: ( 170 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 171 ), 172 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 173 exp.SessionUser: lambda *_: "SESSION_USER", 174 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 175 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 176 exp.ApiProperty: lambda *_: "API", 177 exp.ApplicationProperty: lambda *_: "APPLICATION", 178 exp.CatalogProperty: lambda *_: "CATALOG", 179 exp.ComputeProperty: lambda *_: "COMPUTE", 180 exp.DatabaseProperty: lambda *_: "DATABASE", 181 exp.DynamicProperty: lambda *_: "DYNAMIC", 182 exp.EmptyProperty: lambda *_: "EMPTY", 183 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 184 exp.EndStatement: lambda *_: "END", 185 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 186 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 187 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 188 exp.EphemeralColumnConstraint: lambda self, e: ( 189 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 190 ), 191 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 192 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 193 exp.Except: lambda self, e: self.set_operations(e), 194 exp.ExternalProperty: lambda *_: "EXTERNAL", 195 exp.Floor: lambda self, e: self.ceil_floor(e), 196 exp.Get: lambda self, e: self.get_put_sql(e), 197 exp.GlobalProperty: lambda *_: "GLOBAL", 198 exp.HeapProperty: lambda *_: "HEAP", 199 exp.HybridProperty: lambda *_: "HYBRID", 200 exp.IcebergProperty: lambda *_: "ICEBERG", 201 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 202 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 203 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 204 exp.Intersect: lambda self, e: self.set_operations(e), 205 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 206 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 207 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 208 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 209 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 210 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 211 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 212 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 213 exp.LanguageProperty: lambda self, e: self.naked_property(e), 214 exp.LocationProperty: lambda self, e: self.naked_property(e), 215 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 216 exp.MaskingProperty: lambda *_: "MASKING", 217 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 218 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 219 exp.NetworkProperty: lambda *_: "NETWORK", 220 exp.NonClusteredColumnConstraint: lambda self, e: ( 221 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 222 ), 223 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 224 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 225 exp.OnCommitProperty: lambda _, e: ( 226 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 227 ), 228 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 229 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 230 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 231 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 232 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 233 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 234 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 235 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 236 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 237 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 238 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 239 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 240 f"PROJECTION POLICY {self.sql(e, 'this')}" 241 ), 242 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 243 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 244 exp.Put: lambda self, e: self.get_put_sql(e), 245 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 246 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 247 ), 248 exp.ReturnsProperty: lambda self, e: ( 249 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 250 ), 251 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 252 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 253 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 254 exp.SecureProperty: lambda *_: "SECURE", 255 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 256 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 257 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 258 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 259 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 260 exp.SqlReadWriteProperty: lambda _, e: e.name, 261 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 262 exp.StabilityProperty: lambda _, e: e.name, 263 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 264 exp.StreamingTableProperty: lambda *_: "STREAMING", 265 exp.StrictProperty: lambda *_: "STRICT", 266 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 267 exp.TableColumn: lambda self, e: self.sql(e.this), 268 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 269 exp.TemporaryProperty: lambda *_: "TEMPORARY", 270 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 271 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 272 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 273 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 274 exp.TransientProperty: lambda *_: "TRANSIENT", 275 exp.VirtualProperty: lambda *_: "VIRTUAL", 276 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 277 exp.Union: lambda self, e: self.set_operations(e), 278 exp.UnloggedProperty: lambda *_: "UNLOGGED", 279 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 280 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 281 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 282 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 283 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 284 exp.UtcTimestamp: lambda self, e: self.sql( 285 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 286 ), 287 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 288 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 289 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 290 exp.VolatileProperty: lambda *_: "VOLATILE", 291 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 292 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 293 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 294 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 295 exp.ForceProperty: lambda *_: "FORCE", 296 } 297 298 # Whether null ordering is supported in order by 299 # True: Full Support, None: No support, False: No support for certain cases 300 # such as window specifications, aggregate functions etc 301 NULL_ORDERING_SUPPORTED: bool | None = True 302 303 # Window functions that support NULLS FIRST/LAST 304 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 305 306 # Whether ignore nulls is inside the agg or outside. 307 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 308 IGNORE_NULLS_IN_FUNC = False 309 310 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 311 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 312 IGNORE_NULLS_BEFORE_ORDER = True 313 314 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 315 LOCKING_READS_SUPPORTED = False 316 317 # Whether the EXCEPT and INTERSECT operations can return duplicates 318 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 319 320 # Wrap derived values in parens, usually standard but spark doesn't support it 321 WRAP_DERIVED_VALUES = True 322 323 # Whether create function uses an AS before the RETURN 324 CREATE_FUNCTION_RETURN_AS = True 325 326 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 327 MATCHED_BY_SOURCE = True 328 329 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 330 SUPPORTS_MERGE_WHERE = False 331 332 # Whether the INTERVAL expression works only with values like '1 day' 333 SINGLE_STRING_INTERVAL = False 334 335 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 336 INTERVAL_ALLOWS_PLURAL_FORM = True 337 338 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 339 LIMIT_FETCH = "ALL" 340 341 # Whether limit and fetch allows expresions or just limits 342 LIMIT_ONLY_LITERALS = False 343 344 # Whether a table is allowed to be renamed with a db 345 RENAME_TABLE_WITH_DB = True 346 347 # The separator for grouping sets and rollups 348 GROUPINGS_SEP = "," 349 350 # The string used for creating an index on a table 351 INDEX_ON = "ON" 352 353 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 354 INOUT_SEPARATOR = " " 355 356 # Whether join hints should be generated 357 JOIN_HINTS = True 358 359 # Whether directed joins are supported 360 DIRECTED_JOINS = False 361 362 # Whether table hints should be generated 363 TABLE_HINTS = True 364 365 # Whether query hints should be generated 366 QUERY_HINTS = True 367 368 # What kind of separator to use for query hints 369 QUERY_HINT_SEP = ", " 370 371 # Whether comparing against booleans (e.g. x IS TRUE) is supported 372 IS_BOOL_ALLOWED = True 373 374 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 375 DUPLICATE_KEY_UPDATE_WITH_SET = True 376 377 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 378 LIMIT_IS_TOP = False 379 380 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 381 RETURNING_END = True 382 383 # Whether to generate an unquoted value for EXTRACT's date part argument 384 EXTRACT_ALLOWS_QUOTES = True 385 386 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 387 TZ_TO_WITH_TIME_ZONE = False 388 389 # Whether the NVL2 function is supported 390 NVL2_SUPPORTED = True 391 392 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 393 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 394 395 # Whether VALUES statements can be used as derived tables. 396 # MySQL 5 and Redshift do not allow this, so when False, it will convert 397 # SELECT * VALUES into SELECT UNION 398 VALUES_AS_TABLE = True 399 400 # Whether the word COLUMN is included when adding a column with ALTER TABLE 401 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 402 403 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 404 UNNEST_WITH_ORDINALITY = True 405 406 # Whether FILTER (WHERE cond) can be used for conditional aggregation 407 AGGREGATE_FILTER_SUPPORTED = True 408 409 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 410 SEMI_ANTI_JOIN_WITH_SIDE = True 411 412 # Whether to include the type of a computed column in the CREATE DDL 413 COMPUTED_COLUMN_WITH_TYPE = True 414 415 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 416 SUPPORTS_TABLE_COPY = True 417 418 # Whether parentheses are required around the table sample's expression 419 TABLESAMPLE_REQUIRES_PARENS = True 420 421 # Whether a table sample clause's size needs to be followed by the ROWS keyword 422 TABLESAMPLE_SIZE_IS_ROWS = True 423 424 # The keyword(s) to use when generating a sample clause 425 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 426 427 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 428 TABLESAMPLE_WITH_METHOD = True 429 430 # The keyword to use when specifying the seed of a sample clause 431 TABLESAMPLE_SEED_KEYWORD = "SEED" 432 433 # Whether COLLATE is a function instead of a binary operator 434 COLLATE_IS_FUNC = False 435 436 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 437 DATA_TYPE_SPECIFIERS_ALLOWED = False 438 439 # Whether conditions require booleans WHERE x = 0 vs WHERE x 440 ENSURE_BOOLS = False 441 442 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 443 CTE_RECURSIVE_KEYWORD_REQUIRED = True 444 445 # Whether CONCAT requires >1 arguments 446 SUPPORTS_SINGLE_ARG_CONCAT = True 447 448 # Whether LAST_DAY function supports a date part argument 449 LAST_DAY_SUPPORTS_DATE_PART = True 450 451 # Whether named columns are allowed in table aliases 452 SUPPORTS_TABLE_ALIAS_COLUMNS = True 453 454 # Whether named columns are allowed in CTE definitions 455 SUPPORTS_NAMED_CTE_COLUMNS = True 456 457 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 458 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 459 460 # What delimiter to use for separating JSON key/value pairs 461 JSON_KEY_VALUE_PAIR_SEP = ":" 462 463 # INSERT OVERWRITE TABLE x override 464 INSERT_OVERWRITE = " OVERWRITE TABLE" 465 466 # Whether the SELECT .. INTO syntax is used instead of CTAS 467 SUPPORTS_SELECT_INTO = False 468 469 # Whether UNLOGGED tables can be created 470 SUPPORTS_UNLOGGED_TABLES = False 471 472 # Whether the CREATE TABLE LIKE statement is supported 473 SUPPORTS_CREATE_TABLE_LIKE = True 474 475 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 476 SUPPORTS_MODIFY_COLUMN = False 477 478 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 479 SUPPORTS_CHANGE_COLUMN = False 480 481 # Whether the LikeProperty needs to be specified inside of the schema clause 482 LIKE_PROPERTY_INSIDE_SCHEMA = False 483 484 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 485 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 486 MULTI_ARG_DISTINCT = True 487 488 # Whether the JSON extraction operators expect a value of type JSON 489 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 490 491 # Whether bracketed keys like ["foo"] are supported in JSON paths 492 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 493 494 # Whether to escape keys using single quotes in JSON paths 495 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 496 497 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 498 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 499 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 500 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 501 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 502 503 # The JSONPathPart expressions supported by this dialect 504 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 505 506 # Whether any(f(x) for x in array) can be implemented by this dialect 507 CAN_IMPLEMENT_ARRAY_ANY = False 508 509 # Whether the function TO_NUMBER is supported 510 SUPPORTS_TO_NUMBER = True 511 512 # Whether EXCLUDE in window specification is supported 513 SUPPORTS_WINDOW_EXCLUDE = False 514 515 # Whether or not set op modifiers apply to the outer set op or select. 516 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 517 # True means limit 1 happens after the set op, False means it it happens on y. 518 SET_OP_MODIFIERS = True 519 520 # Whether parameters from COPY statement are wrapped in parentheses 521 COPY_PARAMS_ARE_WRAPPED = True 522 523 # Whether values of params are set with "=" token or empty space 524 COPY_PARAMS_EQ_REQUIRED = False 525 526 # Whether COPY statement has INTO keyword 527 COPY_HAS_INTO_KEYWORD = True 528 529 # Whether the conditional TRY(expression) function is supported 530 TRY_SUPPORTED = True 531 532 # Whether the UESCAPE syntax in unicode strings is supported 533 SUPPORTS_UESCAPE = True 534 535 # Function used to replace escaped unicode codes in unicode strings 536 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 537 538 # The keyword to use when generating a star projection with excluded columns 539 STAR_EXCEPT = "EXCEPT" 540 541 # The HEX function name 542 HEX_FUNC = "HEX" 543 544 # The keywords to use when prefixing & separating WITH based properties 545 WITH_PROPERTIES_PREFIX = "WITH" 546 547 # Whether to quote the generated expression of exp.JsonPath 548 QUOTE_JSON_PATH = True 549 550 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 551 PAD_FILL_PATTERN_IS_REQUIRED = False 552 553 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 554 SUPPORTS_EXPLODING_PROJECTIONS = True 555 556 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 557 ARRAY_CONCAT_IS_VAR_LEN = True 558 559 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 560 SUPPORTS_CONVERT_TIMEZONE = False 561 562 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 563 SUPPORTS_MEDIAN = True 564 565 # Whether UNIX_SECONDS(timestamp) is supported 566 SUPPORTS_UNIX_SECONDS = False 567 568 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 569 ALTER_SET_WRAPPED = False 570 571 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 572 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 573 # TODO: The normalization should be done by default once we've tested it across all dialects. 574 NORMALIZE_EXTRACT_DATE_PARTS = False 575 576 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 577 PARSE_JSON_NAME: str | None = "PARSE_JSON" 578 579 # The function name of the exp.ArraySize expression 580 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 581 582 # The syntax to use when altering the type of a column 583 ALTER_SET_TYPE = "SET DATA TYPE" 584 585 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 586 # None -> Doesn't support it at all 587 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 588 # True (Postgres) -> Explicitly requires it 589 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 590 591 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 592 SUPPORTS_DECODE_CASE = True 593 594 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 595 SUPPORTS_BETWEEN_FLAGS = False 596 597 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 598 SUPPORTS_LIKE_QUANTIFIERS = True 599 600 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 601 MATCH_AGAINST_TABLE_PREFIX: str | None = None 602 603 # Whether to include the VARIABLE keyword for SET assignments 604 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 605 606 # The keyword to use for default value assignment in DECLARE statements 607 DECLARE_DEFAULT_ASSIGNMENT = "=" 608 609 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 610 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 611 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 612 UPDATE_STATEMENT_SUPPORTS_FROM = True 613 614 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 615 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 616 617 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 618 # - Snowflake: DROP ICEBERG TABLE a.b; 619 # - DuckDB: DROP TABLE a.b; 620 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 621 622 TYPE_MAPPING: t.ClassVar = { 623 exp.DType.DATETIME2: "TIMESTAMP", 624 exp.DType.NCHAR: "CHAR", 625 exp.DType.NVARCHAR: "VARCHAR", 626 exp.DType.MEDIUMTEXT: "TEXT", 627 exp.DType.LONGTEXT: "TEXT", 628 exp.DType.TINYTEXT: "TEXT", 629 exp.DType.BLOB: "VARBINARY", 630 exp.DType.MEDIUMBLOB: "BLOB", 631 exp.DType.LONGBLOB: "BLOB", 632 exp.DType.TINYBLOB: "BLOB", 633 exp.DType.INET: "INET", 634 exp.DType.ROWVERSION: "VARBINARY", 635 exp.DType.SMALLDATETIME: "TIMESTAMP", 636 } 637 638 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 639 640 # mapping of DType to its default parameters, bounds 641 TYPE_PARAM_SETTINGS: t.ClassVar[ 642 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 643 ] = {} 644 645 TIME_PART_SINGULARS: t.ClassVar = { 646 "MICROSECONDS": "MICROSECOND", 647 "SECONDS": "SECOND", 648 "MINUTES": "MINUTE", 649 "HOURS": "HOUR", 650 "DAYS": "DAY", 651 "WEEKS": "WEEK", 652 "MONTHS": "MONTH", 653 "QUARTERS": "QUARTER", 654 "YEARS": "YEAR", 655 } 656 657 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 658 "cluster": lambda self, e: self.sql(e, "cluster"), 659 "distribute": lambda self, e: self.sql(e, "distribute"), 660 "sort": lambda self, e: self.sql(e, "sort"), 661 **AFTER_HAVING_MODIFIER_TRANSFORMS, 662 } 663 664 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 665 666 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 667 668 PARAMETER_TOKEN = "@" 669 NAMED_PLACEHOLDER_TOKEN = ":" 670 671 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 672 673 PROPERTIES_LOCATION: t.ClassVar = { 674 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 675 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 676 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 677 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 678 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 682 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 684 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 685 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 686 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 687 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 688 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 689 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 690 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 695 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 696 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 698 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 699 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 701 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 702 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 704 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 705 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 711 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 712 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 713 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 714 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 715 exp.HeapProperty: exp.Properties.Location.POST_WITH, 716 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 717 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 719 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 722 exp.JournalProperty: exp.Properties.Location.POST_NAME, 723 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 728 exp.LogProperty: exp.Properties.Location.POST_NAME, 729 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 730 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 731 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 732 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 734 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 735 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 737 exp.Order: exp.Properties.Location.POST_SCHEMA, 738 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 740 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 742 exp.Property: exp.Properties.Location.POST_WITH, 743 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 747 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 748 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 754 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 755 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 756 exp.Set: exp.Properties.Location.POST_SCHEMA, 757 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.SetProperty: exp.Properties.Location.POST_CREATE, 759 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 761 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 762 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 763 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 769 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.Tags: exp.Properties.Location.POST_WITH, 771 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 772 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 774 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 776 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 777 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 778 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 781 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 782 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 783 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 784 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 785 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 788 } 789 790 # Keywords that can't be used as unquoted identifier names 791 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 792 793 # Exprs whose comments are separated from them for better formatting 794 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 795 exp.Command, 796 exp.Create, 797 exp.Describe, 798 exp.Delete, 799 exp.Drop, 800 exp.From, 801 exp.Insert, 802 exp.Join, 803 exp.MultitableInserts, 804 exp.Order, 805 exp.Group, 806 exp.Having, 807 exp.Select, 808 exp.SetOperation, 809 exp.Update, 810 exp.Where, 811 exp.With, 812 ) 813 814 # Exprs that should not have their comments generated in maybe_comment 815 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 816 exp.Binary, 817 exp.SetOperation, 818 ) 819 820 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 821 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 822 exp.Column, 823 exp.Literal, 824 exp.Neg, 825 exp.Paren, 826 ) 827 828 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 829 exp.DType.NVARCHAR, 830 exp.DType.VARCHAR, 831 exp.DType.CHAR, 832 exp.DType.NCHAR, 833 } 834 835 # Exprs that need to have all CTEs under them bubbled up to them 836 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 837 838 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 839 840 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 841 842 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 843 844 __slots__ = ( 845 "pretty", 846 "identify", 847 "normalize", 848 "pad", 849 "_indent", 850 "normalize_functions", 851 "unsupported_level", 852 "max_unsupported", 853 "leading_comma", 854 "max_text_width", 855 "comments", 856 "dialect", 857 "unsupported_messages", 858 "_escaped_quote_end", 859 "_escaped_byte_quote_end", 860 "_escaped_identifier_end", 861 "_next_name", 862 "_identifier_start", 863 "_identifier_end", 864 "_quote_json_path_key_using_brackets", 865 "_dispatch", 866 ) 867 868 def __init__( 869 self, 870 pretty: bool | int | None = None, 871 identify: str | bool = False, 872 normalize: bool = False, 873 pad: int = 2, 874 indent: int = 2, 875 normalize_functions: str | bool | None = None, 876 unsupported_level: ErrorLevel = ErrorLevel.WARN, 877 max_unsupported: int = 3, 878 leading_comma: bool = False, 879 max_text_width: int = 80, 880 comments: bool = True, 881 dialect: DialectType = None, 882 ): 883 import sqlglot 884 import sqlglot.dialects.dialect 885 886 self.pretty = pretty if pretty is not None else sqlglot.pretty 887 self.identify = identify 888 self.normalize = normalize 889 self.pad = pad 890 self._indent = indent 891 self.unsupported_level = unsupported_level 892 self.max_unsupported = max_unsupported 893 self.leading_comma = leading_comma 894 self.max_text_width = max_text_width 895 self.comments = comments 896 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 897 898 # This is both a Dialect property and a Generator argument, so we prioritize the latter 899 self.normalize_functions = ( 900 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 901 ) 902 903 self.unsupported_messages: list[str] = [] 904 self._escaped_quote_end: str = ( 905 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 906 ) 907 self._escaped_byte_quote_end: str = ( 908 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 909 if self.dialect.BYTE_END 910 else "" 911 ) 912 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 913 914 self._next_name = name_sequence("_t") 915 916 self._identifier_start = self.dialect.IDENTIFIER_START 917 self._identifier_end = self.dialect.IDENTIFIER_END 918 919 self._quote_json_path_key_using_brackets = True 920 921 cls = type(self) 922 dispatch = _DISPATCH_CACHE.get(cls) 923 if dispatch is None: 924 dispatch = _build_dispatch(cls) 925 _DISPATCH_CACHE[cls] = dispatch 926 self._dispatch = dispatch 927 928 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 929 """ 930 Generates the SQL string corresponding to the given syntax tree. 931 932 Args: 933 expression: The syntax tree. 934 copy: Whether to copy the expression. The generator performs mutations so 935 it is safer to copy. 936 937 Returns: 938 The SQL string corresponding to `expression`. 939 """ 940 if copy: 941 expression = expression.copy() 942 943 expression = self.preprocess(expression) 944 945 self.unsupported_messages = [] 946 sql = self.sql(expression).strip() 947 948 if self.pretty: 949 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 950 951 if self.unsupported_level == ErrorLevel.IGNORE: 952 return sql 953 954 if self.unsupported_level == ErrorLevel.WARN: 955 for msg in self.unsupported_messages: 956 logger.warning(msg) 957 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 958 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 959 960 return sql 961 962 def preprocess(self, expression: exp.Expr) -> exp.Expr: 963 """Apply generic preprocessing transformations to a given expression.""" 964 expression = self._move_ctes_to_top_level(expression) 965 966 if self.ENSURE_BOOLS: 967 import sqlglot.transforms 968 969 expression = sqlglot.transforms.ensure_bools(expression) 970 971 return expression 972 973 def _move_ctes_to_top_level(self, expression: E) -> E: 974 if ( 975 not expression.parent 976 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 977 and any(node.parent is not expression for node in expression.find_all(exp.With)) 978 ): 979 import sqlglot.transforms 980 981 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 982 return expression 983 984 def unsupported(self, message: str) -> None: 985 if self.unsupported_level == ErrorLevel.IMMEDIATE: 986 raise UnsupportedError(message) 987 self.unsupported_messages.append(message) 988 989 def sep(self, sep: str = " ") -> str: 990 return f"{sep.strip()}\n" if self.pretty else sep 991 992 def seg(self, sql: str, sep: str = " ") -> str: 993 return f"{self.sep(sep)}{sql}" 994 995 def sanitize_comment(self, comment: str) -> str: 996 comment = " " + comment if comment[0].strip() else comment 997 comment = comment + " " if comment[-1].strip() else comment 998 999 # Escape block comment markers to prevent premature closure or unintended nesting. 1000 # This is necessary because single-line comments (--) are converted to block comments 1001 # (/* */) on output, and any */ in the original text would close the comment early. 1002 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1003 1004 return comment 1005 1006 def maybe_comment( 1007 self, 1008 sql: str, 1009 expression: exp.Expr | None = None, 1010 comments: list[str] | None = None, 1011 separated: bool = False, 1012 ) -> str: 1013 comments = ( 1014 ((expression and expression.comments) if comments is None else comments) # type: ignore 1015 if self.comments 1016 else None 1017 ) 1018 1019 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1020 return sql 1021 1022 comments_list = [ 1023 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1024 for comment in comments 1025 if comment 1026 ] 1027 1028 if not comments_list: 1029 return sql 1030 1031 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1032 comments_sql = self.sep().join(comments_list) 1033 return ( 1034 f"{self.sep()}{comments_sql}{sql}" 1035 if not sql or sql[0].isspace() 1036 else f"{comments_sql}{self.sep()}{sql}" 1037 ) 1038 1039 return f"{sql} {' '.join(comments_list)}" 1040 1041 def wrap(self, expression: exp.Expr | str) -> str: 1042 this_sql = ( 1043 self.sql(expression) 1044 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1045 else self.sql(expression, "this") 1046 ) 1047 if not this_sql: 1048 return "()" 1049 1050 this_sql = self.indent(this_sql, level=1, pad=0) 1051 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1052 1053 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1054 original = self.identify 1055 self.identify = False 1056 result = func(*args, **kwargs) 1057 self.identify = original 1058 return result 1059 1060 def normalize_func(self, name: str) -> str: 1061 if self.normalize_functions == "upper" or self.normalize_functions is True: 1062 return name.upper() 1063 if self.normalize_functions == "lower": 1064 return name.lower() 1065 return name 1066 1067 def indent( 1068 self, 1069 sql: str, 1070 level: int = 0, 1071 pad: int | None = None, 1072 skip_first: bool = False, 1073 skip_last: bool = False, 1074 ) -> str: 1075 if not self.pretty or not sql: 1076 return sql 1077 1078 pad = self.pad if pad is None else pad 1079 lines = sql.split("\n") 1080 1081 return "\n".join( 1082 ( 1083 line 1084 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1085 else f"{' ' * (level * self._indent + pad)}{line}" 1086 ) 1087 for i, line in enumerate(lines) 1088 ) 1089 1090 def sql( 1091 self, 1092 expression: str | exp.Expr | None, 1093 key: str | None = None, 1094 comment: bool = True, 1095 ) -> str: 1096 if not expression: 1097 return "" 1098 1099 if isinstance(expression, str): 1100 return expression 1101 1102 if key: 1103 value = expression.args.get(key) 1104 if value: 1105 return self.sql(value) 1106 return "" 1107 1108 handler = self._dispatch.get(expression.__class__) 1109 1110 if handler: 1111 sql = handler(self, expression) 1112 elif isinstance(expression, exp.Func): 1113 sql = self.function_fallback_sql(expression) 1114 elif isinstance(expression, exp.Property): 1115 sql = self.property_sql(expression) 1116 else: 1117 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1118 1119 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1120 1121 def uncache_sql(self, expression: exp.Uncache) -> str: 1122 table = self.sql(expression, "this") 1123 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1124 return f"UNCACHE TABLE{exists_sql} {table}" 1125 1126 def cache_sql(self, expression: exp.Cache) -> str: 1127 lazy = " LAZY" if expression.args.get("lazy") else "" 1128 table = self.sql(expression, "this") 1129 options = expression.args.get("options") 1130 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1131 sql = self.sql(expression, "expression") 1132 sql = f" AS{self.sep()}{sql}" if sql else "" 1133 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1134 return self.prepend_ctes(expression, sql) 1135 1136 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1137 default = "DEFAULT " if expression.args.get("default") else "" 1138 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1139 1140 def column_parts(self, expression: exp.Column) -> str: 1141 return ".".join( 1142 self.sql(part) 1143 for part in ( 1144 expression.args.get("catalog"), 1145 expression.args.get("db"), 1146 expression.args.get("table"), 1147 expression.args.get("this"), 1148 ) 1149 if part 1150 ) 1151 1152 def column_sql(self, expression: exp.Column) -> str: 1153 join_mark = " (+)" if expression.args.get("join_mark") else "" 1154 1155 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1156 join_mark = "" 1157 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1158 1159 return f"{self.column_parts(expression)}{join_mark}" 1160 1161 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1162 return self.column_sql(expression) 1163 1164 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1165 this = self.sql(expression, "this") 1166 this = f" {this}" if this else "" 1167 position = self.sql(expression, "position") 1168 return f"{position}{this}" 1169 1170 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1171 column = self.sql(expression, "this") 1172 kind = self.sql(expression, "kind") 1173 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1174 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1175 kind = f"{sep}{kind}" if kind else "" 1176 constraints = f" {constraints}" if constraints else "" 1177 position = self.sql(expression, "position") 1178 position = f" {position}" if position else "" 1179 1180 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1181 kind = "" 1182 1183 return f"{exists}{column}{kind}{constraints}{position}" 1184 1185 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1186 this = self.sql(expression, "this") 1187 kind_sql = self.sql(expression, "kind").strip() 1188 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1189 1190 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1191 this = self.sql(expression, "this") 1192 if expression.args.get("not_null"): 1193 persisted = " PERSISTED NOT NULL" 1194 elif expression.args.get("persisted"): 1195 persisted = " PERSISTED" 1196 else: 1197 persisted = "" 1198 1199 return f"AS {this}{persisted}" 1200 1201 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1202 return self.token_sql(TokenType.AUTO_INCREMENT) 1203 1204 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1205 if isinstance(expression.this, list): 1206 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1207 else: 1208 this = self.sql(expression, "this") 1209 1210 return f"COMPRESS {this}" 1211 1212 def generatedasidentitycolumnconstraint_sql( 1213 self, expression: exp.GeneratedAsIdentityColumnConstraint 1214 ) -> str: 1215 this = "" 1216 if expression.this is not None: 1217 on_null = " ON NULL" if expression.args.get("on_null") else "" 1218 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1219 1220 start = expression.args.get("start") 1221 start = f"START WITH {start}" if start else "" 1222 increment = expression.args.get("increment") 1223 increment = f" INCREMENT BY {increment}" if increment else "" 1224 minvalue = expression.args.get("minvalue") 1225 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1226 maxvalue = expression.args.get("maxvalue") 1227 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1228 cycle = expression.args.get("cycle") 1229 cycle_sql = "" 1230 1231 if cycle is not None: 1232 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1233 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1234 1235 sequence_opts = "" 1236 if start or increment or cycle_sql: 1237 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1238 sequence_opts = f" ({sequence_opts.strip()})" 1239 1240 expr = self.sql(expression, "expression") 1241 expr = f"({expr})" if expr else "IDENTITY" 1242 1243 return f"GENERATED{this} AS {expr}{sequence_opts}" 1244 1245 def generatedasrowcolumnconstraint_sql( 1246 self, expression: exp.GeneratedAsRowColumnConstraint 1247 ) -> str: 1248 start = "START" if expression.args.get("start") else "END" 1249 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1250 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1251 1252 def periodforsystemtimeconstraint_sql( 1253 self, expression: exp.PeriodForSystemTimeConstraint 1254 ) -> str: 1255 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1256 1257 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1258 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1259 1260 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1261 desc = expression.args.get("desc") 1262 if desc is not None: 1263 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1264 options = self.expressions(expression, key="options", flat=True, sep=" ") 1265 options = f" {options}" if options else "" 1266 return f"PRIMARY KEY{options}" 1267 1268 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1269 this = self.sql(expression, "this") 1270 this = f" {this}" if this else "" 1271 index_type = expression.args.get("index_type") 1272 index_type = f" USING {index_type}" if index_type else "" 1273 on_conflict = self.sql(expression, "on_conflict") 1274 on_conflict = f" {on_conflict}" if on_conflict else "" 1275 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1276 options = self.expressions(expression, key="options", flat=True, sep=" ") 1277 options = f" {options}" if options else "" 1278 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1279 1280 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1281 input_ = expression.args.get("input_") 1282 output = expression.args.get("output") 1283 variadic = expression.args.get("variadic") 1284 1285 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1286 if variadic: 1287 return "VARIADIC" 1288 1289 if input_ and output: 1290 return f"IN{self.INOUT_SEPARATOR}OUT" 1291 if input_: 1292 return "IN" 1293 if output: 1294 return "OUT" 1295 1296 return "" 1297 1298 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1299 return self.sql(expression, "this") 1300 1301 def create_sql(self, expression: exp.Create) -> str: 1302 kind = self.sql(expression, "kind") 1303 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1304 1305 properties = expression.args.get("properties") 1306 1307 if ( 1308 kind == "TRIGGER" 1309 and properties 1310 and properties.expressions 1311 and isinstance(properties.expressions[0], exp.TriggerProperties) 1312 and properties.expressions[0].args.get("constraint") 1313 ): 1314 kind = f"CONSTRAINT {kind}" 1315 1316 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1317 1318 this = self.createable_sql(expression, properties_locs) 1319 1320 properties_sql = "" 1321 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1322 exp.Properties.Location.POST_WITH 1323 ): 1324 props_ast = exp.Properties( 1325 expressions=[ 1326 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1327 *properties_locs[exp.Properties.Location.POST_WITH], 1328 ] 1329 ) 1330 props_ast.parent = expression 1331 properties_sql = self.sql(props_ast) 1332 1333 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1334 properties_sql = self.sep() + properties_sql 1335 elif not self.pretty: 1336 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1337 properties_sql = f" {properties_sql}" 1338 1339 begin = " BEGIN" if expression.args.get("begin") else "" 1340 1341 expression_sql = self.sql(expression, "expression") 1342 if expression_sql: 1343 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1344 1345 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1346 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1347 ): 1348 postalias_props_sql = "" 1349 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1350 postalias_props_sql = self.properties( 1351 exp.Properties( 1352 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1353 ), 1354 wrapped=False, 1355 ) 1356 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1357 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1358 1359 postindex_props_sql = "" 1360 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1361 postindex_props_sql = self.properties( 1362 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1363 wrapped=False, 1364 prefix=" ", 1365 ) 1366 1367 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1368 indexes = f" {indexes}" if indexes else "" 1369 index_sql = indexes + postindex_props_sql 1370 1371 replace = " OR REPLACE" if expression.args.get("replace") else "" 1372 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1373 unique = " UNIQUE" if expression.args.get("unique") else "" 1374 1375 clustered = expression.args.get("clustered") 1376 if clustered is None: 1377 clustered_sql = "" 1378 elif clustered: 1379 clustered_sql = " CLUSTERED COLUMNSTORE" 1380 else: 1381 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1382 1383 postcreate_props_sql = "" 1384 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1385 postcreate_props_sql = self.properties( 1386 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1387 sep=" ", 1388 prefix=" ", 1389 wrapped=False, 1390 ) 1391 1392 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1393 1394 postexpression_props_sql = "" 1395 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1396 postexpression_props_sql = self.properties( 1397 exp.Properties( 1398 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1399 ), 1400 sep=" ", 1401 prefix=" ", 1402 wrapped=False, 1403 ) 1404 1405 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1406 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1407 no_schema_binding = ( 1408 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1409 ) 1410 1411 clone = self.sql(expression, "clone") 1412 clone = f" {clone}" if clone else "" 1413 1414 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1415 properties_expression = f"{expression_sql}{properties_sql}" 1416 else: 1417 properties_expression = f"{properties_sql}{expression_sql}" 1418 1419 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1420 return self.prepend_ctes(expression, expression_sql) 1421 1422 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1423 start = self.sql(expression, "start") 1424 start = f"START WITH {start}" if start else "" 1425 increment = self.sql(expression, "increment") 1426 increment = f" INCREMENT BY {increment}" if increment else "" 1427 minvalue = self.sql(expression, "minvalue") 1428 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1429 maxvalue = self.sql(expression, "maxvalue") 1430 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1431 owned = self.sql(expression, "owned") 1432 owned = f" OWNED BY {owned}" if owned else "" 1433 1434 cache = expression.args.get("cache") 1435 if cache is None: 1436 cache_str = "" 1437 elif cache is True: 1438 cache_str = " CACHE" 1439 else: 1440 cache_str = f" CACHE {cache}" 1441 1442 options = self.expressions(expression, key="options", flat=True, sep=" ") 1443 options = f" {options}" if options else "" 1444 1445 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1446 1447 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1448 timing = expression.args.get("timing", "") 1449 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1450 timing_events = f"{timing} {events}".strip() if timing or events else "" 1451 1452 parts = [timing_events, "ON", self.sql(expression, "table")] 1453 1454 if referenced_table := expression.args.get("referenced_table"): 1455 parts.extend(["FROM", self.sql(referenced_table)]) 1456 1457 if deferrable := expression.args.get("deferrable"): 1458 parts.append(deferrable) 1459 1460 if initially := expression.args.get("initially"): 1461 parts.append(f"INITIALLY {initially}") 1462 1463 if referencing := expression.args.get("referencing"): 1464 parts.append(self.sql(referencing)) 1465 1466 if for_each := expression.args.get("for_each"): 1467 parts.append(f"FOR EACH {for_each}") 1468 1469 if when := expression.args.get("when"): 1470 parts.append(f"WHEN ({self.sql(when)})") 1471 1472 parts.append(self.sql(expression, "execute")) 1473 1474 return self.sep().join(parts) 1475 1476 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1477 parts = [] 1478 1479 if old_alias := expression.args.get("old"): 1480 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1481 1482 if new_alias := expression.args.get("new"): 1483 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1484 1485 return f"REFERENCING {' '.join(parts)}" 1486 1487 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1488 columns = expression.args.get("columns") 1489 if columns: 1490 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1491 1492 return self.sql(expression, "this") 1493 1494 def clone_sql(self, expression: exp.Clone) -> str: 1495 this = self.sql(expression, "this") 1496 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1497 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1498 return f"{shallow}{keyword} {this}" 1499 1500 def describe_sql(self, expression: exp.Describe) -> str: 1501 style = expression.args.get("style") 1502 style = f" {style}" if style else "" 1503 partition = self.sql(expression, "partition") 1504 partition = f" {partition}" if partition else "" 1505 format = self.sql(expression, "format") 1506 format = f" {format}" if format else "" 1507 as_json = " AS JSON" if expression.args.get("as_json") else "" 1508 1509 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1510 1511 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1512 tag = self.sql(expression, "tag") 1513 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1514 1515 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1516 with_ = self.sql(expression, "with_") 1517 if with_: 1518 sql = f"{with_}{self.sep()}{sql}" 1519 return sql 1520 1521 def with_sql(self, expression: exp.With) -> str: 1522 sql = self.expressions(expression, flat=True) 1523 recursive = ( 1524 "RECURSIVE " 1525 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1526 else "" 1527 ) 1528 search = self.sql(expression, "search") 1529 search = f" {search}" if search else "" 1530 1531 return f"WITH {recursive}{sql}{search}" 1532 1533 def cte_sql(self, expression: exp.CTE) -> str: 1534 alias = expression.args.get("alias") 1535 if alias: 1536 alias.add_comments(expression.pop_comments()) 1537 1538 alias_sql = self.sql(expression, "alias") 1539 1540 materialized = expression.args.get("materialized") 1541 if materialized is False: 1542 materialized = "NOT MATERIALIZED " 1543 elif materialized: 1544 materialized = "MATERIALIZED " 1545 1546 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1547 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1548 1549 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1550 1551 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1552 alias = self.sql(expression, "this") 1553 columns = self.expressions(expression, key="columns", flat=True) 1554 columns = f"({columns})" if columns else "" 1555 1556 if ( 1557 columns 1558 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1559 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1560 ): 1561 columns = "" 1562 self.unsupported("Named columns are not supported in table alias.") 1563 1564 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1565 alias = self._next_name() 1566 1567 return f"{alias}{columns}" 1568 1569 def bitstring_sql(self, expression: exp.BitString) -> str: 1570 this = self.sql(expression, "this") 1571 if self.dialect.BIT_START: 1572 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1573 return f"{int(this, 2)}" 1574 1575 def hexstring_sql( 1576 self, expression: exp.HexString, binary_function_repr: str | None = None 1577 ) -> str: 1578 this = self.sql(expression, "this") 1579 is_integer_type = expression.args.get("is_integer") 1580 1581 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1582 not self.dialect.HEX_START and not binary_function_repr 1583 ): 1584 # Integer representation will be returned if: 1585 # - The read dialect treats the hex value as integer literal but not the write 1586 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1587 return f"{int(this, 16)}" 1588 1589 if not is_integer_type: 1590 # Read dialect treats the hex value as BINARY/BLOB 1591 if binary_function_repr: 1592 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1593 return self.func(binary_function_repr, exp.Literal.string(this)) 1594 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1595 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1596 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1597 1598 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1599 1600 def bytestring_sql(self, expression: exp.ByteString) -> str: 1601 this = self.sql(expression, "this") 1602 if self.dialect.BYTE_START: 1603 escaped_byte_string = self.escape_str( 1604 this, 1605 escape_backslash=False, 1606 delimiter=self.dialect.BYTE_END, 1607 escaped_delimiter=self._escaped_byte_quote_end, 1608 is_byte_string=True, 1609 ) 1610 is_bytes = expression.args.get("is_bytes", False) 1611 delimited_byte_string = ( 1612 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1613 ) 1614 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1615 return self.sql( 1616 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1617 ) 1618 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1619 return self.sql( 1620 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1621 ) 1622 1623 return delimited_byte_string 1624 1625 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1626 return self.sql(exp.Literal.string(this)) 1627 1628 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1629 return "" 1630 1631 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1632 this = self.sql(expression, "this") 1633 escape = expression.args.get("escape") 1634 1635 if self.dialect.UNICODE_START: 1636 escape_substitute = r"\\\1" 1637 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1638 else: 1639 escape_substitute = r"\\u\1" 1640 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1641 1642 if escape: 1643 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1644 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1645 else: 1646 escape_pattern = ESCAPED_UNICODE_RE 1647 escape_sql = "" 1648 1649 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1650 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1651 1652 return f"{left_quote}{this}{right_quote}{escape_sql}" 1653 1654 def rawstring_sql(self, expression: exp.RawString) -> str: 1655 string = expression.this 1656 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1657 string = string.replace("\\", "\\\\") 1658 1659 string = self.escape_str(string, escape_backslash=False) 1660 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1661 1662 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1663 this = self.sql(expression, "this") 1664 specifier = self.sql(expression, "expression") 1665 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1666 return f"{this}{specifier}" 1667 1668 def datatype_param_bound_limiter( 1669 self, 1670 expression: exp.DataType, 1671 type_value: exp.DType, 1672 defaults: tuple[int, ...], 1673 bounds: tuple[int | None, ...], 1674 ) -> exp.DataType: 1675 params = expression.expressions 1676 1677 if not params: 1678 if defaults: 1679 expression.set( 1680 "expressions", 1681 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1682 ) 1683 return expression 1684 1685 if not bounds: 1686 return expression 1687 1688 for i, param in enumerate(params): 1689 bound = bounds[i] if i < len(bounds) else None 1690 if bound is None: 1691 continue 1692 1693 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1694 if ( 1695 isinstance(param_value, exp.Literal) 1696 and param_value.is_number 1697 and int(param_value.to_py()) > bound 1698 ): 1699 self.unsupported( 1700 f"{type_value.value} parameter {param_value.name} exceeds " 1701 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1702 ) 1703 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1704 1705 return expression 1706 1707 def datatype_sql(self, expression: exp.DataType) -> str: 1708 nested = "" 1709 values = "" 1710 1711 expr_nested = expression.args.get("nested") 1712 type_value = expression.this 1713 1714 if ( 1715 not expr_nested 1716 and isinstance(type_value, exp.DType) 1717 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1718 ): 1719 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1720 1721 interior = ( 1722 self.expressions( 1723 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1724 ) 1725 if expr_nested and self.pretty 1726 else self.expressions(expression, flat=True) 1727 ) 1728 1729 if type_value in self.UNSUPPORTED_TYPES: 1730 self.unsupported( 1731 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1732 ) 1733 1734 type_sql: t.Any = "" 1735 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1736 type_sql = self.sql(expression, "kind") 1737 elif type_value == exp.DType.CHARACTER_SET: 1738 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1739 else: 1740 type_sql = ( 1741 self.TYPE_MAPPING.get(type_value, type_value.value) 1742 if isinstance(type_value, exp.DType) 1743 else type_value 1744 ) 1745 1746 if interior: 1747 if expr_nested: 1748 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1749 if expression.args.get("values") is not None: 1750 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1751 values = self.expressions(expression, key="values", flat=True) 1752 values = f"{delimiters[0]}{values}{delimiters[1]}" 1753 elif type_value == exp.DType.INTERVAL: 1754 nested = f" {interior}" 1755 else: 1756 nested = f"({interior})" 1757 1758 type_sql = f"{type_sql}{nested}{values}" 1759 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1760 exp.DType.TIMETZ, 1761 exp.DType.TIMESTAMPTZ, 1762 ): 1763 type_sql = f"{type_sql} WITH TIME ZONE" 1764 1765 collate = self.sql(expression, "collate") 1766 if collate: 1767 type_sql = f"{type_sql} COLLATE {collate}" 1768 1769 return type_sql 1770 1771 def directory_sql(self, expression: exp.Directory) -> str: 1772 local = "LOCAL " if expression.args.get("local") else "" 1773 row_format = self.sql(expression, "row_format") 1774 row_format = f" {row_format}" if row_format else "" 1775 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1776 1777 def delete_sql(self, expression: exp.Delete) -> str: 1778 hint = self.sql(expression, "hint") 1779 this = self.sql(expression, "this") 1780 this = f" FROM {this}" if this else "" 1781 using = self.expressions(expression, key="using") 1782 using = f" USING {using}" if using else "" 1783 cluster = self.sql(expression, "cluster") 1784 cluster = f" {cluster}" if cluster else "" 1785 where = self.sql(expression, "where") 1786 returning = self.sql(expression, "returning") 1787 order = self.sql(expression, "order") 1788 limit = self.sql(expression, "limit") 1789 tables = self.expressions(expression, key="tables") 1790 tables = f" {tables}" if tables else "" 1791 if self.RETURNING_END: 1792 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1793 else: 1794 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1795 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1796 1797 def drop_sql(self, expression: exp.Drop) -> str: 1798 this = self.sql(expression, "this") 1799 expressions = self.expressions(expression, flat=True) 1800 expressions = f" ({expressions})" if expressions else "" 1801 kind = expression.args["kind"] 1802 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1803 iceberg = ( 1804 " ICEBERG" 1805 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1806 else "" 1807 ) 1808 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1809 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1810 on_cluster = self.sql(expression, "cluster") 1811 on_cluster = f" {on_cluster}" if on_cluster else "" 1812 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1813 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1814 cascade = " CASCADE" if expression.args.get("cascade") else "" 1815 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1816 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1817 purge = " PURGE" if expression.args.get("purge") else "" 1818 sync = " SYNC" if expression.args.get("sync") else "" 1819 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1820 1821 def set_operation(self, expression: exp.SetOperation) -> str: 1822 op_type = type(expression) 1823 op_name = op_type.key.upper() 1824 1825 distinct = expression.args.get("distinct") 1826 if ( 1827 distinct is False 1828 and op_type in (exp.Except, exp.Intersect) 1829 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1830 ): 1831 self.unsupported(f"{op_name} ALL is not supported") 1832 1833 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1834 1835 if distinct is None: 1836 distinct = default_distinct 1837 if distinct is None: 1838 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1839 1840 if distinct is default_distinct: 1841 distinct_or_all = "" 1842 else: 1843 distinct_or_all = " DISTINCT" if distinct else " ALL" 1844 1845 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1846 side_kind = f"{side_kind} " if side_kind else "" 1847 1848 by_name = " BY NAME" if expression.args.get("by_name") else "" 1849 on = self.expressions(expression, key="on", flat=True) 1850 on = f" ON ({on})" if on else "" 1851 1852 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1853 1854 def set_operations(self, expression: exp.SetOperation) -> str: 1855 if not self.SET_OP_MODIFIERS: 1856 limit = expression.args.get("limit") 1857 order = expression.args.get("order") 1858 1859 if limit or order: 1860 select = self._move_ctes_to_top_level( 1861 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1862 ) 1863 1864 if limit: 1865 select = select.limit(limit.pop(), copy=False) 1866 if order: 1867 select = select.order_by(order.pop(), copy=False) 1868 return self.sql(select) 1869 1870 sqls: list[str] = [] 1871 stack: list[str | exp.Expr] = [expression] 1872 1873 while stack: 1874 node = stack.pop() 1875 1876 if isinstance(node, exp.SetOperation): 1877 stack.append(node.expression) 1878 stack.append( 1879 self.maybe_comment( 1880 self.set_operation(node), comments=node.comments, separated=True 1881 ) 1882 ) 1883 stack.append(node.this) 1884 else: 1885 sqls.append(self.sql(node)) 1886 1887 this = self.sep().join(sqls) 1888 this = self.query_modifiers(expression, this) 1889 return self.prepend_ctes(expression, this) 1890 1891 def fetch_sql(self, expression: exp.Fetch) -> str: 1892 direction = expression.args.get("direction") 1893 direction = f" {direction}" if direction else "" 1894 count = self.sql(expression, "count") 1895 count = f" {count}" if count else "" 1896 limit_options = self.sql(expression, "limit_options") 1897 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1898 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1899 1900 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1901 percent = " PERCENT" if expression.args.get("percent") else "" 1902 rows = " ROWS" if expression.args.get("rows") else "" 1903 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1904 if not with_ties and rows: 1905 with_ties = " ONLY" 1906 return f"{percent}{rows}{with_ties}" 1907 1908 def filter_sql(self, expression: exp.Filter) -> str: 1909 if self.AGGREGATE_FILTER_SUPPORTED: 1910 this = self.sql(expression, "this") 1911 where = self.sql(expression, "expression").strip() 1912 return f"{this} FILTER({where})" 1913 1914 agg = expression.this 1915 agg_arg = agg.this 1916 cond = expression.expression.this 1917 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1918 return self.sql(agg) 1919 1920 def hint_sql(self, expression: exp.Hint) -> str: 1921 if not self.QUERY_HINTS: 1922 self.unsupported("Hints are not supported") 1923 return "" 1924 1925 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1926 1927 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1928 using = self.sql(expression, "using") 1929 using = f" USING {using}" if using else "" 1930 columns = self.expressions(expression, key="columns", flat=True) 1931 columns = f"({columns})" if columns else "" 1932 partition_by = self.expressions(expression, key="partition_by", flat=True) 1933 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1934 where = self.sql(expression, "where") 1935 include = self.expressions(expression, key="include", flat=True) 1936 if include: 1937 include = f" INCLUDE ({include})" 1938 with_storage = self.expressions(expression, key="with_storage", flat=True) 1939 with_storage = f" WITH ({with_storage})" if with_storage else "" 1940 tablespace = self.sql(expression, "tablespace") 1941 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1942 on = self.sql(expression, "on") 1943 on = f" ON {on}" if on else "" 1944 1945 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1946 1947 def index_sql(self, expression: exp.Index) -> str: 1948 unique = "UNIQUE " if expression.args.get("unique") else "" 1949 primary = "PRIMARY " if expression.args.get("primary") else "" 1950 amp = "AMP " if expression.args.get("amp") else "" 1951 name = self.sql(expression, "this") 1952 name = f"{name} " if name else "" 1953 table = self.sql(expression, "table") 1954 table = f"{self.INDEX_ON} {table}" if table else "" 1955 1956 index = "INDEX " if not table else "" 1957 1958 params = self.sql(expression, "params") 1959 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1960 1961 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1962 this = expression.this 1963 if this and this.is_string: 1964 resolved = maybe_parse(this.name).sql(self.dialect) 1965 if "expressions" in expression.args: 1966 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1967 # We can't safely emit the call to other dialects since name/arg semantics may differ 1968 self.unsupported( 1969 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1970 ) 1971 return resolved 1972 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1973 return self.func("IDENTIFIER", this) 1974 1975 def identifier_sql(self, expression: exp.Identifier) -> str: 1976 text = expression.name 1977 lower = text.lower() 1978 quoted = expression.quoted 1979 text = lower if self.normalize and not quoted else text 1980 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1981 if ( 1982 quoted 1983 or self.dialect.can_quote(expression, self.identify) 1984 or lower in self.RESERVED_KEYWORDS 1985 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1986 ): 1987 text = ( 1988 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1989 ) 1990 return text 1991 1992 def hex_sql(self, expression: exp.Hex) -> str: 1993 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1994 if self.dialect.HEX_LOWERCASE: 1995 text = self.func("LOWER", text) 1996 1997 return text 1998 1999 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2000 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2001 if not self.dialect.HEX_LOWERCASE: 2002 text = self.func("LOWER", text) 2003 return text 2004 2005 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2006 input_format = self.sql(expression, "input_format") 2007 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2008 output_format = self.sql(expression, "output_format") 2009 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2010 return self.sep().join((input_format, output_format)) 2011 2012 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2013 string = self.sql(exp.Literal.string(expression.name)) 2014 return f"{prefix}{string}" 2015 2016 def partition_sql(self, expression: exp.Partition) -> str: 2017 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2018 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2019 2020 def properties_sql(self, expression: exp.Properties) -> str: 2021 root_properties = [] 2022 with_properties = [] 2023 2024 for p in expression.expressions: 2025 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2026 if p_loc == exp.Properties.Location.POST_WITH: 2027 with_properties.append(p) 2028 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2029 root_properties.append(p) 2030 2031 root_props_ast = exp.Properties(expressions=root_properties) 2032 root_props_ast.parent = expression.parent 2033 2034 with_props_ast = exp.Properties(expressions=with_properties) 2035 with_props_ast.parent = expression.parent 2036 2037 root_props = self.root_properties(root_props_ast) 2038 with_props = self.with_properties(with_props_ast) 2039 2040 if root_props and with_props and not self.pretty: 2041 with_props = " " + with_props 2042 2043 return root_props + with_props 2044 2045 def root_properties(self, properties: exp.Properties) -> str: 2046 if properties.expressions: 2047 return self.expressions(properties, indent=False, sep=" ") 2048 return "" 2049 2050 def properties( 2051 self, 2052 properties: exp.Properties, 2053 prefix: str = "", 2054 sep: str = ", ", 2055 suffix: str = "", 2056 wrapped: bool = True, 2057 ) -> str: 2058 if properties.expressions: 2059 expressions = self.expressions(properties, sep=sep, indent=False) 2060 if expressions: 2061 expressions = self.wrap(expressions) if wrapped else expressions 2062 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2063 return "" 2064 2065 def with_properties(self, properties: exp.Properties) -> str: 2066 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2067 2068 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2069 properties_locs = defaultdict(list) 2070 for p in properties.expressions: 2071 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2072 if p_loc != exp.Properties.Location.UNSUPPORTED: 2073 properties_locs[p_loc].append(p) 2074 else: 2075 self.unsupported(f"Unsupported property {p.key}") 2076 2077 return properties_locs 2078 2079 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2080 if isinstance(expression.this, exp.Dot): 2081 return self.sql(expression, "this") 2082 return f"'{expression.name}'" if string_key else expression.name 2083 2084 def property_sql(self, expression: exp.Property) -> str: 2085 property_cls = expression.__class__ 2086 if property_cls == exp.Property: 2087 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2088 2089 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2090 if not property_name: 2091 self.unsupported(f"Unsupported property {expression.key}") 2092 2093 return f"{property_name}={self.sql(expression, 'this')}" 2094 2095 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2096 return f"UUID {self.sql(expression, 'this')}" 2097 2098 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2099 if self.SUPPORTS_CREATE_TABLE_LIKE: 2100 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2101 options = f" {options}" if options else "" 2102 2103 like = f"LIKE {self.sql(expression, 'this')}{options}" 2104 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2105 like = f"({like})" 2106 2107 return like 2108 2109 if expression.expressions: 2110 self.unsupported("Transpilation of LIKE property options is unsupported") 2111 2112 select = exp.select("*").from_(expression.this).limit(0) 2113 return f"AS {self.sql(select)}" 2114 2115 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2116 no = "NO " if expression.args.get("no") else "" 2117 protection = " PROTECTION" if expression.args.get("protection") else "" 2118 return f"{no}FALLBACK{protection}" 2119 2120 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2121 no = "NO " if expression.args.get("no") else "" 2122 local = expression.args.get("local") 2123 local = f"{local} " if local else "" 2124 dual = "DUAL " if expression.args.get("dual") else "" 2125 before = "BEFORE " if expression.args.get("before") else "" 2126 after = "AFTER " if expression.args.get("after") else "" 2127 return f"{no}{local}{dual}{before}{after}JOURNAL" 2128 2129 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2130 freespace = self.sql(expression, "this") 2131 percent = " PERCENT" if expression.args.get("percent") else "" 2132 return f"FREESPACE={freespace}{percent}" 2133 2134 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2135 if expression.args.get("default"): 2136 property = "DEFAULT" 2137 elif expression.args.get("on"): 2138 property = "ON" 2139 else: 2140 property = "OFF" 2141 return f"CHECKSUM={property}" 2142 2143 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2144 if expression.args.get("no"): 2145 return "NO MERGEBLOCKRATIO" 2146 if expression.args.get("default"): 2147 return "DEFAULT MERGEBLOCKRATIO" 2148 2149 percent = " PERCENT" if expression.args.get("percent") else "" 2150 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2151 2152 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2153 expressions = self.expressions(expression, flat=True) 2154 expressions = f"({expressions})" if expressions else "" 2155 return f"USING {self.sql(expression, 'this')}{expressions}" 2156 2157 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2158 default = expression.args.get("default") 2159 minimum = expression.args.get("minimum") 2160 maximum = expression.args.get("maximum") 2161 if default or minimum or maximum: 2162 if default: 2163 prop = "DEFAULT" 2164 elif minimum: 2165 prop = "MINIMUM" 2166 else: 2167 prop = "MAXIMUM" 2168 return f"{prop} DATABLOCKSIZE" 2169 units = expression.args.get("units") 2170 units = f" {units}" if units else "" 2171 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2172 2173 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2174 autotemp = expression.args.get("autotemp") 2175 always = expression.args.get("always") 2176 default = expression.args.get("default") 2177 manual = expression.args.get("manual") 2178 never = expression.args.get("never") 2179 2180 if autotemp is not None: 2181 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2182 elif always: 2183 prop = "ALWAYS" 2184 elif default: 2185 prop = "DEFAULT" 2186 elif manual: 2187 prop = "MANUAL" 2188 elif never: 2189 prop = "NEVER" 2190 return f"BLOCKCOMPRESSION={prop}" 2191 2192 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2193 no = expression.args.get("no") 2194 no = " NO" if no else "" 2195 concurrent = expression.args.get("concurrent") 2196 concurrent = " CONCURRENT" if concurrent else "" 2197 target = self.sql(expression, "target") 2198 target = f" {target}" if target else "" 2199 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2200 2201 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2202 if isinstance(expression.this, list): 2203 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2204 if expression.this: 2205 modulus = self.sql(expression, "this") 2206 remainder = self.sql(expression, "expression") 2207 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2208 2209 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2210 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2211 return f"FROM ({from_expressions}) TO ({to_expressions})" 2212 2213 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2214 this = self.sql(expression, "this") 2215 2216 for_values_or_default = expression.expression 2217 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2218 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2219 else: 2220 for_values_or_default = " DEFAULT" 2221 2222 return f"PARTITION OF {this}{for_values_or_default}" 2223 2224 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2225 kind = expression.args.get("kind") 2226 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2227 for_or_in = expression.args.get("for_or_in") 2228 for_or_in = f" {for_or_in}" if for_or_in else "" 2229 lock_type = expression.args.get("lock_type") 2230 override = " OVERRIDE" if expression.args.get("override") else "" 2231 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2232 2233 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2234 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2235 statistics = expression.args.get("statistics") 2236 statistics_sql = "" 2237 if statistics is not None: 2238 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2239 return f"{data_sql}{statistics_sql}" 2240 2241 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2242 this = self.sql(expression, "this") 2243 this = f"HISTORY_TABLE={this}" if this else "" 2244 data_consistency: str | None = self.sql(expression, "data_consistency") 2245 data_consistency = ( 2246 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2247 ) 2248 retention_period: str | None = self.sql(expression, "retention_period") 2249 retention_period = ( 2250 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2251 ) 2252 2253 if this: 2254 on_sql = self.func("ON", this, data_consistency, retention_period) 2255 else: 2256 on_sql = "ON" if expression.args.get("on") else "OFF" 2257 2258 sql = f"SYSTEM_VERSIONING={on_sql}" 2259 2260 return f"WITH({sql})" if expression.args.get("with_") else sql 2261 2262 def insert_sql(self, expression: exp.Insert) -> str: 2263 hint = self.sql(expression, "hint") 2264 overwrite = expression.args.get("overwrite") 2265 2266 if isinstance(expression.this, exp.Directory): 2267 this = " OVERWRITE" if overwrite else " INTO" 2268 else: 2269 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2270 2271 stored = self.sql(expression, "stored") 2272 stored = f" {stored}" if stored else "" 2273 alternative = expression.args.get("alternative") 2274 alternative = f" OR {alternative}" if alternative else "" 2275 ignore = " IGNORE" if expression.args.get("ignore") else "" 2276 is_function = expression.args.get("is_function") 2277 if is_function: 2278 this = f"{this} FUNCTION" 2279 this = f"{this} {self.sql(expression, 'this')}" 2280 2281 exists = " IF EXISTS" if expression.args.get("exists") else "" 2282 where = self.sql(expression, "where") 2283 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2284 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2285 on_conflict = self.sql(expression, "conflict") 2286 on_conflict = f" {on_conflict}" if on_conflict else "" 2287 by_name = " BY NAME" if expression.args.get("by_name") else "" 2288 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2289 returning = self.sql(expression, "returning") 2290 2291 if self.RETURNING_END: 2292 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2293 else: 2294 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2295 2296 partition_by = self.sql(expression, "partition") 2297 partition_by = f" {partition_by}" if partition_by else "" 2298 settings = self.sql(expression, "settings") 2299 settings = f" {settings}" if settings else "" 2300 2301 source = self.sql(expression, "source") 2302 source = f"TABLE {source}" if source else "" 2303 2304 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2305 return self.prepend_ctes(expression, sql) 2306 2307 def introducer_sql(self, expression: exp.Introducer) -> str: 2308 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2309 2310 def kill_sql(self, expression: exp.Kill) -> str: 2311 kind = self.sql(expression, "kind") 2312 kind = f" {kind}" if kind else "" 2313 this = self.sql(expression, "this") 2314 this = f" {this}" if this else "" 2315 return f"KILL{kind}{this}" 2316 2317 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2318 return expression.name 2319 2320 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2321 return expression.name 2322 2323 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2324 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2325 2326 constraint = self.sql(expression, "constraint") 2327 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2328 2329 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2330 if conflict_keys: 2331 conflict_keys = f"({conflict_keys})" 2332 2333 index_predicate = self.sql(expression, "index_predicate") 2334 conflict_keys = f"{conflict_keys}{index_predicate} " 2335 2336 action = self.sql(expression, "action") 2337 2338 expressions = self.expressions(expression, flat=True) 2339 if expressions: 2340 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2341 expressions = f" {set_keyword}{expressions}" 2342 2343 where = self.sql(expression, "where") 2344 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2345 2346 def returning_sql(self, expression: exp.Returning) -> str: 2347 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2348 2349 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2350 fields = self.sql(expression, "fields") 2351 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2352 escaped = self.sql(expression, "escaped") 2353 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2354 items = self.sql(expression, "collection_items") 2355 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2356 keys = self.sql(expression, "map_keys") 2357 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2358 lines = self.sql(expression, "lines") 2359 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2360 null = self.sql(expression, "null") 2361 null = f" NULL DEFINED AS {null}" if null else "" 2362 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2363 2364 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2365 return f"WITH ({self.expressions(expression, flat=True)})" 2366 2367 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2368 this = f"{self.sql(expression, 'this')} INDEX" 2369 target = self.sql(expression, "target") 2370 target = f" FOR {target}" if target else "" 2371 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2372 2373 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2374 this = self.sql(expression, "this") 2375 kind = self.sql(expression, "kind") 2376 expr = self.sql(expression, "expression") 2377 return f"{this} ({kind} => {expr})" 2378 2379 def table_parts(self, expression: exp.Table) -> str: 2380 return ".".join( 2381 self.sql(part) 2382 for part in ( 2383 expression.args.get("catalog"), 2384 expression.args.get("db"), 2385 expression.args.get("this"), 2386 ) 2387 if part is not None 2388 ) 2389 2390 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2391 table = self.table_parts(expression) 2392 only = "ONLY " if expression.args.get("only") else "" 2393 partition = self.sql(expression, "partition") 2394 partition = f" {partition}" if partition else "" 2395 version = self.sql(expression, "version") 2396 version = f" {version}" if version else "" 2397 alias = self.sql(expression, "alias") 2398 alias = f"{sep}{alias}" if alias else "" 2399 2400 sample = self.sql(expression, "sample") 2401 post_alias = "" 2402 pre_alias = "" 2403 2404 if self.dialect.ALIAS_POST_TABLESAMPLE: 2405 pre_alias = sample 2406 else: 2407 post_alias = sample 2408 2409 if self.dialect.ALIAS_POST_VERSION: 2410 pre_alias = f"{pre_alias}{version}" 2411 else: 2412 post_alias = f"{post_alias}{version}" 2413 2414 hints = self.expressions(expression, key="hints", sep=" ") 2415 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2416 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2417 joins = self.indent( 2418 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2419 ) 2420 laterals = self.expressions(expression, key="laterals", sep="") 2421 2422 file_format = self.sql(expression, "format") 2423 pattern = self.sql(expression, "pattern") 2424 if file_format: 2425 pattern = f", PATTERN => {pattern}" if pattern else "" 2426 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2427 elif pattern: 2428 file_format = f" (PATTERN => {pattern})" 2429 2430 ordinality = expression.args.get("ordinality") or "" 2431 if ordinality: 2432 ordinality = f" WITH ORDINALITY{alias}" 2433 alias = "" 2434 2435 when = self.sql(expression, "when") 2436 if when: 2437 table = f"{table} {when}" 2438 2439 changes = self.sql(expression, "changes") 2440 changes = f" {changes}" if changes else "" 2441 2442 rows_from = self.expressions(expression, key="rows_from") 2443 if rows_from: 2444 table = f"ROWS FROM {self.wrap(rows_from)}" 2445 2446 indexed = expression.args.get("indexed") 2447 if indexed is not None: 2448 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2449 else: 2450 indexed = "" 2451 2452 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2453 2454 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2455 table = self.func("TABLE", expression.this) 2456 alias = self.sql(expression, "alias") 2457 alias = f" AS {alias}" if alias else "" 2458 sample = self.sql(expression, "sample") 2459 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2460 joins = self.indent( 2461 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2462 ) 2463 return f"{table}{alias}{pivots}{sample}{joins}" 2464 2465 def tablesample_sql( 2466 self, 2467 expression: exp.TableSample, 2468 tablesample_keyword: str | None = None, 2469 ) -> str: 2470 method = self.sql(expression, "method") 2471 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2472 numerator = self.sql(expression, "bucket_numerator") 2473 denominator = self.sql(expression, "bucket_denominator") 2474 field = self.sql(expression, "bucket_field") 2475 field = f" ON {field}" if field else "" 2476 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2477 seed = self.sql(expression, "seed") 2478 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2479 2480 size = self.sql(expression, "size") 2481 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2482 size = f"{size} ROWS" 2483 2484 percent = self.sql(expression, "percent") 2485 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2486 percent = f"{percent} PERCENT" 2487 2488 expr = f"{bucket}{percent}{size}" 2489 if self.TABLESAMPLE_REQUIRES_PARENS: 2490 expr = f"({expr})" 2491 2492 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2493 2494 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2495 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2496 # the stored column name differs from the target dialect's natural output. 2497 columns = expression.args.get("columns") 2498 if not columns or len(expression.fields) != 1: 2499 return None 2500 2501 args = expression.args 2502 parser_cls = self.dialect.parser_class 2503 2504 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2505 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2506 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2507 2508 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2509 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2510 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2511 2512 if ( 2513 src_identify_pivot_strings == tgt_identify_pivot_strings 2514 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2515 and src_pivot_column_naming == tgt_pivot_column_naming 2516 ): 2517 return None 2518 2519 in_exprs = expression.fields[0].expressions 2520 step = len(columns) // len(in_exprs) 2521 2522 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2523 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2524 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2525 first_stored = columns[0].name 2526 2527 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2528 if not first_stored.startswith(first_base): 2529 return None 2530 2531 suffix = first_stored[len(first_base) :] 2532 2533 # Whether the target dialect would append an agg-name suffix for this pivot. 2534 # Spark single-agg uniquely drops the agg alias entirely. 2535 target_has_suffix = ( 2536 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2537 ) and any(a.alias for a in expression.expressions) 2538 source_has_suffix = suffix != "" 2539 2540 new_exprs: list[exp.Expression] = [] 2541 modified = False 2542 for val_idx, e in enumerate(in_exprs): 2543 if isinstance(e, exp.PivotAlias): 2544 new_exprs.append(e) 2545 continue 2546 2547 i = val_idx * step 2548 stored_full = columns[i].name 2549 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2550 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2551 2552 # Source had a suffix, but target won't apply one 2553 if source_has_suffix and not target_has_suffix: 2554 new_exprs.append( 2555 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2556 ) 2557 modified = True 2558 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2559 elif stored_value != target_value: 2560 new_exprs.append( 2561 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2562 ) 2563 modified = True 2564 else: 2565 new_exprs.append(e) 2566 2567 return new_exprs if modified else None 2568 2569 def pivot_sql(self, expression: exp.Pivot) -> str: 2570 expressions = self.expressions(expression, flat=True) 2571 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2572 2573 group = self.sql(expression, "group") 2574 2575 if expression.this: 2576 this = self.sql(expression, "this") 2577 if not expressions: 2578 sql = f"UNPIVOT {this}" 2579 else: 2580 on = f"{self.seg('ON')} {expressions}" 2581 into = self.sql(expression, "into") 2582 into = f"{self.seg('INTO')} {into}" if into else "" 2583 using = self.expressions(expression, key="using", flat=True) 2584 using = f"{self.seg('USING')} {using}" if using else "" 2585 sql = f"{direction} {this}{on}{into}{using}{group}" 2586 return self.prepend_ctes(expression, sql) 2587 2588 if not expression.unpivot: 2589 # Wrap IN-list values with explicit aliases where the target dialect would differ 2590 new_field_exprs = self._pivot_in_value_aliases(expression) 2591 if new_field_exprs is not None: 2592 expression.fields[0].set("expressions", new_field_exprs) 2593 2594 alias = self.sql(expression, "alias") 2595 alias = f" AS {alias}" if alias else "" 2596 2597 fields = self.expressions( 2598 expression, 2599 "fields", 2600 sep=" ", 2601 dynamic=True, 2602 new_line=True, 2603 skip_first=True, 2604 skip_last=True, 2605 ) 2606 2607 include_nulls = expression.args.get("include_nulls") 2608 if include_nulls is not None: 2609 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2610 else: 2611 nulls = "" 2612 2613 default_on_null = self.sql(expression, "default_on_null") 2614 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2615 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2616 return self.prepend_ctes(expression, sql) 2617 2618 def version_sql(self, expression: exp.Version) -> str: 2619 this = f"FOR {expression.name}" 2620 kind = expression.text("kind") 2621 expr = self.sql(expression, "expression") 2622 return f"{this} {kind} {expr}" 2623 2624 def tuple_sql(self, expression: exp.Tuple) -> str: 2625 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2626 2627 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2628 """ 2629 Returns (join_sql, from_sql) for UPDATE statements. 2630 - join_sql: placed after UPDATE table, before SET 2631 - from_sql: placed after SET clause (standard position) 2632 Dialects like MySQL need to convert FROM to JOIN syntax. 2633 """ 2634 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2635 return ("", self.sql(expression, "from_")) 2636 2637 # Qualify unqualified columns in SET clause with the target table 2638 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2639 target_table = expression.this 2640 if isinstance(target_table, exp.Table): 2641 target_name = exp.to_identifier(target_table.alias_or_name) 2642 for eq in expression.expressions: 2643 col = eq.this 2644 if isinstance(col, exp.Column) and not col.table: 2645 col.set("table", target_name) 2646 2647 table = from_expr.this 2648 if nested_joins := table.args.get("joins", []): 2649 table.set("joins", None) 2650 2651 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2652 for nested in nested_joins: 2653 if not nested.args.get("on") and not nested.args.get("using"): 2654 nested.set("on", exp.true()) 2655 join_sql += self.sql(nested) 2656 2657 return (join_sql, "") 2658 2659 def update_sql(self, expression: exp.Update) -> str: 2660 hint = self.sql(expression, "hint") 2661 this = self.sql(expression, "this") 2662 join_sql, from_sql = self._update_from_joins_sql(expression) 2663 set_sql = self.expressions(expression, flat=True) 2664 where_sql = self.sql(expression, "where") 2665 returning = self.sql(expression, "returning") 2666 order = self.sql(expression, "order") 2667 limit = self.sql(expression, "limit") 2668 if self.RETURNING_END: 2669 expression_sql = f"{from_sql}{where_sql}{returning}" 2670 else: 2671 expression_sql = f"{returning}{from_sql}{where_sql}" 2672 options = self.expressions(expression, key="options") 2673 options = f" OPTION({options})" if options else "" 2674 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2675 return self.prepend_ctes(expression, sql) 2676 2677 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2678 values_as_table = values_as_table and self.VALUES_AS_TABLE 2679 2680 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2681 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2682 args = self.expressions(expression) 2683 alias = self.sql(expression, "alias") 2684 values = f"VALUES{self.seg('')}{args}" 2685 values = ( 2686 f"({values})" 2687 if self.WRAP_DERIVED_VALUES 2688 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2689 else values 2690 ) 2691 values = self.query_modifiers(expression, values) 2692 return f"{values} AS {alias}" if alias else values 2693 2694 # Converts `VALUES...` expression into a series of select unions. 2695 alias_node = expression.args.get("alias") 2696 column_names = alias_node and alias_node.columns 2697 2698 selects: list[exp.Query] = [] 2699 2700 for i, tup in enumerate(expression.expressions): 2701 row = tup.expressions 2702 2703 if i == 0 and column_names: 2704 row = [ 2705 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2706 ] 2707 2708 selects.append(exp.Select(expressions=row)) 2709 2710 if self.pretty: 2711 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2712 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2713 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2714 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2715 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2716 2717 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2718 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2719 return f"({unions}){alias}" 2720 2721 def var_sql(self, expression: exp.Var) -> str: 2722 return self.sql(expression, "this") 2723 2724 @unsupported_args("expressions") 2725 def into_sql(self, expression: exp.Into) -> str: 2726 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2727 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2728 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2729 2730 def from_sql(self, expression: exp.From) -> str: 2731 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2732 2733 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2734 grouping_sets = self.expressions(expression, indent=False) 2735 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2736 2737 def rollup_sql(self, expression: exp.Rollup) -> str: 2738 expressions = self.expressions(expression, indent=False) 2739 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2740 2741 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2742 this = self.sql(expression, "this") 2743 2744 columns = self.expressions(expression, flat=True) 2745 2746 from_sql = self.sql(expression, "from_index") 2747 from_sql = f" FROM {from_sql}" if from_sql else "" 2748 2749 properties = expression.args.get("properties") 2750 properties_sql = ( 2751 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2752 ) 2753 2754 return f"{this}({columns}){from_sql}{properties_sql}" 2755 2756 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2757 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2758 2759 def cube_sql(self, expression: exp.Cube) -> str: 2760 expressions = self.expressions(expression, indent=False) 2761 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2762 2763 def group_sql(self, expression: exp.Group) -> str: 2764 group_by_all = expression.args.get("all") 2765 if group_by_all is True: 2766 modifier = " ALL" 2767 elif group_by_all is False: 2768 modifier = " DISTINCT" 2769 else: 2770 modifier = "" 2771 2772 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2773 2774 grouping_sets = self.expressions(expression, key="grouping_sets") 2775 cube = self.expressions(expression, key="cube") 2776 rollup = self.expressions(expression, key="rollup") 2777 2778 groupings = csv( 2779 self.seg(grouping_sets) if grouping_sets else "", 2780 self.seg(cube) if cube else "", 2781 self.seg(rollup) if rollup else "", 2782 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2783 sep=self.GROUPINGS_SEP, 2784 ) 2785 2786 if ( 2787 expression.expressions 2788 and groupings 2789 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2790 ): 2791 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2792 2793 return f"{group_by}{groupings}" 2794 2795 def having_sql(self, expression: exp.Having) -> str: 2796 this = self.indent(self.sql(expression, "this")) 2797 return f"{self.seg('HAVING')}{self.sep()}{this}" 2798 2799 def connect_sql(self, expression: exp.Connect) -> str: 2800 start = self.sql(expression, "start") 2801 start = self.seg(f"START WITH {start}") if start else "" 2802 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2803 connect = self.sql(expression, "connect") 2804 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2805 return start + connect 2806 2807 def prior_sql(self, expression: exp.Prior) -> str: 2808 return f"PRIOR {self.sql(expression, 'this')}" 2809 2810 def join_sql(self, expression: exp.Join) -> str: 2811 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2812 side = None 2813 else: 2814 side = expression.side 2815 2816 op_sql = " ".join( 2817 op 2818 for op in ( 2819 expression.method, 2820 "GLOBAL" if expression.args.get("global_") else None, 2821 side, 2822 expression.kind, 2823 expression.hint if self.JOIN_HINTS else None, 2824 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2825 ) 2826 if op 2827 ) 2828 match_cond = self.sql(expression, "match_condition") 2829 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2830 on_sql = self.sql(expression, "on") 2831 using = expression.args.get("using") 2832 2833 if not on_sql and using: 2834 on_sql = csv(*(self.sql(column) for column in using)) 2835 2836 this = expression.this 2837 this_sql = self.sql(this) 2838 2839 exprs = self.expressions(expression) 2840 if exprs: 2841 this_sql = f"{this_sql},{self.seg(exprs)}" 2842 2843 if on_sql: 2844 on_sql = self.indent(on_sql, skip_first=True) 2845 space = self.seg(" " * self.pad) if self.pretty else " " 2846 if using: 2847 on_sql = f"{space}USING ({on_sql})" 2848 else: 2849 on_sql = f"{space}ON {on_sql}" 2850 elif not op_sql: 2851 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2852 return f" {this_sql}" 2853 2854 return f", {this_sql}" 2855 2856 if op_sql != "STRAIGHT_JOIN": 2857 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2858 2859 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2860 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2861 2862 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2863 args = self.expressions(expression, flat=True) 2864 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2865 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2866 2867 def lateral_op(self, expression: exp.Lateral) -> str: 2868 cross_apply = expression.args.get("cross_apply") 2869 2870 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2871 if cross_apply is True: 2872 op = "INNER JOIN " 2873 elif cross_apply is False: 2874 op = "LEFT JOIN " 2875 else: 2876 op = "" 2877 2878 return f"{op}LATERAL" 2879 2880 def lateral_sql(self, expression: exp.Lateral) -> str: 2881 this = self.sql(expression, "this") 2882 2883 if expression.args.get("view"): 2884 alias = expression.args["alias"] 2885 columns = self.expressions(alias, key="columns", flat=True) 2886 table = f" {alias.name}" if alias.name else "" 2887 columns = f" AS {columns}" if columns else "" 2888 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2889 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2890 2891 alias = self.sql(expression, "alias") 2892 alias = f" AS {alias}" if alias else "" 2893 2894 ordinality = expression.args.get("ordinality") or "" 2895 if ordinality: 2896 ordinality = f" WITH ORDINALITY{alias}" 2897 alias = "" 2898 2899 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2900 2901 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2902 this = self.sql(expression, "this") 2903 2904 args = [ 2905 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2906 for e in (expression.args.get(k) for k in ("offset", "expression")) 2907 if e 2908 ] 2909 2910 args_sql = ", ".join(self.sql(e) for e in args) 2911 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2912 expressions = self.expressions(expression, flat=True) 2913 limit_options = self.sql(expression, "limit_options") 2914 expressions = f" BY {expressions}" if expressions else "" 2915 2916 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2917 2918 def offset_sql(self, expression: exp.Offset) -> str: 2919 this = self.sql(expression, "this") 2920 value = expression.expression 2921 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2922 expressions = self.expressions(expression, flat=True) 2923 expressions = f" BY {expressions}" if expressions else "" 2924 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2925 2926 def setitem_sql(self, expression: exp.SetItem) -> str: 2927 kind = self.sql(expression, "kind") 2928 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2929 kind = "" 2930 else: 2931 kind = f"{kind} " if kind else "" 2932 this = self.sql(expression, "this") 2933 expressions = self.expressions(expression) 2934 collate = self.sql(expression, "collate") 2935 collate = f" COLLATE {collate}" if collate else "" 2936 global_ = "GLOBAL " if expression.args.get("global_") else "" 2937 return f"{global_}{kind}{this}{expressions}{collate}" 2938 2939 def set_sql(self, expression: exp.Set) -> str: 2940 expressions = f" {self.expressions(expression, flat=True)}" 2941 tag = " TAG" if expression.args.get("tag") else "" 2942 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2943 2944 def queryband_sql(self, expression: exp.QueryBand) -> str: 2945 this = self.sql(expression, "this") 2946 update = " UPDATE" if expression.args.get("update") else "" 2947 scope = self.sql(expression, "scope") 2948 scope = f" FOR {scope}" if scope else "" 2949 2950 return f"QUERY_BAND = {this}{update}{scope}" 2951 2952 def pragma_sql(self, expression: exp.Pragma) -> str: 2953 return f"PRAGMA {self.sql(expression, 'this')}" 2954 2955 def lock_sql(self, expression: exp.Lock) -> str: 2956 if not self.LOCKING_READS_SUPPORTED: 2957 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2958 return "" 2959 2960 update = expression.args["update"] 2961 key = expression.args.get("key") 2962 if update: 2963 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2964 else: 2965 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2966 expressions = self.expressions(expression, flat=True) 2967 expressions = f" OF {expressions}" if expressions else "" 2968 wait = expression.args.get("wait") 2969 2970 if wait is not None: 2971 if isinstance(wait, exp.Literal): 2972 wait = f" WAIT {self.sql(wait)}" 2973 else: 2974 wait = " NOWAIT" if wait else " SKIP LOCKED" 2975 2976 return f"{lock_type}{expressions}{wait or ''}" 2977 2978 def literal_sql(self, expression: exp.Literal) -> str: 2979 text = expression.this or "" 2980 if expression.is_string: 2981 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2982 return text 2983 2984 def escape_str( 2985 self, 2986 text: str, 2987 escape_backslash: bool = True, 2988 delimiter: str | None = None, 2989 escaped_delimiter: str | None = None, 2990 is_byte_string: bool = False, 2991 ) -> str: 2992 if is_byte_string: 2993 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2994 else: 2995 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2996 2997 if supports_escape_sequences: 2998 text = "".join( 2999 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3000 for ch in text 3001 ) 3002 3003 delimiter = delimiter or self.dialect.QUOTE_END 3004 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3005 3006 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3007 3008 def loaddata_sql(self, expression: exp.LoadData) -> str: 3009 is_overwrite = expression.args.get("overwrite") 3010 overwrite = " OVERWRITE" if is_overwrite else "" 3011 this = self.sql(expression, "this") 3012 3013 files = expression.args.get("files") 3014 if files: 3015 files_sql = self.expressions(files, flat=True) 3016 files_sql = f"FILES{self.wrap(files_sql)}" 3017 if is_overwrite: 3018 this = f" {this}" 3019 elif expression.args.get("temp"): 3020 this = f" INTO TEMP TABLE {this}" 3021 else: 3022 this = f" INTO TABLE {this}" 3023 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3024 3025 local = " LOCAL" if expression.args.get("local") else "" 3026 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3027 this = f" INTO TABLE {this}" 3028 partition = self.sql(expression, "partition") 3029 partition = f" {partition}" if partition else "" 3030 input_format = self.sql(expression, "input_format") 3031 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3032 serde = self.sql(expression, "serde") 3033 serde = f" SERDE {serde}" if serde else "" 3034 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3035 3036 def null_sql(self, *_) -> str: 3037 return "NULL" 3038 3039 def boolean_sql(self, expression: exp.Boolean) -> str: 3040 return "TRUE" if expression.this else "FALSE" 3041 3042 def booland_sql(self, expression: exp.Booland) -> str: 3043 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3044 3045 def boolor_sql(self, expression: exp.Boolor) -> str: 3046 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3047 3048 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3049 this = self.sql(expression, "this") 3050 this = f"{this} " if this else this 3051 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3052 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3053 3054 def withfill_sql(self, expression: exp.WithFill) -> str: 3055 from_sql = self.sql(expression, "from_") 3056 from_sql = f" FROM {from_sql}" if from_sql else "" 3057 to_sql = self.sql(expression, "to") 3058 to_sql = f" TO {to_sql}" if to_sql else "" 3059 step_sql = self.sql(expression, "step") 3060 step_sql = f" STEP {step_sql}" if step_sql else "" 3061 interpolated_values = [ 3062 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3063 if isinstance(e, exp.Alias) 3064 else self.sql(e, "this") 3065 for e in expression.args.get("interpolate") or [] 3066 ] 3067 interpolate = ( 3068 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3069 ) 3070 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3071 3072 def cluster_sql(self, expression: exp.Cluster) -> str: 3073 return self.op_expressions("CLUSTER BY", expression) 3074 3075 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3076 if expression.this: 3077 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3078 return "" 3079 expressions = self.expressions(expression, flat=True) 3080 return f"CLUSTER BY ({expressions})" 3081 3082 def distribute_sql(self, expression: exp.Distribute) -> str: 3083 return self.op_expressions("DISTRIBUTE BY", expression) 3084 3085 def sort_sql(self, expression: exp.Sort) -> str: 3086 return self.op_expressions("SORT BY", expression) 3087 3088 def _resolve_ordered_for_null_ordering_simulation( 3089 self, expression: exp.Ordered 3090 ) -> exp.Expr | None: 3091 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3092 3093 Returns the underlying expression of the uniquely-matching projection 3094 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3095 simulation, since the CASE is evaluated in FROM-clause scope rather 3096 than alias scope (MySQL error 1052). Returns None if no safe 3097 substitution applies, leaving the original behaviour unchanged. 3098 """ 3099 this = expression.this 3100 if not (isinstance(this, exp.Column) and not this.table): 3101 return None 3102 3103 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3104 if not isinstance(ancestor, exp.Select): 3105 return None 3106 3107 column_name = this.name 3108 matched: list[exp.Expr] = [ 3109 p.this if isinstance(p, exp.Alias) else p 3110 for p in ancestor.selects 3111 if p.output_name == column_name 3112 ] 3113 match = matched[0] if len(matched) == 1 else None 3114 3115 # Skip the substitution when it would be identical to the existing 3116 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3117 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3118 return None 3119 3120 return match 3121 3122 def ordered_sql(self, expression: exp.Ordered) -> str: 3123 desc = expression.args.get("desc") 3124 asc = not desc 3125 3126 nulls_first = expression.args.get("nulls_first") 3127 nulls_last = not nulls_first 3128 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3129 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3130 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3131 3132 this = self.sql(expression, "this") 3133 3134 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3135 nulls_sort_change = "" 3136 if nulls_first and ( 3137 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3138 ): 3139 nulls_sort_change = " NULLS FIRST" 3140 elif ( 3141 nulls_last 3142 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3143 and not nulls_are_last 3144 ): 3145 nulls_sort_change = " NULLS LAST" 3146 3147 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3148 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3149 window = expression.find_ancestor(exp.Window, exp.Select) 3150 3151 if isinstance(window, exp.Window): 3152 window_this = window.this 3153 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3154 window_this = window_this.this 3155 spec = window.args.get("spec") 3156 else: 3157 window_this = None 3158 spec = None 3159 3160 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3161 # without a spec or with a ROWS spec, but not with RANGE 3162 if not ( 3163 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3164 and (not spec or spec.text("kind").upper() == "ROWS") 3165 ): 3166 if window_this and spec: 3167 self.unsupported( 3168 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3169 ) 3170 nulls_sort_change = "" 3171 elif self.NULL_ORDERING_SUPPORTED is False and ( 3172 (asc and nulls_sort_change == " NULLS LAST") 3173 or (desc and nulls_sort_change == " NULLS FIRST") 3174 ): 3175 # BigQuery does not allow these ordering/nulls combinations when used under 3176 # an aggregation func or under a window containing one 3177 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3178 3179 if isinstance(ancestor, exp.Window): 3180 ancestor = ancestor.this 3181 if isinstance(ancestor, exp.AggFunc): 3182 self.unsupported( 3183 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3184 ) 3185 nulls_sort_change = "" 3186 elif self.NULL_ORDERING_SUPPORTED is None: 3187 if expression.this.is_int: 3188 self.unsupported( 3189 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3190 ) 3191 elif not isinstance(expression.this, exp.Rand): 3192 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3193 target = self.sql(resolved) if resolved is not None else this 3194 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3195 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3196 nulls_sort_change = "" 3197 3198 with_fill = self.sql(expression, "with_fill") 3199 with_fill = f" {with_fill}" if with_fill else "" 3200 3201 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3202 3203 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3204 window_frame = self.sql(expression, "window_frame") 3205 window_frame = f"{window_frame} " if window_frame else "" 3206 3207 this = self.sql(expression, "this") 3208 3209 return f"{window_frame}{this}" 3210 3211 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3212 partition = self.partition_by_sql(expression) 3213 order = self.sql(expression, "order") 3214 measures = self.expressions(expression, key="measures") 3215 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3216 rows = self.sql(expression, "rows") 3217 rows = self.seg(rows) if rows else "" 3218 after = self.sql(expression, "after") 3219 after = self.seg(after) if after else "" 3220 pattern = self.sql(expression, "pattern") 3221 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3222 definition_sqls = [ 3223 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3224 for definition in expression.args.get("define", []) 3225 ] 3226 definitions = self.expressions(sqls=definition_sqls) 3227 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3228 body = "".join( 3229 ( 3230 partition, 3231 order, 3232 measures, 3233 rows, 3234 after, 3235 pattern, 3236 define, 3237 ) 3238 ) 3239 alias = self.sql(expression, "alias") 3240 alias = f" {alias}" if alias else "" 3241 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3242 3243 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3244 limit = expression.args.get("limit") 3245 3246 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3247 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3248 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3249 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3250 3251 return csv( 3252 *sqls, 3253 *[self.sql(join) for join in expression.args.get("joins") or []], 3254 self.sql(expression, "match"), 3255 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3256 self.sql(expression, "prewhere"), 3257 self.sql(expression, "where"), 3258 self.sql(expression, "connect"), 3259 self.sql(expression, "group"), 3260 self.sql(expression, "having"), 3261 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3262 self.sql(expression, "order"), 3263 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3264 *self.after_limit_modifiers(expression), 3265 self.options_modifier(expression), 3266 self.sql(expression, "for_"), 3267 sep="", 3268 ) 3269 3270 def options_modifier(self, expression: exp.Expr) -> str: 3271 options = self.expressions(expression, key="options") 3272 return f" {options}" if options else "" 3273 3274 def forclause_sql(self, expression: exp.ForClause) -> str: 3275 kind = expression.args["kind"] 3276 if kind == "BROWSE": 3277 return f"{self.sep()}FOR BROWSE" 3278 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3279 # the target dialect doesn't support QueryOption, so we drop the clause. 3280 options = self.expressions(expression, key="expressions") 3281 if not options: 3282 return "" 3283 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3284 3285 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3286 self.unsupported("Unsupported query option.") 3287 return "" 3288 3289 def offset_limit_modifiers( 3290 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3291 ) -> list[str]: 3292 return [ 3293 self.sql(expression, "offset") if fetch else self.sql(limit), 3294 self.sql(limit) if fetch else self.sql(expression, "offset"), 3295 ] 3296 3297 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3298 locks = self.expressions(expression, key="locks", sep=" ") 3299 locks = f" {locks}" if locks else "" 3300 return [locks, self.sql(expression, "sample")] 3301 3302 def select_sql(self, expression: exp.Select) -> str: 3303 into = expression.args.get("into") 3304 if not self.SUPPORTS_SELECT_INTO and into: 3305 into.pop() 3306 3307 hint = self.sql(expression, "hint") 3308 distinct = self.sql(expression, "distinct") 3309 distinct = f" {distinct}" if distinct else "" 3310 kind = self.sql(expression, "kind") 3311 3312 limit = expression.args.get("limit") 3313 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3314 top = self.limit_sql(limit, top=True) 3315 limit.pop() 3316 else: 3317 top = "" 3318 3319 expressions = self.expressions(expression) 3320 3321 if kind: 3322 if kind in self.SELECT_KINDS: 3323 kind = f" AS {kind}" 3324 else: 3325 if kind == "STRUCT": 3326 expressions = self.expressions( 3327 sqls=[ 3328 self.sql( 3329 exp.Struct( 3330 expressions=[ 3331 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3332 if isinstance(e, exp.Alias) 3333 else e 3334 for e in expression.expressions 3335 ] 3336 ) 3337 ) 3338 ] 3339 ) 3340 kind = "" 3341 3342 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3343 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3344 3345 exclude = expression.args.get("exclude") 3346 3347 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3348 exclude_sql = self.expressions(sqls=exclude, flat=True) 3349 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3350 3351 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3352 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3353 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3354 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3355 sql = self.query_modifiers( 3356 expression, 3357 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3358 self.sql(expression, "into", comment=False), 3359 self.sql(expression, "from_", comment=False), 3360 ) 3361 3362 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3363 if expression.args.get("with_"): 3364 sql = self.maybe_comment(sql, expression) 3365 expression.pop_comments() 3366 3367 sql = self.prepend_ctes(expression, sql) 3368 3369 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3370 expression.set("exclude", None) 3371 subquery = expression.subquery(copy=False) 3372 star = exp.Star(except_=exclude) 3373 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3374 3375 if not self.SUPPORTS_SELECT_INTO and into: 3376 if into.args.get("temporary"): 3377 table_kind = " TEMPORARY" 3378 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3379 table_kind = " UNLOGGED" 3380 else: 3381 table_kind = "" 3382 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3383 3384 return sql 3385 3386 def schema_sql(self, expression: exp.Schema) -> str: 3387 this = self.sql(expression, "this") 3388 sql = self.schema_columns_sql(expression) 3389 return f"{this} {sql}" if this and sql else this or sql 3390 3391 def schema_columns_sql(self, expression: exp.Expr) -> str: 3392 if expression.expressions: 3393 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3394 return "" 3395 3396 def star_sql(self, expression: exp.Star) -> str: 3397 except_ = self.expressions(expression, key="except_", flat=True) 3398 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3399 replace = self.expressions(expression, key="replace", flat=True) 3400 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3401 rename = self.expressions(expression, key="rename", flat=True) 3402 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3403 ilike = self.sql(expression, "ilike") 3404 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3405 return f"*{ilike}{except_}{replace}{rename}" 3406 3407 def parameter_sql(self, expression: exp.Parameter) -> str: 3408 this = self.sql(expression, "this") 3409 return f"{self.PARAMETER_TOKEN}{this}" 3410 3411 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3412 this = self.sql(expression, "this") 3413 kind = expression.text("kind") 3414 if kind: 3415 kind = f"{kind}." 3416 return f"@@{kind}{this}" 3417 3418 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3419 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3420 3421 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3422 alias = self.sql(expression, "alias") 3423 alias = f"{sep}{alias}" if alias else "" 3424 sample = self.sql(expression, "sample") 3425 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3426 alias = f"{sample}{alias}" 3427 3428 # Set to None so it's not generated again by self.query_modifiers() 3429 expression.set("sample", None) 3430 3431 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3432 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3433 return self.prepend_ctes(expression, sql) 3434 3435 def qualify_sql(self, expression: exp.Qualify) -> str: 3436 this = self.indent(self.sql(expression, "this")) 3437 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3438 3439 def unnest_sql(self, expression: exp.Unnest) -> str: 3440 args = self.expressions(expression, flat=True) 3441 3442 alias = expression.args.get("alias") 3443 offset = expression.args.get("offset") 3444 3445 if self.UNNEST_WITH_ORDINALITY: 3446 if alias and isinstance(offset, exp.Expr): 3447 alias.append("columns", offset) 3448 expression.set("offset", None) 3449 3450 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3451 columns = alias.columns 3452 alias = self.sql(columns[0]) if columns else "" 3453 else: 3454 alias = self.sql(alias) 3455 3456 alias = f" AS {alias}" if alias else alias 3457 if self.UNNEST_WITH_ORDINALITY: 3458 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3459 else: 3460 if isinstance(offset, exp.Expr): 3461 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3462 elif offset: 3463 suffix = f"{alias} WITH OFFSET" 3464 else: 3465 suffix = alias 3466 3467 return f"UNNEST({args}){suffix}" 3468 3469 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3470 return "" 3471 3472 def where_sql(self, expression: exp.Where) -> str: 3473 this = self.indent(self.sql(expression, "this")) 3474 return f"{self.seg('WHERE')}{self.sep()}{this}" 3475 3476 def window_sql(self, expression: exp.Window) -> str: 3477 this = self.sql(expression, "this") 3478 partition = self.partition_by_sql(expression) 3479 order = expression.args.get("order") 3480 order = self.order_sql(order, flat=True) if order else "" 3481 spec = self.sql(expression, "spec") 3482 alias = self.sql(expression, "alias") 3483 over = self.sql(expression, "over") or "OVER" 3484 3485 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3486 3487 first = expression.args.get("first") 3488 if first is None: 3489 first = "" 3490 else: 3491 first = "FIRST" if first else "LAST" 3492 3493 if not partition and not order and not spec and alias: 3494 return f"{this} {alias}" 3495 3496 args = self.format_args( 3497 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3498 ) 3499 return f"{this} ({args})" 3500 3501 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3502 partition = self.expressions(expression, key="partition_by", flat=True) 3503 return f"PARTITION BY {partition}" if partition else "" 3504 3505 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3506 kind = self.sql(expression, "kind") 3507 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3508 end = ( 3509 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3510 or "CURRENT ROW" 3511 ) 3512 3513 window_spec = f"{kind} BETWEEN {start} AND {end}" 3514 3515 exclude = self.sql(expression, "exclude") 3516 if exclude: 3517 if self.SUPPORTS_WINDOW_EXCLUDE: 3518 window_spec += f" EXCLUDE {exclude}" 3519 else: 3520 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3521 3522 return window_spec 3523 3524 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3525 this = self.sql(expression, "this") 3526 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3527 return f"{this} WITHIN GROUP ({expression_sql})" 3528 3529 def between_sql(self, expression: exp.Between) -> str: 3530 this = self.sql(expression, "this") 3531 low = self.sql(expression, "low") 3532 high = self.sql(expression, "high") 3533 symmetric = expression.args.get("symmetric") 3534 3535 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3536 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3537 3538 flag = ( 3539 " SYMMETRIC" 3540 if symmetric 3541 else " ASYMMETRIC" 3542 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3543 else "" # silently drop ASYMMETRIC – semantics identical 3544 ) 3545 return f"{this} BETWEEN{flag} {low} AND {high}" 3546 3547 def bracket_offset_expressions( 3548 self, expression: exp.Bracket, index_offset: int | None = None 3549 ) -> list[exp.Expr]: 3550 if expression.args.get("json_access"): 3551 return expression.expressions 3552 3553 return apply_index_offset( 3554 expression.this, 3555 expression.expressions, 3556 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3557 dialect=self.dialect, 3558 ) 3559 3560 def bracket_sql(self, expression: exp.Bracket) -> str: 3561 expressions = self.bracket_offset_expressions(expression) 3562 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3563 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3564 3565 def all_sql(self, expression: exp.All) -> str: 3566 this = self.sql(expression, "this") 3567 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3568 this = self.wrap(this) 3569 return f"ALL {this}" 3570 3571 def any_sql(self, expression: exp.Any) -> str: 3572 this = self.sql(expression, "this") 3573 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3574 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3575 this = self.wrap(this) 3576 return f"ANY{this}" 3577 return f"ANY {this}" 3578 3579 def exists_sql(self, expression: exp.Exists) -> str: 3580 return f"EXISTS{self.wrap(expression)}" 3581 3582 def case_sql(self, expression: exp.Case) -> str: 3583 this = self.sql(expression, "this") 3584 statements = [f"CASE {this}" if this else "CASE"] 3585 3586 for e in expression.args["ifs"]: 3587 statements.append(f"WHEN {self.sql(e, 'this')}") 3588 statements.append(f"THEN {self.sql(e, 'true')}") 3589 3590 default = self.sql(expression, "default") 3591 3592 if default: 3593 statements.append(f"ELSE {default}") 3594 3595 statements.append("END") 3596 3597 if self.pretty and self.too_wide(statements): 3598 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3599 3600 return " ".join(statements) 3601 3602 def constraint_sql(self, expression: exp.Constraint) -> str: 3603 this = self.sql(expression, "this") 3604 expressions = self.expressions(expression, flat=True) 3605 return f"CONSTRAINT {this} {expressions}" 3606 3607 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3608 order = expression.args.get("order") 3609 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3610 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3611 3612 def extract_sql(self, expression: exp.Extract) -> str: 3613 import sqlglot.dialects.dialect 3614 3615 this = ( 3616 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3617 if self.NORMALIZE_EXTRACT_DATE_PARTS 3618 else expression.this 3619 ) 3620 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3621 expression_sql = self.sql(expression, "expression") 3622 3623 return f"EXTRACT({this_sql} FROM {expression_sql})" 3624 3625 def trim_sql(self, expression: exp.Trim) -> str: 3626 trim_type = self.sql(expression, "position") 3627 3628 if trim_type == "LEADING": 3629 func_name = "LTRIM" 3630 elif trim_type == "TRAILING": 3631 func_name = "RTRIM" 3632 else: 3633 func_name = "TRIM" 3634 3635 return self.func(func_name, expression.this, expression.expression) 3636 3637 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3638 args = expression.expressions 3639 if isinstance(expression, exp.ConcatWs): 3640 args = args[1:] # Skip the delimiter 3641 3642 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3643 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3644 3645 concat_coalesce = ( 3646 self.dialect.CONCAT_WS_COALESCE 3647 if isinstance(expression, exp.ConcatWs) 3648 else self.dialect.CONCAT_COALESCE 3649 ) 3650 3651 if not concat_coalesce and expression.args.get("coalesce"): 3652 3653 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3654 if not e.type: 3655 import sqlglot.optimizer.annotate_types 3656 3657 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3658 3659 if e.is_string or e.is_type(exp.DType.ARRAY): 3660 return e 3661 3662 return exp.func("coalesce", e, exp.Literal.string("")) 3663 3664 args = [_wrap_with_coalesce(e) for e in args] 3665 3666 return args 3667 3668 def concat_sql(self, expression: exp.Concat) -> str: 3669 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3670 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3671 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3672 # instead of coalescing them to empty string. 3673 import sqlglot.dialects.dialect 3674 3675 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3676 3677 expressions = self.convert_concat_args(expression) 3678 3679 # Some dialects don't allow a single-argument CONCAT call 3680 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3681 return self.sql(expressions[0]) 3682 3683 return self.func("CONCAT", *expressions) 3684 3685 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3686 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3687 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3688 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3689 all_args = expression.expressions 3690 expression.set("coalesce", True) 3691 return self.sql( 3692 exp.case() 3693 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3694 .else_(expression) 3695 ) 3696 3697 return self.func( 3698 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3699 ) 3700 3701 def check_sql(self, expression: exp.Check) -> str: 3702 this = self.sql(expression, key="this") 3703 return f"CHECK ({this})" 3704 3705 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3706 expressions = self.expressions(expression, flat=True) 3707 expressions = f" ({expressions})" if expressions else "" 3708 reference = self.sql(expression, "reference") 3709 reference = f" {reference}" if reference else "" 3710 delete = self.sql(expression, "delete") 3711 delete = f" ON DELETE {delete}" if delete else "" 3712 update = self.sql(expression, "update") 3713 update = f" ON UPDATE {update}" if update else "" 3714 options = self.expressions(expression, key="options", flat=True, sep=" ") 3715 options = f" {options}" if options else "" 3716 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3717 3718 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3719 this = self.sql(expression, "this") 3720 this = f" {this}" if this else "" 3721 expressions = self.expressions(expression, flat=True) 3722 include = self.sql(expression, "include") 3723 options = self.expressions(expression, key="options", flat=True, sep=" ") 3724 options = f" {options}" if options else "" 3725 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3726 3727 def if_sql(self, expression: exp.If) -> str: 3728 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3729 3730 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3731 if self.MATCH_AGAINST_TABLE_PREFIX: 3732 expressions = [] 3733 for expr in expression.expressions: 3734 if isinstance(expr, exp.Table): 3735 expressions.append(f"TABLE {self.sql(expr)}") 3736 else: 3737 expressions.append(expr) 3738 else: 3739 expressions = expression.expressions 3740 3741 modifier = expression.args.get("modifier") 3742 modifier = f" {modifier}" if modifier else "" 3743 return ( 3744 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3745 ) 3746 3747 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3748 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3749 3750 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3751 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3752 3753 if self.QUOTE_JSON_PATH: 3754 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3755 3756 return path 3757 3758 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3759 if isinstance(expression, exp.JSONPathPart): 3760 transform = self.TRANSFORMS.get(expression.__class__) 3761 if not callable(transform): 3762 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3763 return "" 3764 3765 return transform(self, expression) 3766 3767 if isinstance(expression, int): 3768 return str(expression) 3769 3770 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3771 escaped = expression.replace("'", "\\'") 3772 escaped = f"\\'{expression}\\'" 3773 else: 3774 escaped = expression.replace('"', '\\"') 3775 escaped = f'"{escaped}"' 3776 3777 return escaped 3778 3779 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3780 return f"{self.sql(expression, 'this')} FORMAT JSON" 3781 3782 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3783 # Output the Teradata column FORMAT override. 3784 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3785 this = self.sql(expression, "this") 3786 fmt = self.sql(expression, "format") 3787 return f"{this} (FORMAT {fmt})" 3788 3789 def _jsonobject_sql( 3790 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3791 ) -> str: 3792 null_handling = expression.args.get("null_handling") 3793 null_handling = f" {null_handling}" if null_handling else "" 3794 3795 unique_keys = expression.args.get("unique_keys") 3796 if unique_keys is not None: 3797 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3798 else: 3799 unique_keys = "" 3800 3801 return_type = self.sql(expression, "return_type") 3802 return_type = f" RETURNING {return_type}" if return_type else "" 3803 encoding = self.sql(expression, "encoding") 3804 encoding = f" ENCODING {encoding}" if encoding else "" 3805 3806 if not name: 3807 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3808 3809 return self.func( 3810 name, 3811 *expression.expressions, 3812 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3813 ) 3814 3815 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3816 null_handling = expression.args.get("null_handling") 3817 null_handling = f" {null_handling}" if null_handling else "" 3818 return_type = self.sql(expression, "return_type") 3819 return_type = f" RETURNING {return_type}" if return_type else "" 3820 strict = " STRICT" if expression.args.get("strict") else "" 3821 return self.func( 3822 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3823 ) 3824 3825 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3826 this = self.sql(expression, "this") 3827 order = self.sql(expression, "order") 3828 null_handling = expression.args.get("null_handling") 3829 null_handling = f" {null_handling}" if null_handling else "" 3830 return_type = self.sql(expression, "return_type") 3831 return_type = f" RETURNING {return_type}" if return_type else "" 3832 strict = " STRICT" if expression.args.get("strict") else "" 3833 return self.func( 3834 "JSON_ARRAYAGG", 3835 this, 3836 suffix=f"{order}{null_handling}{return_type}{strict})", 3837 ) 3838 3839 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3840 path = self.sql(expression, "path") 3841 path = f" PATH {path}" if path else "" 3842 nested_schema = self.sql(expression, "nested_schema") 3843 3844 if nested_schema: 3845 return f"NESTED{path} {nested_schema}" 3846 3847 this = self.sql(expression, "this") 3848 kind = self.sql(expression, "kind") 3849 kind = f" {kind}" if kind else "" 3850 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3851 3852 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3853 return f"{this}{kind}{format_json}{path}{ordinality}" 3854 3855 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3856 return self.func("COLUMNS", *expression.expressions) 3857 3858 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3859 this = self.sql(expression, "this") 3860 path = self.sql(expression, "path") 3861 path = f", {path}" if path else "" 3862 error_handling = expression.args.get("error_handling") 3863 error_handling = f" {error_handling}" if error_handling else "" 3864 empty_handling = expression.args.get("empty_handling") 3865 empty_handling = f" {empty_handling}" if empty_handling else "" 3866 schema = self.sql(expression, "schema") 3867 return self.func( 3868 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3869 ) 3870 3871 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3872 this = self.sql(expression, "this") 3873 kind = self.sql(expression, "kind") 3874 path = self.sql(expression, "path") 3875 path = f" {path}" if path else "" 3876 as_json = " AS JSON" if expression.args.get("as_json") else "" 3877 return f"{this} {kind}{path}{as_json}" 3878 3879 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3880 this = self.sql(expression, "this") 3881 path = self.sql(expression, "path") 3882 path = f", {path}" if path else "" 3883 expressions = self.expressions(expression) 3884 with_ = ( 3885 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3886 if expressions 3887 else "" 3888 ) 3889 return f"OPENJSON({this}{path}){with_}" 3890 3891 def in_sql(self, expression: exp.In) -> str: 3892 query = expression.args.get("query") 3893 unnest = expression.args.get("unnest") 3894 field = expression.args.get("field") 3895 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3896 3897 if query: 3898 in_sql = self.sql(query) 3899 elif unnest: 3900 in_sql = self.in_unnest_op(unnest) 3901 elif field: 3902 in_sql = self.sql(field) 3903 else: 3904 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3905 3906 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3907 3908 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3909 return f"(SELECT {self.sql(unnest)})" 3910 3911 def interval_sql(self, expression: exp.Interval) -> str: 3912 unit_expression = expression.args.get("unit") 3913 unit = self.sql(unit_expression) if unit_expression else "" 3914 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3915 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3916 unit = f" {unit}" if unit else "" 3917 3918 if self.SINGLE_STRING_INTERVAL: 3919 this = expression.this.name if expression.this else "" 3920 if this: 3921 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3922 return f"INTERVAL '{this}'{unit}" 3923 return f"INTERVAL '{this}{unit}'" 3924 return f"INTERVAL{unit}" 3925 3926 this = self.sql(expression, "this") 3927 if this: 3928 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3929 this = f" {this}" if unwrapped else f" ({this})" 3930 3931 return f"INTERVAL{this}{unit}" 3932 3933 def return_sql(self, expression: exp.Return) -> str: 3934 return f"RETURN {self.sql(expression, 'this')}" 3935 3936 def reference_sql(self, expression: exp.Reference) -> str: 3937 this = self.sql(expression, "this") 3938 expressions = self.expressions(expression, flat=True) 3939 expressions = f"({expressions})" if expressions else "" 3940 options = self.expressions(expression, key="options", flat=True, sep=" ") 3941 options = f" {options}" if options else "" 3942 return f"REFERENCES {this}{expressions}{options}" 3943 3944 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3945 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3946 parent = expression.parent 3947 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3948 3949 return self.func( 3950 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3951 ) 3952 3953 def paren_sql(self, expression: exp.Paren) -> str: 3954 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3955 return f"({sql}{self.seg(')', sep='')}" 3956 3957 def neg_sql(self, expression: exp.Neg) -> str: 3958 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3959 this_sql = self.sql(expression, "this") 3960 sep = " " if this_sql[0] == "-" else "" 3961 return f"-{sep}{this_sql}" 3962 3963 def not_sql(self, expression: exp.Not) -> str: 3964 return f"NOT {self.sql(expression, 'this')}" 3965 3966 def alias_sql(self, expression: exp.Alias) -> str: 3967 alias = self.sql(expression, "alias") 3968 alias = f" AS {alias}" if alias else "" 3969 return f"{self.sql(expression, 'this')}{alias}" 3970 3971 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3972 alias = expression.args["alias"] 3973 3974 parent = expression.parent 3975 pivot = parent and parent.parent 3976 3977 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3978 identifier_alias = isinstance(alias, exp.Identifier) 3979 literal_alias = isinstance(alias, exp.Literal) 3980 3981 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3982 alias.replace(exp.Literal.string(alias.output_name)) 3983 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3984 alias.replace(exp.to_identifier(alias.output_name)) 3985 3986 return self.alias_sql(expression) 3987 3988 def aliases_sql(self, expression: exp.Aliases) -> str: 3989 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3990 3991 def atindex_sql(self, expression: exp.AtIndex) -> str: 3992 this = self.sql(expression, "this") 3993 index = self.sql(expression, "expression") 3994 return f"{this} AT {index}" 3995 3996 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3997 this = self.sql(expression, "this") 3998 zone = self.sql(expression, "zone") 3999 return f"{this} AT TIME ZONE {zone}" 4000 4001 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4002 this = self.sql(expression, "this") 4003 zone = self.sql(expression, "zone") 4004 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4005 4006 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4007 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4008 4009 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4010 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4011 4012 def add_sql(self, expression: exp.Add) -> str: 4013 return self.binary(expression, "+") 4014 4015 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4016 return self.connector_sql(expression, "AND", stack) 4017 4018 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4019 return self.connector_sql(expression, "OR", stack) 4020 4021 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4022 return self.connector_sql(expression, "XOR", stack) 4023 4024 def connector_sql( 4025 self, 4026 expression: exp.Connector, 4027 op: str, 4028 stack: list[str | exp.Expr] | None = None, 4029 ) -> str: 4030 if stack is not None: 4031 if expression.expressions: 4032 stack.append(self.expressions(expression, sep=f" {op} ")) 4033 else: 4034 stack.append(expression.right) 4035 if expression.comments and self.comments: 4036 op = self.maybe_comment(op, comments=expression.comments) 4037 stack.extend((op, expression.left)) 4038 return op 4039 4040 stack = [expression] 4041 sqls: list[str] = [] 4042 ops = set() 4043 4044 while stack: 4045 node = stack.pop() 4046 if isinstance(node, exp.Connector): 4047 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4048 else: 4049 sql = self.sql(node) 4050 if sqls and sqls[-1] in ops: 4051 sqls[-1] += f" {sql}" 4052 else: 4053 sqls.append(sql) 4054 4055 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4056 return sep.join(sqls) 4057 4058 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4059 return self.binary(expression, "&") 4060 4061 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4062 return self.binary(expression, "<<") 4063 4064 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4065 return f"~{self.sql(expression, 'this')}" 4066 4067 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4068 return self.binary(expression, "|") 4069 4070 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4071 return self.binary(expression, ">>") 4072 4073 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4074 return self.binary(expression, "^") 4075 4076 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4077 format_sql = self.sql(expression, "format") 4078 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4079 to_sql = self.sql(expression, "to") 4080 to_sql = f" {to_sql}" if to_sql else "" 4081 action = self.sql(expression, "action") 4082 action = f" {action}" if action else "" 4083 default = self.sql(expression, "default") 4084 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4085 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4086 4087 # Base implementation that excludes safe, zone, and target_type metadata args 4088 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4089 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4090 4091 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4092 return self.func( 4093 "PARSE_DATETIME", 4094 expression.this, 4095 expression.args.get("format"), 4096 expression.args.get("zone"), 4097 ) 4098 4099 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4100 zone = self.sql(expression, "this") 4101 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4102 4103 def collate_sql(self, expression: exp.Collate) -> str: 4104 if self.COLLATE_IS_FUNC: 4105 return self.function_fallback_sql(expression) 4106 return self.binary(expression, "COLLATE") 4107 4108 def command_sql(self, expression: exp.Command) -> str: 4109 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4110 4111 def comment_sql(self, expression: exp.Comment) -> str: 4112 this = self.sql(expression, "this") 4113 kind = expression.args["kind"] 4114 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4115 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4116 expression_sql = self.sql(expression, "expression") 4117 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4118 4119 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4120 this = self.sql(expression, "this") 4121 delete = " DELETE" if expression.args.get("delete") else "" 4122 recompress = self.sql(expression, "recompress") 4123 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4124 to_disk = self.sql(expression, "to_disk") 4125 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4126 to_volume = self.sql(expression, "to_volume") 4127 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4128 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4129 4130 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4131 where = self.sql(expression, "where") 4132 group = self.sql(expression, "group") 4133 aggregates = self.expressions(expression, key="aggregates") 4134 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4135 4136 if not (where or group or aggregates) and len(expression.expressions) == 1: 4137 return f"TTL {self.expressions(expression, flat=True)}" 4138 4139 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4140 4141 def transaction_sql(self, expression: exp.Transaction) -> str: 4142 modes = self.expressions(expression, key="modes") 4143 modes = f" {modes}" if modes else "" 4144 return f"BEGIN{modes}" 4145 4146 def commit_sql(self, expression: exp.Commit) -> str: 4147 chain = expression.args.get("chain") 4148 if chain is not None: 4149 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4150 4151 return f"COMMIT{chain or ''}" 4152 4153 def rollback_sql(self, expression: exp.Rollback) -> str: 4154 savepoint = expression.args.get("savepoint") 4155 savepoint = f" TO {savepoint}" if savepoint else "" 4156 return f"ROLLBACK{savepoint}" 4157 4158 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4159 this = self.sql(expression, "this") 4160 4161 dtype = self.sql(expression, "dtype") 4162 if dtype: 4163 collate = self.sql(expression, "collate") 4164 collate = f" COLLATE {collate}" if collate else "" 4165 using = self.sql(expression, "using") 4166 using = f" USING {using}" if using else "" 4167 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4168 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4169 4170 default = self.sql(expression, "default") 4171 if default: 4172 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4173 4174 comment = self.sql(expression, "comment") 4175 if comment: 4176 return f"ALTER COLUMN {this} COMMENT {comment}" 4177 4178 visible = expression.args.get("visible") 4179 if visible: 4180 return f"ALTER COLUMN {this} SET {visible}" 4181 4182 allow_null = expression.args.get("allow_null") 4183 drop = expression.args.get("drop") 4184 4185 if not drop and not allow_null: 4186 self.unsupported("Unsupported ALTER COLUMN syntax") 4187 4188 if allow_null is not None: 4189 keyword = "DROP" if drop else "SET" 4190 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4191 4192 return f"ALTER COLUMN {this} DROP DEFAULT" 4193 4194 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4195 this = self.sql(expression, "this") 4196 rename_from = self.sql(expression, "rename_from") 4197 if rename_from: 4198 if not self.SUPPORTS_CHANGE_COLUMN: 4199 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4200 return f"CHANGE COLUMN {rename_from} {this}" 4201 if not self.SUPPORTS_MODIFY_COLUMN: 4202 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4203 return f"MODIFY COLUMN {this}" 4204 4205 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4206 this = self.sql(expression, "this") 4207 4208 visible = expression.args.get("visible") 4209 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4210 4211 return f"ALTER INDEX {this} {visible_sql}" 4212 4213 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4214 this = self.sql(expression, "this") 4215 if not isinstance(expression.this, exp.Var): 4216 this = f"KEY DISTKEY {this}" 4217 return f"ALTER DISTSTYLE {this}" 4218 4219 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4220 compound = " COMPOUND" if expression.args.get("compound") else "" 4221 this = self.sql(expression, "this") 4222 expressions = self.expressions(expression, flat=True) 4223 expressions = f"({expressions})" if expressions else "" 4224 return f"ALTER{compound} SORTKEY {this or expressions}" 4225 4226 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4227 if not self.RENAME_TABLE_WITH_DB: 4228 # Remove db from tables 4229 expression = expression.transform( 4230 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4231 ).assert_is(exp.AlterRename) 4232 this = self.sql(expression, "this") 4233 to_kw = " TO" if include_to else "" 4234 return f"RENAME{to_kw} {this}" 4235 4236 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4237 exists = " IF EXISTS" if expression.args.get("exists") else "" 4238 old_column = self.sql(expression, "this") 4239 new_column = self.sql(expression, "to") 4240 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4241 4242 def alterset_sql(self, expression: exp.AlterSet) -> str: 4243 exprs = self.expressions(expression, flat=True) 4244 if self.ALTER_SET_WRAPPED: 4245 exprs = f"({exprs})" 4246 4247 return f"SET {exprs}" 4248 4249 def alter_sql(self, expression: exp.Alter) -> str: 4250 actions = expression.args["actions"] 4251 4252 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4253 actions[0], exp.ColumnDef 4254 ): 4255 actions_sql = self.expressions(expression, key="actions", flat=True) 4256 actions_sql = f"ADD {actions_sql}" 4257 else: 4258 actions_list = [] 4259 for action in actions: 4260 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4261 action_sql = self.add_column_sql(action) 4262 else: 4263 action_sql = self.sql(action) 4264 if isinstance(action, exp.Query): 4265 action_sql = f"AS {action_sql}" 4266 4267 actions_list.append(action_sql) 4268 4269 actions_sql = self.format_args(*actions_list).lstrip("\n") 4270 4271 iceberg = ( 4272 "ICEBERG " 4273 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4274 else "" 4275 ) 4276 exists = " IF EXISTS" if expression.args.get("exists") else "" 4277 on_cluster = self.sql(expression, "cluster") 4278 on_cluster = f" {on_cluster}" if on_cluster else "" 4279 only = " ONLY" if expression.args.get("only") else "" 4280 options = self.expressions(expression, key="options") 4281 options = f", {options}" if options else "" 4282 kind = self.sql(expression, "kind") 4283 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4284 check = " WITH CHECK" if expression.args.get("check") else "" 4285 cascade = ( 4286 " CASCADE" 4287 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4288 else "" 4289 ) 4290 this = self.sql(expression, "this") 4291 this = f" {this}" if this else "" 4292 4293 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4294 4295 def altersession_sql(self, expression: exp.AlterSession) -> str: 4296 items_sql = self.expressions(expression, flat=True) 4297 keyword = "UNSET" if expression.args.get("unset") else "SET" 4298 return f"{keyword} {items_sql}" 4299 4300 def add_column_sql(self, expression: exp.Expr) -> str: 4301 sql = self.sql(expression) 4302 if isinstance(expression, exp.Schema): 4303 column_text = " COLUMNS" 4304 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4305 column_text = " COLUMN" 4306 else: 4307 column_text = "" 4308 4309 return f"ADD{column_text} {sql}" 4310 4311 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4312 expressions = self.expressions(expression) 4313 exists = " IF EXISTS " if expression.args.get("exists") else " " 4314 return f"DROP{exists}{expressions}" 4315 4316 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4317 return "DROP PRIMARY KEY" 4318 4319 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4320 return f"ADD {self.expressions(expression, indent=False)}" 4321 4322 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4323 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4324 location = self.sql(expression, "location") 4325 location = f" {location}" if location else "" 4326 return f"ADD {exists}{self.sql(expression.this)}{location}" 4327 4328 def distinct_sql(self, expression: exp.Distinct) -> str: 4329 this = self.expressions(expression, flat=True) 4330 4331 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4332 case = exp.case() 4333 for arg in expression.expressions: 4334 case = case.when(arg.is_(exp.null()), exp.null()) 4335 this = self.sql(case.else_(f"({this})")) 4336 4337 this = f" {this}" if this else "" 4338 4339 on = self.sql(expression, "on") 4340 on = f" ON {on}" if on else "" 4341 return f"DISTINCT{this}{on}" 4342 4343 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4344 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4345 4346 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4347 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4348 4349 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4350 this_sql = self.sql(expression, "this") 4351 expression_sql = self.sql(expression, "expression") 4352 kind = "MAX" if expression.args.get("max") else "MIN" 4353 return f"{this_sql} HAVING {kind} {expression_sql}" 4354 4355 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4356 return self.sql( 4357 exp.Cast( 4358 this=exp.Div(this=expression.this, expression=expression.expression), 4359 to=exp.DataType(this=exp.DType.INT), 4360 ) 4361 ) 4362 4363 def dpipe_sql(self, expression: exp.DPipe) -> str: 4364 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4365 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4366 return self.binary(expression, "||") 4367 4368 def div_sql(self, expression: exp.Div) -> str: 4369 l, r = expression.left, expression.right 4370 4371 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4372 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4373 4374 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4375 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4376 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4377 4378 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4379 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4380 return self.sql( 4381 exp.cast( 4382 l / r, 4383 to=exp.DType.BIGINT, 4384 ) 4385 ) 4386 4387 return self.binary(expression, "/") 4388 4389 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4390 n = exp._wrap(expression.this, exp.Binary) 4391 d = exp._wrap(expression.expression, exp.Binary) 4392 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4393 4394 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4395 return self.binary(expression, "OVERLAPS") 4396 4397 def distance_sql(self, expression: exp.Distance) -> str: 4398 return self.binary(expression, "<->") 4399 4400 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4401 return self.binary(expression, "<<->>") 4402 4403 def dot_sql(self, expression: exp.Dot) -> str: 4404 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4405 4406 def eq_sql(self, expression: exp.EQ) -> str: 4407 return self.binary(expression, "=") 4408 4409 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4410 return self.binary(expression, ":=") 4411 4412 def escape_sql(self, expression: exp.Escape) -> str: 4413 this = expression.this 4414 if ( 4415 isinstance(this, (exp.Like, exp.ILike)) 4416 and isinstance(this.expression, (exp.All, exp.Any)) 4417 and not self.SUPPORTS_LIKE_QUANTIFIERS 4418 ): 4419 return self._like_sql(this, escape=expression) 4420 return self.binary(expression, "ESCAPE") 4421 4422 def glob_sql(self, expression: exp.Glob) -> str: 4423 return self.binary(expression, "GLOB") 4424 4425 def gt_sql(self, expression: exp.GT) -> str: 4426 return self.binary(expression, ">") 4427 4428 def gte_sql(self, expression: exp.GTE) -> str: 4429 return self.binary(expression, ">=") 4430 4431 def is_sql(self, expression: exp.Is) -> str: 4432 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4433 return self.sql( 4434 expression.this if expression.expression.this else exp.not_(expression.this) 4435 ) 4436 return self.binary(expression, "IS") 4437 4438 def _like_sql( 4439 self, 4440 expression: exp.Like | exp.ILike, 4441 escape: exp.Escape | None = None, 4442 ) -> str: 4443 this = expression.this 4444 rhs = expression.expression 4445 4446 if isinstance(expression, exp.Like): 4447 exp_class: type[exp.Like | exp.ILike] = exp.Like 4448 op = "LIKE" 4449 else: 4450 exp_class = exp.ILike 4451 op = "ILIKE" 4452 4453 if expression.args.get("negate"): 4454 op = f"NOT {op}" 4455 4456 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4457 exprs = rhs.this.unnest() 4458 4459 if isinstance(exprs, exp.Tuple): 4460 exprs = exprs.expressions 4461 else: 4462 exprs = [exprs] 4463 4464 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4465 4466 def _make_like(expr: exp.Expression) -> exp.Expression: 4467 like: exp.Expression = exp_class( 4468 this=this, expression=expr, negate=expression.args.get("negate") 4469 ) 4470 if escape: 4471 like = exp.Escape(this=like, expression=escape.expression.copy()) 4472 return like 4473 4474 like_expr: exp.Expr = _make_like(exprs[0]) 4475 for expr in exprs[1:]: 4476 like_expr = connective(like_expr, _make_like(expr), copy=False) 4477 4478 parent = escape.parent if escape else expression.parent 4479 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4480 parent, exp.Condition 4481 ): 4482 like_expr = exp.paren(like_expr, copy=False) 4483 4484 return self.sql(like_expr) 4485 4486 return self.binary(expression, op) 4487 4488 def like_sql(self, expression: exp.Like) -> str: 4489 return self._like_sql(expression) 4490 4491 def ilike_sql(self, expression: exp.ILike) -> str: 4492 return self._like_sql(expression) 4493 4494 def match_sql(self, expression: exp.Match) -> str: 4495 return self.binary(expression, "MATCH") 4496 4497 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4498 return self.binary(expression, "SIMILAR TO") 4499 4500 def lt_sql(self, expression: exp.LT) -> str: 4501 return self.binary(expression, "<") 4502 4503 def lte_sql(self, expression: exp.LTE) -> str: 4504 return self.binary(expression, "<=") 4505 4506 def mod_sql(self, expression: exp.Mod) -> str: 4507 return self.binary(expression, "%") 4508 4509 def mul_sql(self, expression: exp.Mul) -> str: 4510 return self.binary(expression, "*") 4511 4512 def neq_sql(self, expression: exp.NEQ) -> str: 4513 return self.binary(expression, "<>") 4514 4515 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4516 return self.binary(expression, "IS NOT DISTINCT FROM") 4517 4518 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4519 return self.binary(expression, "IS DISTINCT FROM") 4520 4521 def sub_sql(self, expression: exp.Sub) -> str: 4522 return self.binary(expression, "-") 4523 4524 def trycast_sql(self, expression: exp.TryCast) -> str: 4525 return self.cast_sql(expression, safe_prefix="TRY_") 4526 4527 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4528 return self.cast_sql(expression) 4529 4530 def try_sql(self, expression: exp.Try) -> str: 4531 if not self.TRY_SUPPORTED: 4532 self.unsupported("Unsupported TRY function") 4533 return self.sql(expression, "this") 4534 4535 return self.func("TRY", expression.this) 4536 4537 def log_sql(self, expression: exp.Log) -> str: 4538 this = expression.this 4539 expr = expression.expression 4540 4541 if self.dialect.LOG_BASE_FIRST is False: 4542 this, expr = expr, this 4543 elif self.dialect.LOG_BASE_FIRST is None and expr: 4544 if this.name in ("2", "10"): 4545 return self.func(f"LOG{this.name}", expr) 4546 4547 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4548 4549 return self.func("LOG", this, expr) 4550 4551 def use_sql(self, expression: exp.Use) -> str: 4552 kind = self.sql(expression, "kind") 4553 kind = f" {kind}" if kind else "" 4554 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4555 this = f" {this}" if this else "" 4556 return f"USE{kind}{this}" 4557 4558 def binary(self, expression: exp.Binary, op: str) -> str: 4559 sqls: list[str] = [] 4560 stack: list[None | str | exp.Expr] = [expression] 4561 binary_type = type(expression) 4562 4563 while stack: 4564 node = stack.pop() 4565 4566 if type(node) is binary_type: 4567 op_func = node.args.get("operator") 4568 if op_func: 4569 op = f"OPERATOR({self.sql(op_func)})" 4570 4571 stack.append(node.args.get("expression")) 4572 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4573 stack.append(node.args.get("this")) 4574 else: 4575 sqls.append(self.sql(node)) 4576 4577 return "".join(sqls) 4578 4579 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4580 to_clause = self.sql(expression, "to") 4581 if to_clause: 4582 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4583 4584 return self.function_fallback_sql(expression) 4585 4586 def function_fallback_sql(self, expression: exp.Func) -> str: 4587 args = [] 4588 4589 for key in expression.arg_types: 4590 arg_value = expression.args.get(key) 4591 4592 if isinstance(arg_value, list): 4593 for value in arg_value: 4594 args.append(value) 4595 elif arg_value is not None: 4596 args.append(arg_value) 4597 4598 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4599 name = expression.meta_get("name") or expression.sql_name() 4600 else: 4601 name = expression.sql_name() 4602 4603 return self.func(name, *args) 4604 4605 def func( 4606 self, 4607 name: str, 4608 *args: t.Any, 4609 prefix: str = "(", 4610 suffix: str = ")", 4611 normalize: bool = True, 4612 ) -> str: 4613 name = self.normalize_func(name) if normalize else name 4614 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4615 4616 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4617 arg_sqls = tuple( 4618 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4619 ) 4620 if self.pretty and self.too_wide(arg_sqls): 4621 return self.indent( 4622 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4623 ) 4624 return sep.join(arg_sqls) 4625 4626 def too_wide(self, args: t.Iterable) -> bool: 4627 return sum(len(arg) for arg in args) > self.max_text_width 4628 4629 def format_time( 4630 self, 4631 expression: exp.Expr, 4632 inverse_time_mapping: dict[str, str] | None = None, 4633 inverse_time_trie: dict | None = None, 4634 ) -> str | None: 4635 return format_time( 4636 self.sql(expression, "format"), 4637 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4638 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4639 ) 4640 4641 def expressions( 4642 self, 4643 expression: exp.Expr | None = None, 4644 key: str | None = None, 4645 sqls: t.Collection[str | exp.Expr] | None = None, 4646 flat: bool = False, 4647 indent: bool = True, 4648 skip_first: bool = False, 4649 skip_last: bool = False, 4650 sep: str = ", ", 4651 prefix: str = "", 4652 dynamic: bool = False, 4653 new_line: bool = False, 4654 ) -> str: 4655 expressions = expression.args.get(key or "expressions") if expression else sqls 4656 4657 if not expressions: 4658 return "" 4659 4660 if flat: 4661 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4662 4663 num_sqls = len(expressions) 4664 result_sqls = [] 4665 4666 for i, e in enumerate(expressions): 4667 sql = self.sql(e, comment=False) 4668 if not sql: 4669 continue 4670 4671 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4672 4673 if self.pretty: 4674 if self.leading_comma: 4675 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4676 else: 4677 result_sqls.append( 4678 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4679 ) 4680 else: 4681 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4682 4683 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4684 if new_line: 4685 result_sqls.insert(0, "") 4686 result_sqls.append("") 4687 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4688 else: 4689 result_sql = "".join(result_sqls) 4690 4691 return ( 4692 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4693 if indent 4694 else result_sql 4695 ) 4696 4697 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4698 flat = flat or isinstance(expression.parent, exp.Properties) 4699 expressions_sql = self.expressions(expression, flat=flat) 4700 if flat: 4701 return f"{op} {expressions_sql}" 4702 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4703 4704 def naked_property(self, expression: exp.Property) -> str: 4705 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4706 if not property_name: 4707 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4708 return f"{property_name} {self.sql(expression, 'this')}" 4709 4710 def tag_sql(self, expression: exp.Tag) -> str: 4711 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4712 4713 def token_sql(self, token_type: TokenType) -> str: 4714 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4715 4716 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4717 this = self.sql(expression, "this") 4718 expressions = self.no_identify(self.expressions, expression) 4719 expressions = ( 4720 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4721 ) 4722 return f"{this}{expressions}" if expressions.strip() != "" else this 4723 4724 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4725 return self.expressions(expression, flat=True) 4726 4727 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4728 params = self.no_identify(self.expressions, expression, flat=True) 4729 body = self.sql(expression, "this") 4730 prefix = "TABLE " if expression.args.get("is_table") else "" 4731 return f"({params}) AS {prefix}{body}" 4732 4733 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4734 this = self.sql(expression, "this") 4735 expressions = self.expressions(expression, flat=True) 4736 return f"{this}({expressions})" 4737 4738 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4739 return self.binary(expression, "=>") 4740 4741 def when_sql(self, expression: exp.When) -> str: 4742 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4743 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4744 condition = self.sql(expression, "condition") 4745 condition = f" AND {condition}" if condition else "" 4746 4747 then_expression = expression.args.get("then") 4748 if isinstance(then_expression, exp.Insert): 4749 this = self.sql(then_expression, "this") 4750 this = f"INSERT {this}" if this else "INSERT" 4751 then = self.sql(then_expression, "expression") 4752 then = f"{this} VALUES {then}" if then else this 4753 elif isinstance(then_expression, exp.Update): 4754 if isinstance(then_expression.args.get("expressions"), exp.Star): 4755 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4756 else: 4757 expressions_sql = self.expressions(then_expression) 4758 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4759 else: 4760 then = self.sql(then_expression) 4761 4762 if isinstance(then_expression, (exp.Insert, exp.Update)): 4763 where = self.sql(then_expression, "where") 4764 if where and not self.SUPPORTS_MERGE_WHERE: 4765 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4766 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4767 where = "" 4768 then = f"{then}{where}" 4769 return f"WHEN {matched}{source}{condition} THEN {then}" 4770 4771 def whens_sql(self, expression: exp.Whens) -> str: 4772 return self.expressions(expression, sep=" ", indent=False) 4773 4774 def merge_sql(self, expression: exp.Merge) -> str: 4775 table = expression.this 4776 table_alias = "" 4777 4778 hints = table.args.get("hints") 4779 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4780 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4781 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4782 4783 this = self.sql(table) 4784 using = f"USING {self.sql(expression, 'using')}" 4785 whens = self.sql(expression, "whens") 4786 4787 on = self.sql(expression, "on") 4788 on = f"ON {on}" if on else "" 4789 4790 if not on: 4791 on = self.expressions(expression, key="using_cond") 4792 on = f"USING ({on})" if on else "" 4793 4794 returning = self.sql(expression, "returning") 4795 if returning: 4796 whens = f"{whens}{returning}" 4797 4798 sep = self.sep() 4799 4800 return self.prepend_ctes( 4801 expression, 4802 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4803 ) 4804 4805 @unsupported_args("format") 4806 def tochar_sql(self, expression: exp.ToChar) -> str: 4807 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4808 4809 @unsupported_args("default") 4810 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4811 if not self.SUPPORTS_TO_NUMBER: 4812 self.unsupported("Unsupported TO_NUMBER function") 4813 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4814 4815 fmt = expression.args.get("format") 4816 if not fmt: 4817 self.unsupported("Conversion format is required for TO_NUMBER") 4818 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4819 4820 return self.func("TO_NUMBER", expression.this, fmt) 4821 4822 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4823 this = self.sql(expression, "this") 4824 kind = self.sql(expression, "kind") 4825 settings_sql = self.expressions(expression, key="settings", sep=" ") 4826 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4827 return f"{this}({kind}{args})" 4828 4829 def dictrange_sql(self, expression: exp.DictRange) -> str: 4830 this = self.sql(expression, "this") 4831 max = self.sql(expression, "max") 4832 min = self.sql(expression, "min") 4833 return f"{this}(MIN {min} MAX {max})" 4834 4835 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4836 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4837 4838 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4839 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4840 4841 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4842 def uniquekeyproperty_sql( 4843 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4844 ) -> str: 4845 return f"{prefix} ({self.expressions(expression, flat=True)})" 4846 4847 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4848 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4849 expressions = self.expressions(expression, flat=True) 4850 expressions = f" {self.wrap(expressions)}" if expressions else "" 4851 buckets = self.sql(expression, "buckets") 4852 kind = self.sql(expression, "kind") 4853 buckets = f" BUCKETS {buckets}" if buckets else "" 4854 order = self.sql(expression, "order") 4855 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4856 4857 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4858 return "" 4859 4860 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4861 expressions = self.expressions(expression, key="expressions", flat=True) 4862 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4863 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4864 buckets = self.sql(expression, "buckets") 4865 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4866 4867 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4868 this = self.sql(expression, "this") 4869 having = self.sql(expression, "having") 4870 4871 if having: 4872 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4873 4874 return self.func("ANY_VALUE", this) 4875 4876 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4877 transform = self.func("TRANSFORM", *expression.expressions) 4878 row_format_before = self.sql(expression, "row_format_before") 4879 row_format_before = f" {row_format_before}" if row_format_before else "" 4880 record_writer = self.sql(expression, "record_writer") 4881 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4882 using = f" USING {self.sql(expression, 'command_script')}" 4883 schema = self.sql(expression, "schema") 4884 schema = f" AS {schema}" if schema else "" 4885 row_format_after = self.sql(expression, "row_format_after") 4886 row_format_after = f" {row_format_after}" if row_format_after else "" 4887 record_reader = self.sql(expression, "record_reader") 4888 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4889 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4890 4891 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4892 key_block_size = self.sql(expression, "key_block_size") 4893 if key_block_size: 4894 return f"KEY_BLOCK_SIZE = {key_block_size}" 4895 4896 using = self.sql(expression, "using") 4897 if using: 4898 return f"USING {using}" 4899 4900 parser = self.sql(expression, "parser") 4901 if parser: 4902 return f"WITH PARSER {parser}" 4903 4904 comment = self.sql(expression, "comment") 4905 if comment: 4906 return f"COMMENT {comment}" 4907 4908 visible = expression.args.get("visible") 4909 if visible is not None: 4910 return "VISIBLE" if visible else "INVISIBLE" 4911 4912 engine_attr = self.sql(expression, "engine_attr") 4913 if engine_attr: 4914 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4915 4916 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4917 if secondary_engine_attr: 4918 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4919 4920 self.unsupported("Unsupported index constraint option.") 4921 return "" 4922 4923 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4924 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4925 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4926 4927 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4928 kind = self.sql(expression, "kind") 4929 kind = f"{kind} INDEX" if kind else "INDEX" 4930 this = self.sql(expression, "this") 4931 this = f" {this}" if this else "" 4932 index_type = self.sql(expression, "index_type") 4933 index_type = f" USING {index_type}" if index_type else "" 4934 expressions = self.expressions(expression, flat=True) 4935 expressions = f" ({expressions})" if expressions else "" 4936 options = self.expressions(expression, key="options", sep=" ") 4937 options = f" {options}" if options else "" 4938 return f"{kind}{this}{index_type}{expressions}{options}" 4939 4940 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4941 if self.NVL2_SUPPORTED: 4942 return self.function_fallback_sql(expression) 4943 4944 case = exp.Case().when( 4945 expression.this.is_(exp.null()).not_(copy=False), 4946 expression.args["true"], 4947 copy=False, 4948 ) 4949 else_cond = expression.args.get("false") 4950 if else_cond: 4951 case.else_(else_cond, copy=False) 4952 4953 return self.sql(case) 4954 4955 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4956 this = self.sql(expression, "this") 4957 expr = self.sql(expression, "expression") 4958 position = self.sql(expression, "position") 4959 position = f", {position}" if position else "" 4960 iterator = self.sql(expression, "iterator") 4961 condition = self.sql(expression, "condition") 4962 condition = f" IF {condition}" if condition else "" 4963 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4964 4965 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4966 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4967 4968 def opclass_sql(self, expression: exp.Opclass) -> str: 4969 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4970 4971 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4972 model = self.sql(expression, "this") 4973 model = f"MODEL {model}" 4974 expr = expression.expression 4975 if expr: 4976 expr_sql = self.sql(expression, "expression") 4977 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4978 else: 4979 expr_sql = None 4980 4981 parameters = self.sql(expression, "params_struct") or None 4982 4983 return self.func(name, model, expr_sql, parameters) 4984 4985 def predict_sql(self, expression: exp.Predict) -> str: 4986 return self._ml_sql(expression, "PREDICT") 4987 4988 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4989 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4990 return self._ml_sql(expression, name) 4991 4992 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4993 return self._ml_sql(expression, "GENERATE_TEXT") 4994 4995 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4996 return self._ml_sql(expression, "GENERATE_TABLE") 4997 4998 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4999 return self._ml_sql(expression, "GENERATE_BOOL") 5000 5001 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5002 return self._ml_sql(expression, "GENERATE_INT") 5003 5004 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5005 return self._ml_sql(expression, "GENERATE_DOUBLE") 5006 5007 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5008 return self._ml_sql(expression, "TRANSLATE") 5009 5010 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5011 return self._ml_sql(expression, "FORECAST") 5012 5013 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5014 this_sql = self.sql(expression, "this") 5015 if isinstance(expression.this, exp.Table): 5016 this_sql = f"TABLE {this_sql}" 5017 5018 return self.func( 5019 "FORECAST", 5020 this_sql, 5021 expression.args.get("data_col"), 5022 expression.args.get("timestamp_col"), 5023 expression.args.get("model"), 5024 expression.args.get("id_cols"), 5025 expression.args.get("horizon"), 5026 expression.args.get("forecast_end_timestamp"), 5027 expression.args.get("confidence_level"), 5028 expression.args.get("output_historical_time_series"), 5029 expression.args.get("context_window"), 5030 ) 5031 5032 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5033 this_sql = self.sql(expression, "this") 5034 if isinstance(expression.this, exp.Table): 5035 this_sql = f"TABLE {this_sql}" 5036 5037 return self.func( 5038 "FEATURES_AT_TIME", 5039 this_sql, 5040 expression.args.get("time"), 5041 expression.args.get("num_rows"), 5042 expression.args.get("ignore_feature_nulls"), 5043 ) 5044 5045 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5046 this_sql = self.sql(expression, "this") 5047 if isinstance(expression.this, exp.Table): 5048 this_sql = f"TABLE {this_sql}" 5049 5050 query_table = self.sql(expression, "query_table") 5051 if isinstance(expression.args["query_table"], exp.Table): 5052 query_table = f"TABLE {query_table}" 5053 5054 return self.func( 5055 "VECTOR_SEARCH", 5056 this_sql, 5057 expression.args.get("column_to_search"), 5058 query_table, 5059 expression.args.get("query_column_to_search"), 5060 expression.args.get("top_k"), 5061 expression.args.get("distance_type"), 5062 expression.args.get("options"), 5063 ) 5064 5065 def forin_sql(self, expression: exp.ForIn) -> str: 5066 this = self.sql(expression, "this") 5067 expression_sql = self.sql(expression, "expression") 5068 return f"FOR {this} DO {expression_sql}" 5069 5070 def refresh_sql(self, expression: exp.Refresh) -> str: 5071 this = self.sql(expression, "this") 5072 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5073 return f"REFRESH {kind}{this}" 5074 5075 def toarray_sql(self, expression: exp.ToArray) -> str: 5076 arg = expression.this 5077 if not arg.type: 5078 import sqlglot.optimizer.annotate_types 5079 5080 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5081 5082 if arg.is_type(exp.DType.ARRAY): 5083 return self.sql(arg) 5084 5085 cond_for_null = arg.is_(exp.null()) 5086 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5087 5088 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5089 this = expression.this 5090 time_format = self.format_time(expression) 5091 5092 if time_format: 5093 return self.sql( 5094 exp.cast( 5095 exp.StrToTime(this=this, format=expression.args["format"]), 5096 exp.DType.TIME, 5097 ) 5098 ) 5099 5100 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5101 return self.sql(this) 5102 5103 return self.sql(exp.cast(this, exp.DType.TIME)) 5104 5105 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5106 this = expression.this 5107 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5108 return self.sql(this) 5109 5110 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5111 5112 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5113 this = expression.this 5114 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5115 return self.sql(this) 5116 5117 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5118 5119 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5120 this = expression.this 5121 time_format = self.format_time(expression) 5122 safe = expression.args.get("safe") 5123 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5124 return self.sql( 5125 exp.cast( 5126 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5127 exp.DType.DATE, 5128 ) 5129 ) 5130 5131 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5132 return self.sql(this) 5133 5134 if safe: 5135 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5136 5137 return self.sql(exp.cast(this, exp.DType.DATE)) 5138 5139 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5140 return self.sql( 5141 exp.func( 5142 "DATEDIFF", 5143 expression.this, 5144 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5145 "day", 5146 ) 5147 ) 5148 5149 def lastday_sql(self, expression: exp.LastDay) -> str: 5150 if self.LAST_DAY_SUPPORTS_DATE_PART: 5151 return self.function_fallback_sql(expression) 5152 5153 unit = expression.text("unit") 5154 if unit and unit != "MONTH": 5155 self.unsupported("Date parts are not supported in LAST_DAY.") 5156 5157 return self.func("LAST_DAY", expression.this) 5158 5159 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5160 import sqlglot.dialects.dialect 5161 5162 return self.func( 5163 "DATE_ADD", 5164 expression.this, 5165 expression.expression, 5166 sqlglot.dialects.dialect.unit_to_str(expression), 5167 ) 5168 5169 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5170 if self.CAN_IMPLEMENT_ARRAY_ANY: 5171 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5172 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5173 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5174 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5175 5176 import sqlglot.dialects.dialect 5177 5178 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5179 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5180 self.unsupported("ARRAY_ANY is unsupported") 5181 5182 return self.function_fallback_sql(expression) 5183 5184 def struct_sql(self, expression: exp.Struct) -> str: 5185 expression.set( 5186 "expressions", 5187 [ 5188 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5189 if isinstance(e, exp.PropertyEQ) 5190 else e 5191 for e in expression.expressions 5192 ], 5193 ) 5194 5195 return self.function_fallback_sql(expression) 5196 5197 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5198 low = self.sql(expression, "this") 5199 high = self.sql(expression, "expression") 5200 5201 return f"{low} TO {high}" 5202 5203 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5204 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5205 tables = f" {self.expressions(expression)}" 5206 5207 exists = " IF EXISTS" if expression.args.get("exists") else "" 5208 5209 on_cluster = self.sql(expression, "cluster") 5210 on_cluster = f" {on_cluster}" if on_cluster else "" 5211 5212 identity = self.sql(expression, "identity") 5213 identity = f" {identity} IDENTITY" if identity else "" 5214 5215 option = self.sql(expression, "option") 5216 option = f" {option}" if option else "" 5217 5218 partition = self.sql(expression, "partition") 5219 partition = f" {partition}" if partition else "" 5220 5221 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5222 5223 # This transpiles T-SQL's CONVERT function 5224 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5225 def convert_sql(self, expression: exp.Convert) -> str: 5226 to = expression.this 5227 value = expression.expression 5228 style = expression.args.get("style") 5229 safe = expression.args.get("safe") 5230 strict = expression.args.get("strict") 5231 5232 if not to or not value: 5233 return "" 5234 5235 # Retrieve length of datatype and override to default if not specified 5236 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5237 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5238 5239 transformed: exp.Expr | None = None 5240 cast = exp.Cast if strict else exp.TryCast 5241 5242 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5243 if isinstance(style, exp.Literal) and style.is_int: 5244 import sqlglot.dialects.tsql 5245 5246 style_value = style.name 5247 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5248 if not converted_style: 5249 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5250 5251 fmt = exp.Literal.string(converted_style) 5252 5253 if to.this == exp.DType.DATE: 5254 transformed = exp.StrToDate(this=value, format=fmt) 5255 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5256 transformed = exp.StrToTime(this=value, format=fmt) 5257 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5258 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5259 elif to.this == exp.DType.TEXT: 5260 transformed = exp.TimeToStr(this=value, format=fmt) 5261 5262 if not transformed: 5263 transformed = cast(this=value, to=to, safe=safe) 5264 5265 return self.sql(transformed) 5266 5267 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5268 this = expression.this 5269 if isinstance(this, exp.JSONPathWildcard): 5270 this = self.json_path_part(this) 5271 return f".{this}" if this else "" 5272 5273 quoted = expression.args.get("quoted") 5274 if not ( 5275 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5276 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5277 return f".{this}" 5278 5279 this = self.json_path_part(this) 5280 5281 if quoted and self.QUOTE_JSON_PATH: 5282 # The whole path is rendered as a single quoted string literal, so the bracketed key 5283 # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be 5284 # escaped again for the outer string literal (-> ["x \\"y\\"z"]). 5285 this = self.escape_str(this) 5286 5287 return ( 5288 f"[{this}]" 5289 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5290 else f".{this}" 5291 ) 5292 5293 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5294 this = self.json_path_part(expression.this) 5295 return f"[{this}]" if this else "" 5296 5297 def _simplify_unless_literal(self, expression: E) -> E: 5298 if not isinstance(expression, exp.Literal): 5299 import sqlglot.optimizer.simplify 5300 5301 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5302 5303 return expression 5304 5305 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5306 this = expression.this 5307 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5308 self.unsupported( 5309 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5310 ) 5311 return self.sql(this) 5312 5313 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5314 if self.IGNORE_NULLS_BEFORE_ORDER: 5315 # The first modifier here will be the one closest to the AggFunc's arg 5316 mods = sorted( 5317 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5318 key=lambda x: ( 5319 0 5320 if isinstance(x, exp.HavingMax) 5321 else (1 if isinstance(x, exp.Order) else 2) 5322 ), 5323 ) 5324 5325 if mods: 5326 mod = mods[0] 5327 this = expression.__class__(this=mod.this.copy()) 5328 this.meta["inline"] = True 5329 mod.this.replace(this) 5330 return self.sql(expression.this) 5331 5332 agg_func = expression.find(exp.AggFunc) 5333 5334 if agg_func: 5335 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5336 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5337 5338 return f"{self.sql(expression, 'this')} {text}" 5339 5340 def _replace_line_breaks(self, string: str) -> str: 5341 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5342 if self.pretty: 5343 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5344 return string 5345 5346 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5347 option = self.sql(expression, "this") 5348 5349 if expression.expressions: 5350 upper = option.upper() 5351 5352 # Snowflake FILE_FORMAT options are separated by whitespace 5353 sep = " " if upper == "FILE_FORMAT" else ", " 5354 5355 # Databricks copy/format options do not set their list of values with EQ 5356 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5357 values = self.expressions(expression, flat=True, sep=sep) 5358 return f"{option}{op}({values})" 5359 5360 value = self.sql(expression, "expression") 5361 5362 if not value: 5363 return option 5364 5365 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5366 5367 return f"{option}{op}{value}" 5368 5369 def credentials_sql(self, expression: exp.Credentials) -> str: 5370 cred_expr = expression.args.get("credentials") 5371 if isinstance(cred_expr, exp.Literal): 5372 # Redshift case: CREDENTIALS <string> 5373 credentials = self.sql(expression, "credentials") 5374 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5375 else: 5376 # Snowflake case: CREDENTIALS = (...) 5377 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5378 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5379 5380 storage = self.sql(expression, "storage") 5381 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5382 5383 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5384 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5385 5386 iam_role = self.sql(expression, "iam_role") 5387 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5388 5389 region = self.sql(expression, "region") 5390 region = f" REGION {region}" if region else "" 5391 5392 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5393 5394 def copy_sql(self, expression: exp.Copy) -> str: 5395 this = self.sql(expression, "this") 5396 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5397 5398 credentials = self.sql(expression, "credentials") 5399 credentials = self.seg(credentials) if credentials else "" 5400 files = self.expressions(expression, key="files", flat=True) 5401 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5402 5403 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5404 params = self.expressions( 5405 expression, 5406 key="params", 5407 sep=sep, 5408 new_line=True, 5409 skip_last=True, 5410 skip_first=True, 5411 indent=self.COPY_PARAMS_ARE_WRAPPED, 5412 ) 5413 5414 if params: 5415 if self.COPY_PARAMS_ARE_WRAPPED: 5416 params = f" WITH ({params})" 5417 elif not self.pretty and (files or credentials): 5418 params = f" {params}" 5419 5420 return f"COPY{this}{kind} {files}{credentials}{params}" 5421 5422 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5423 return "" 5424 5425 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5426 on_sql = "ON" if expression.args.get("on") else "OFF" 5427 filter_col: str | None = self.sql(expression, "filter_column") 5428 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5429 retention_period: str | None = self.sql(expression, "retention_period") 5430 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5431 5432 if filter_col or retention_period: 5433 on_sql = self.func("ON", filter_col, retention_period) 5434 5435 return f"DATA_DELETION={on_sql}" 5436 5437 def maskingpolicycolumnconstraint_sql( 5438 self, expression: exp.MaskingPolicyColumnConstraint 5439 ) -> str: 5440 this = self.sql(expression, "this") 5441 expressions = self.expressions(expression, flat=True) 5442 expressions = f" USING ({expressions})" if expressions else "" 5443 return f"MASKING POLICY {this}{expressions}" 5444 5445 def gapfill_sql(self, expression: exp.GapFill) -> str: 5446 this = self.sql(expression, "this") 5447 this = f"TABLE {this}" 5448 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5449 5450 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5451 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5452 5453 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5454 this = self.sql(expression, "this") 5455 expr = expression.expression 5456 5457 if isinstance(expr, exp.Func): 5458 # T-SQL's CLR functions are case sensitive 5459 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5460 else: 5461 expr = self.sql(expression, "expression") 5462 5463 return self.scope_resolution(expr, this) 5464 5465 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5466 if self.PARSE_JSON_NAME is None: 5467 return self.sql(expression.this) 5468 5469 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5470 5471 def rand_sql(self, expression: exp.Rand) -> str: 5472 lower = self.sql(expression, "lower") 5473 upper = self.sql(expression, "upper") 5474 5475 if lower and upper: 5476 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5477 return self.func("RAND", expression.this) 5478 5479 def changes_sql(self, expression: exp.Changes) -> str: 5480 information = self.sql(expression, "information") 5481 information = f"INFORMATION => {information}" 5482 at_before = self.sql(expression, "at_before") 5483 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5484 end = self.sql(expression, "end") 5485 end = f"{self.seg('')}{end}" if end else "" 5486 5487 return f"CHANGES ({information}){at_before}{end}" 5488 5489 def pad_sql(self, expression: exp.Pad) -> str: 5490 prefix = "L" if expression.args.get("is_left") else "R" 5491 5492 fill_pattern = self.sql(expression, "fill_pattern") or None 5493 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5494 fill_pattern = "' '" 5495 5496 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5497 5498 def summarize_sql(self, expression: exp.Summarize) -> str: 5499 table = " TABLE" if expression.args.get("table") else "" 5500 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5501 5502 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5503 generate_series = exp.GenerateSeries(**expression.args) 5504 5505 parent = expression.parent 5506 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5507 parent = parent.parent 5508 5509 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5510 return self.sql(exp.Unnest(expressions=[generate_series])) 5511 5512 if isinstance(parent, exp.Select): 5513 self.unsupported("GenerateSeries projection unnesting is not supported.") 5514 5515 return self.sql(generate_series) 5516 5517 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5518 if self.SUPPORTS_CONVERT_TIMEZONE: 5519 return self.function_fallback_sql(expression) 5520 5521 source_tz = expression.args.get("source_tz") 5522 target_tz = expression.args.get("target_tz") 5523 timestamp = expression.args.get("timestamp") 5524 5525 if source_tz and timestamp: 5526 timestamp = exp.AtTimeZone( 5527 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5528 ) 5529 5530 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5531 5532 return self.sql(expr) 5533 5534 def json_sql(self, expression: exp.JSON) -> str: 5535 this = self.sql(expression, "this") 5536 this = f" {this}" if this else "" 5537 5538 _with = expression.args.get("with_") 5539 5540 if _with is None: 5541 with_sql = "" 5542 elif not _with: 5543 with_sql = " WITHOUT" 5544 else: 5545 with_sql = " WITH" 5546 5547 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5548 5549 return f"JSON{this}{with_sql}{unique_sql}" 5550 5551 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5552 path = self.sql(expression, "path") 5553 returning = self.sql(expression, "returning") 5554 returning = f" RETURNING {returning}" if returning else "" 5555 5556 on_condition = self.sql(expression, "on_condition") 5557 on_condition = f" {on_condition}" if on_condition else "" 5558 5559 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5560 5561 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5562 regexp = " REGEXP" if expression.args.get("regexp") else "" 5563 return f"SKIP{regexp} {self.sql(expression.expression)}" 5564 5565 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5566 else_ = "ELSE " if expression.args.get("else_") else "" 5567 condition = self.sql(expression, "expression") 5568 condition = f"WHEN {condition} THEN " if condition else else_ 5569 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5570 return f"{condition}{insert}" 5571 5572 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5573 kind = self.sql(expression, "kind") 5574 expressions = self.seg(self.expressions(expression, sep=" ")) 5575 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5576 return res 5577 5578 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5579 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5580 empty = expression.args.get("empty") 5581 empty = ( 5582 f"DEFAULT {empty} ON EMPTY" 5583 if isinstance(empty, exp.Expr) 5584 else self.sql(expression, "empty") 5585 ) 5586 5587 error = expression.args.get("error") 5588 error = ( 5589 f"DEFAULT {error} ON ERROR" 5590 if isinstance(error, exp.Expr) 5591 else self.sql(expression, "error") 5592 ) 5593 5594 if error and empty: 5595 error = ( 5596 f"{empty} {error}" 5597 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5598 else f"{error} {empty}" 5599 ) 5600 empty = "" 5601 5602 null = self.sql(expression, "null") 5603 5604 return f"{empty}{error}{null}" 5605 5606 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5607 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5608 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5609 5610 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5611 this = self.sql(expression, "this") 5612 path = self.sql(expression, "path") 5613 5614 passing = self.expressions(expression, "passing") 5615 passing = f" PASSING {passing}" if passing else "" 5616 5617 on_condition = self.sql(expression, "on_condition") 5618 on_condition = f" {on_condition}" if on_condition else "" 5619 5620 path = f"{path}{passing}{on_condition}" 5621 5622 return self.func("JSON_EXISTS", this, path) 5623 5624 def _add_arrayagg_null_filter( 5625 self, 5626 array_agg_sql: str, 5627 array_agg_expr: exp.ArrayAgg, 5628 column_expr: exp.Expr, 5629 ) -> str: 5630 """ 5631 Add NULL filter to ARRAY_AGG if dialect requires it. 5632 5633 Args: 5634 array_agg_sql: The generated ARRAY_AGG SQL string 5635 array_agg_expr: The ArrayAgg expression node 5636 column_expr: The column/expression to filter (before ORDER BY wrapping) 5637 5638 Returns: 5639 SQL string with FILTER clause added if needed 5640 """ 5641 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5642 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5643 if not ( 5644 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5645 ): 5646 return array_agg_sql 5647 5648 parent = array_agg_expr.parent 5649 if isinstance(parent, exp.Filter): 5650 parent_cond = parent.expression.this 5651 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5652 elif column_expr.find(exp.Column): 5653 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5654 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5655 this_sql = ( 5656 self.expressions(column_expr) 5657 if isinstance(column_expr, exp.Distinct) 5658 else self.sql(column_expr) 5659 ) 5660 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5661 5662 return array_agg_sql 5663 5664 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5665 array_agg = self.function_fallback_sql(expression) 5666 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5667 5668 def slice_sql(self, expression: exp.Slice) -> str: 5669 step = self.sql(expression, "step") 5670 end = self.sql(expression.expression) 5671 begin = self.sql(expression.this) 5672 5673 sql = f"{end}:{step}" if step else end 5674 return f"{begin}:{sql}" if sql else f"{begin}:" 5675 5676 def apply_sql(self, expression: exp.Apply) -> str: 5677 this = self.sql(expression, "this") 5678 expr = self.sql(expression, "expression") 5679 5680 return f"{this} APPLY({expr})" 5681 5682 def _grant_or_revoke_sql( 5683 self, 5684 expression: exp.Grant | exp.Revoke, 5685 keyword: str, 5686 preposition: str, 5687 grant_option_prefix: str = "", 5688 grant_option_suffix: str = "", 5689 ) -> str: 5690 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5691 5692 kind = self.sql(expression, "kind") 5693 kind = f" {kind}" if kind else "" 5694 5695 securable = self.sql(expression, "securable") 5696 securable = f" {securable}" if securable else "" 5697 5698 principals = self.expressions(expression, key="principals", flat=True) 5699 5700 if not expression.args.get("grant_option"): 5701 grant_option_prefix = grant_option_suffix = "" 5702 5703 # cascade for revoke only 5704 cascade = self.sql(expression, "cascade") 5705 cascade = f" {cascade}" if cascade else "" 5706 5707 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5708 5709 def grant_sql(self, expression: exp.Grant) -> str: 5710 return self._grant_or_revoke_sql( 5711 expression, 5712 keyword="GRANT", 5713 preposition="TO", 5714 grant_option_suffix=" WITH GRANT OPTION", 5715 ) 5716 5717 def revoke_sql(self, expression: exp.Revoke) -> str: 5718 return self._grant_or_revoke_sql( 5719 expression, 5720 keyword="REVOKE", 5721 preposition="FROM", 5722 grant_option_prefix="GRANT OPTION FOR ", 5723 ) 5724 5725 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5726 this = self.sql(expression, "this") 5727 columns = self.expressions(expression, flat=True) 5728 columns = f"({columns})" if columns else "" 5729 5730 return f"{this}{columns}" 5731 5732 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5733 this = self.sql(expression, "this") 5734 5735 kind = self.sql(expression, "kind") 5736 kind = f"{kind} " if kind else "" 5737 5738 return f"{kind}{this}" 5739 5740 def columns_sql(self, expression: exp.Columns) -> str: 5741 func = self.function_fallback_sql(expression) 5742 if expression.args.get("unpack"): 5743 func = f"*{func}" 5744 5745 return func 5746 5747 def overlay_sql(self, expression: exp.Overlay) -> str: 5748 this = self.sql(expression, "this") 5749 expr = self.sql(expression, "expression") 5750 from_sql = self.sql(expression, "from_") 5751 for_sql = self.sql(expression, "for_") 5752 for_sql = f" FOR {for_sql}" if for_sql else "" 5753 5754 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5755 5756 @unsupported_args("format") 5757 def todouble_sql(self, expression: exp.ToDouble) -> str: 5758 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5759 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5760 5761 def string_sql(self, expression: exp.String) -> str: 5762 this = expression.this 5763 zone = expression.args.get("zone") 5764 5765 if zone: 5766 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5767 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5768 # set for source_tz to transpile the time conversion before the STRING cast 5769 this = exp.ConvertTimezone( 5770 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5771 ) 5772 5773 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5774 5775 def median_sql(self, expression: exp.Median) -> str: 5776 if not self.SUPPORTS_MEDIAN: 5777 return self.sql( 5778 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5779 ) 5780 5781 return self.function_fallback_sql(expression) 5782 5783 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5784 filler = self.sql(expression, "this") 5785 filler = f" {filler}" if filler else "" 5786 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5787 return f"TRUNCATE{filler} {with_count}" 5788 5789 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5790 if self.SUPPORTS_UNIX_SECONDS: 5791 return self.function_fallback_sql(expression) 5792 5793 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5794 5795 return self.sql( 5796 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5797 ) 5798 5799 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5800 dim = expression.expression 5801 5802 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5803 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5804 if not (dim.is_int and dim.name == "1"): 5805 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5806 dim = None 5807 5808 # If dimension is required but not specified, default initialize it 5809 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5810 dim = exp.Literal.number(1) 5811 5812 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5813 5814 def attach_sql(self, expression: exp.Attach) -> str: 5815 this = self.sql(expression, "this") 5816 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5817 expressions = self.expressions(expression) 5818 expressions = f" ({expressions})" if expressions else "" 5819 5820 return f"ATTACH{exists_sql} {this}{expressions}" 5821 5822 def detach_sql(self, expression: exp.Detach) -> str: 5823 kind = self.sql(expression, "kind") 5824 kind = f" {kind}" if kind else "" 5825 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5826 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5827 exists = " IF EXISTS" if expression.args.get("exists") else "" 5828 if exists: 5829 kind = kind or " DATABASE" 5830 5831 this = self.sql(expression, "this") 5832 this = f" {this}" if this else "" 5833 cluster = self.sql(expression, "cluster") 5834 cluster = f" {cluster}" if cluster else "" 5835 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5836 sync = " SYNC" if expression.args.get("sync") else "" 5837 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5838 5839 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5840 this = self.sql(expression, "this") 5841 value = self.sql(expression, "expression") 5842 value = f" {value}" if value else "" 5843 return f"{this}{value}" 5844 5845 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5846 return ( 5847 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5848 ) 5849 5850 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5851 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5852 encode = f"{encode} {self.sql(expression, 'this')}" 5853 5854 properties = expression.args.get("properties") 5855 if properties: 5856 encode = f"{encode} {self.properties(properties)}" 5857 5858 return encode 5859 5860 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5861 this = self.sql(expression, "this") 5862 include = f"INCLUDE {this}" 5863 5864 column_def = self.sql(expression, "column_def") 5865 if column_def: 5866 include = f"{include} {column_def}" 5867 5868 alias = self.sql(expression, "alias") 5869 if alias: 5870 include = f"{include} AS {alias}" 5871 5872 return include 5873 5874 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5875 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5876 name = f"{prefix} {self.sql(expression, 'this')}" 5877 return self.func("XMLELEMENT", name, *expression.expressions) 5878 5879 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5880 this = self.sql(expression, "this") 5881 expr = self.sql(expression, "expression") 5882 expr = f"({expr})" if expr else "" 5883 return f"{this}{expr}" 5884 5885 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5886 partitions = self.expressions(expression, "partition_expressions") 5887 create = self.expressions(expression, "create_expressions") 5888 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5889 5890 def partitionbyrangepropertydynamic_sql( 5891 self, expression: exp.PartitionByRangePropertyDynamic 5892 ) -> str: 5893 start = self.sql(expression, "start") 5894 end = self.sql(expression, "end") 5895 5896 every = expression.args["every"] 5897 if isinstance(every, exp.Interval) and every.this.is_string: 5898 every.this.replace(exp.Literal.number(every.name)) 5899 5900 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5901 5902 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5903 name = self.sql(expression, "this") 5904 values = self.expressions(expression, flat=True) 5905 5906 return f"NAME {name} VALUE {values}" 5907 5908 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5909 kind = self.sql(expression, "kind") 5910 sample = self.sql(expression, "sample") 5911 return f"SAMPLE {sample} {kind}" 5912 5913 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5914 kind = self.sql(expression, "kind") 5915 option = self.sql(expression, "option") 5916 option = f" {option}" if option else "" 5917 this = self.sql(expression, "this") 5918 this = f" {this}" if this else "" 5919 columns = self.expressions(expression) 5920 columns = f" {columns}" if columns else "" 5921 return f"{kind}{option} STATISTICS{this}{columns}" 5922 5923 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5924 this = self.sql(expression, "this") 5925 columns = self.expressions(expression) 5926 inner_expression = self.sql(expression, "expression") 5927 inner_expression = f" {inner_expression}" if inner_expression else "" 5928 update_options = self.sql(expression, "update_options") 5929 update_options = f" {update_options} UPDATE" if update_options else "" 5930 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5931 5932 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5933 kind = self.sql(expression, "kind") 5934 kind = f" {kind}" if kind else "" 5935 return f"DELETE{kind} STATISTICS" 5936 5937 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5938 inner_expression = self.sql(expression, "expression") 5939 return f"LIST CHAINED ROWS{inner_expression}" 5940 5941 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5942 kind = self.sql(expression, "kind") 5943 this = self.sql(expression, "this") 5944 this = f" {this}" if this else "" 5945 inner_expression = self.sql(expression, "expression") 5946 return f"VALIDATE {kind}{this}{inner_expression}" 5947 5948 def analyze_sql(self, expression: exp.Analyze) -> str: 5949 options = self.expressions(expression, key="options", sep=" ") 5950 options = f" {options}" if options else "" 5951 kind = self.sql(expression, "kind") 5952 kind = f" {kind}" if kind else "" 5953 this = self.sql(expression, "this") 5954 this = f" {this}" if this else "" 5955 mode = self.sql(expression, "mode") 5956 mode = f" {mode}" if mode else "" 5957 properties = self.sql(expression, "properties") 5958 properties = f" {properties}" if properties else "" 5959 partition = self.sql(expression, "partition") 5960 partition = f" {partition}" if partition else "" 5961 inner_expression = self.sql(expression, "expression") 5962 inner_expression = f" {inner_expression}" if inner_expression else "" 5963 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5964 5965 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5966 this = self.sql(expression, "this") 5967 namespaces = self.expressions(expression, key="namespaces") 5968 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5969 passing = self.expressions(expression, key="passing") 5970 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5971 columns = self.expressions(expression, key="columns") 5972 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5973 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5974 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5975 5976 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5977 this = self.sql(expression, "this") 5978 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5979 5980 def export_sql(self, expression: exp.Export) -> str: 5981 this = self.sql(expression, "this") 5982 connection = self.sql(expression, "connection") 5983 connection = f"WITH CONNECTION {connection} " if connection else "" 5984 options = self.sql(expression, "options") 5985 return f"EXPORT DATA {connection}{options} AS {this}" 5986 5987 def declare_sql(self, expression: exp.Declare) -> str: 5988 replace = "OR REPLACE " if expression.args.get("replace") else "" 5989 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5990 5991 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5992 variables = self.expressions(expression, "this") 5993 default = self.sql(expression, "default") 5994 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5995 5996 kind = self.sql(expression, "kind") 5997 if isinstance(expression.args.get("kind"), exp.Schema): 5998 kind = f"TABLE {kind}" 5999 6000 kind = f" {kind}" if kind else "" 6001 6002 return f"{variables}{kind}{default}" 6003 6004 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6005 kind = self.sql(expression, "kind") 6006 this = self.sql(expression, "this") 6007 set = self.sql(expression, "expression") 6008 using = self.sql(expression, "using") 6009 using = f" USING {using}" if using else "" 6010 6011 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6012 6013 return f"{kind_sql} {this} SET {set}{using}" 6014 6015 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6016 params = self.expressions(expression, key="params", flat=True) 6017 return self.func(expression.name, *expression.expressions) + f"({params})" 6018 6019 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6020 return self.func(expression.name, *expression.expressions) 6021 6022 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6023 return self.anonymousaggfunc_sql(expression) 6024 6025 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6026 return self.parameterizedagg_sql(expression) 6027 6028 def show_sql(self, expression: exp.Show) -> str: 6029 self.unsupported("Unsupported SHOW statement") 6030 return "" 6031 6032 def install_sql(self, expression: exp.Install) -> str: 6033 self.unsupported("Unsupported INSTALL statement") 6034 return "" 6035 6036 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6037 # Snowflake GET/PUT statements: 6038 # PUT <file> <internalStage> <properties> 6039 # GET <internalStage> <file> <properties> 6040 props = expression.args.get("properties") 6041 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6042 this = self.sql(expression, "this") 6043 target = self.sql(expression, "target") 6044 6045 if isinstance(expression, exp.Put): 6046 return f"PUT {this} {target}{props_sql}" 6047 else: 6048 return f"GET {target} {this}{props_sql}" 6049 6050 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6051 this = self.sql(expression, "this") 6052 expr = self.sql(expression, "expression") 6053 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6054 return f"TRANSLATE({this} USING {expr}{with_error})" 6055 6056 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6057 if self.SUPPORTS_DECODE_CASE: 6058 return self.func("DECODE", *expression.expressions) 6059 6060 decode_expr, *expressions = expression.expressions 6061 6062 ifs = [] 6063 for search, result in zip(expressions[::2], expressions[1::2]): 6064 if isinstance(search, exp.Literal): 6065 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6066 elif isinstance(search, exp.Null): 6067 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6068 else: 6069 if isinstance(search, exp.Binary): 6070 search = exp.paren(search) 6071 6072 cond = exp.or_( 6073 decode_expr.eq(search), 6074 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6075 copy=False, 6076 ) 6077 ifs.append(exp.If(this=cond, true=result)) 6078 6079 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6080 return self.sql(case) 6081 6082 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6083 this = self.sql(expression, "this") 6084 this = self.seg(this, sep="") 6085 dimensions = self.expressions( 6086 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6087 ) 6088 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6089 metrics = self.expressions( 6090 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6091 ) 6092 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6093 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6094 facts = self.seg(f"FACTS {facts}") if facts else "" 6095 where = self.sql(expression, "where") 6096 where = self.seg(f"WHERE {where}") if where else "" 6097 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6098 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6099 6100 def getextract_sql(self, expression: exp.GetExtract) -> str: 6101 this = expression.this 6102 expr = expression.expression 6103 6104 if not this.type or not expression.type: 6105 import sqlglot.optimizer.annotate_types 6106 6107 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6108 6109 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6110 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6111 6112 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6113 6114 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6115 return self.sql( 6116 exp.DateAdd( 6117 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6118 expression=expression.this, 6119 unit=exp.var("DAY"), 6120 ) 6121 ) 6122 6123 def space_sql(self: Generator, expression: exp.Space) -> str: 6124 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6125 6126 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6127 return f"BUILD {self.sql(expression, 'this')}" 6128 6129 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6130 method = self.sql(expression, "method") 6131 kind = expression.args.get("kind") 6132 if not kind: 6133 return f"REFRESH {method}" 6134 6135 every = self.sql(expression, "every") 6136 unit = self.sql(expression, "unit") 6137 every = f" EVERY {every} {unit}" if every else "" 6138 starts = self.sql(expression, "starts") 6139 starts = f" STARTS {starts}" if starts else "" 6140 6141 return f"REFRESH {method} ON {kind}{every}{starts}" 6142 6143 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6144 self.unsupported("The model!attribute syntax is not supported") 6145 return "" 6146 6147 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6148 return self.func("DIRECTORY", expression.this) 6149 6150 def uuid_sql(self, expression: exp.Uuid) -> str: 6151 is_string = expression.args.get("is_string", False) 6152 uuid_func_sql = self.func("UUID") 6153 6154 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6155 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6156 6157 return uuid_func_sql 6158 6159 def initcap_sql(self, expression: exp.Initcap) -> str: 6160 delimiters = expression.expression 6161 6162 if delimiters: 6163 # do not generate delimiters arg if we are round-tripping from default delimiters 6164 if ( 6165 delimiters.is_string 6166 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6167 ): 6168 delimiters = None 6169 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6170 self.unsupported("INITCAP does not support custom delimiters") 6171 delimiters = None 6172 6173 return self.func("INITCAP", expression.this, delimiters) 6174 6175 def localtime_sql(self, expression: exp.Localtime) -> str: 6176 this = expression.this 6177 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6178 6179 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6180 this = expression.this 6181 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6182 6183 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6184 this = expression.this.name.upper() 6185 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 6186 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 6187 return "WEEK" 6188 6189 return self.func("WEEK", expression.this) 6190 6191 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6192 this = self.expressions(expression) 6193 charset = self.sql(expression, "charset") 6194 using = f" USING {charset}" if charset else "" 6195 return self.func(name, this + using) 6196 6197 def block_sql(self, expression: exp.Block) -> str: 6198 expressions = self.expressions(expression, sep="; ", flat=True) 6199 return f"{expressions}" if expressions else "" 6200 6201 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6202 self.unsupported("Unsupported Stored Procedure syntax") 6203 return "" 6204 6205 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6206 self.unsupported("Unsupported If block syntax") 6207 return "" 6208 6209 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6210 self.unsupported("Unsupported While block syntax") 6211 return "" 6212 6213 def execute_sql(self, expression: exp.Execute) -> str: 6214 self.unsupported("Unsupported Execute syntax") 6215 return "" 6216 6217 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6218 self.unsupported("Unsupported Execute syntax") 6219 return "" 6220 6221 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6222 props = self.expressions(expression, sep=" ") 6223 return f"MODIFY {props}" 6224 6225 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6226 kind = expression.args.get("kind") 6227 return f"USING {kind} {self.sql(expression, 'this')}" 6228 6229 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6230 this = self.sql(expression, "this") 6231 to = self.sql(expression, "to") 6232 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)
868 def __init__( 869 self, 870 pretty: bool | int | None = None, 871 identify: str | bool = False, 872 normalize: bool = False, 873 pad: int = 2, 874 indent: int = 2, 875 normalize_functions: str | bool | None = None, 876 unsupported_level: ErrorLevel = ErrorLevel.WARN, 877 max_unsupported: int = 3, 878 leading_comma: bool = False, 879 max_text_width: int = 80, 880 comments: bool = True, 881 dialect: DialectType = None, 882 ): 883 import sqlglot 884 import sqlglot.dialects.dialect 885 886 self.pretty = pretty if pretty is not None else sqlglot.pretty 887 self.identify = identify 888 self.normalize = normalize 889 self.pad = pad 890 self._indent = indent 891 self.unsupported_level = unsupported_level 892 self.max_unsupported = max_unsupported 893 self.leading_comma = leading_comma 894 self.max_text_width = max_text_width 895 self.comments = comments 896 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 897 898 # This is both a Dialect property and a Generator argument, so we prioritize the latter 899 self.normalize_functions = ( 900 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 901 ) 902 903 self.unsupported_messages: list[str] = [] 904 self._escaped_quote_end: str = ( 905 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 906 ) 907 self._escaped_byte_quote_end: str = ( 908 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 909 if self.dialect.BYTE_END 910 else "" 911 ) 912 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 913 914 self._next_name = name_sequence("_t") 915 916 self._identifier_start = self.dialect.IDENTIFIER_START 917 self._identifier_end = self.dialect.IDENTIFIER_END 918 919 self._quote_json_path_key_using_brackets = True 920 921 cls = type(self) 922 dispatch = _DISPATCH_CACHE.get(cls) 923 if dispatch is None: 924 dispatch = _build_dispatch(cls) 925 _DISPATCH_CACHE[cls] = dispatch 926 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.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.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] =
()
SUPPORTED_JSON_PATH_PARTS: ClassVar =
{<class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.NCHAR: 'NCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.CHAR: 'CHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
928 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 929 """ 930 Generates the SQL string corresponding to the given syntax tree. 931 932 Args: 933 expression: The syntax tree. 934 copy: Whether to copy the expression. The generator performs mutations so 935 it is safer to copy. 936 937 Returns: 938 The SQL string corresponding to `expression`. 939 """ 940 if copy: 941 expression = expression.copy() 942 943 expression = self.preprocess(expression) 944 945 self.unsupported_messages = [] 946 sql = self.sql(expression).strip() 947 948 if self.pretty: 949 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 950 951 if self.unsupported_level == ErrorLevel.IGNORE: 952 return sql 953 954 if self.unsupported_level == ErrorLevel.WARN: 955 for msg in self.unsupported_messages: 956 logger.warning(msg) 957 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 958 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 959 960 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.
962 def preprocess(self, expression: exp.Expr) -> exp.Expr: 963 """Apply generic preprocessing transformations to a given expression.""" 964 expression = self._move_ctes_to_top_level(expression) 965 966 if self.ENSURE_BOOLS: 967 import sqlglot.transforms 968 969 expression = sqlglot.transforms.ensure_bools(expression) 970 971 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
995 def sanitize_comment(self, comment: str) -> str: 996 comment = " " + comment if comment[0].strip() else comment 997 comment = comment + " " if comment[-1].strip() else comment 998 999 # Escape block comment markers to prevent premature closure or unintended nesting. 1000 # This is necessary because single-line comments (--) are converted to block comments 1001 # (/* */) on output, and any */ in the original text would close the comment early. 1002 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1003 1004 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1006 def maybe_comment( 1007 self, 1008 sql: str, 1009 expression: exp.Expr | None = None, 1010 comments: list[str] | None = None, 1011 separated: bool = False, 1012 ) -> str: 1013 comments = ( 1014 ((expression and expression.comments) if comments is None else comments) # type: ignore 1015 if self.comments 1016 else None 1017 ) 1018 1019 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1020 return sql 1021 1022 comments_list = [ 1023 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1024 for comment in comments 1025 if comment 1026 ] 1027 1028 if not comments_list: 1029 return sql 1030 1031 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1032 comments_sql = self.sep().join(comments_list) 1033 return ( 1034 f"{self.sep()}{comments_sql}{sql}" 1035 if not sql or sql[0].isspace() 1036 else f"{comments_sql}{self.sep()}{sql}" 1037 ) 1038 1039 return f"{sql} {' '.join(comments_list)}"
1041 def wrap(self, expression: exp.Expr | str) -> str: 1042 this_sql = ( 1043 self.sql(expression) 1044 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1045 else self.sql(expression, "this") 1046 ) 1047 if not this_sql: 1048 return "()" 1049 1050 this_sql = self.indent(this_sql, level=1, pad=0) 1051 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:
1067 def indent( 1068 self, 1069 sql: str, 1070 level: int = 0, 1071 pad: int | None = None, 1072 skip_first: bool = False, 1073 skip_last: bool = False, 1074 ) -> str: 1075 if not self.pretty or not sql: 1076 return sql 1077 1078 pad = self.pad if pad is None else pad 1079 lines = sql.split("\n") 1080 1081 return "\n".join( 1082 ( 1083 line 1084 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1085 else f"{' ' * (level * self._indent + pad)}{line}" 1086 ) 1087 for i, line in enumerate(lines) 1088 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1090 def sql( 1091 self, 1092 expression: str | exp.Expr | None, 1093 key: str | None = None, 1094 comment: bool = True, 1095 ) -> str: 1096 if not expression: 1097 return "" 1098 1099 if isinstance(expression, str): 1100 return expression 1101 1102 if key: 1103 value = expression.args.get(key) 1104 if value: 1105 return self.sql(value) 1106 return "" 1107 1108 handler = self._dispatch.get(expression.__class__) 1109 1110 if handler: 1111 sql = handler(self, expression) 1112 elif isinstance(expression, exp.Func): 1113 sql = self.function_fallback_sql(expression) 1114 elif isinstance(expression, exp.Property): 1115 sql = self.property_sql(expression) 1116 else: 1117 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1118 1119 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1126 def cache_sql(self, expression: exp.Cache) -> str: 1127 lazy = " LAZY" if expression.args.get("lazy") else "" 1128 table = self.sql(expression, "this") 1129 options = expression.args.get("options") 1130 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1131 sql = self.sql(expression, "expression") 1132 sql = f" AS{self.sep()}{sql}" if sql else "" 1133 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1134 return self.prepend_ctes(expression, sql)
1152 def column_sql(self, expression: exp.Column) -> str: 1153 join_mark = " (+)" if expression.args.get("join_mark") else "" 1154 1155 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1156 join_mark = "" 1157 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1158 1159 return f"{self.column_parts(expression)}{join_mark}"
1170 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1171 column = self.sql(expression, "this") 1172 kind = self.sql(expression, "kind") 1173 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1174 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1175 kind = f"{sep}{kind}" if kind else "" 1176 constraints = f" {constraints}" if constraints else "" 1177 position = self.sql(expression, "position") 1178 position = f" {position}" if position else "" 1179 1180 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1181 kind = "" 1182 1183 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:
1190 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1191 this = self.sql(expression, "this") 1192 if expression.args.get("not_null"): 1193 persisted = " PERSISTED NOT NULL" 1194 elif expression.args.get("persisted"): 1195 persisted = " PERSISTED" 1196 else: 1197 persisted = "" 1198 1199 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:
1212 def generatedasidentitycolumnconstraint_sql( 1213 self, expression: exp.GeneratedAsIdentityColumnConstraint 1214 ) -> str: 1215 this = "" 1216 if expression.this is not None: 1217 on_null = " ON NULL" if expression.args.get("on_null") else "" 1218 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1219 1220 start = expression.args.get("start") 1221 start = f"START WITH {start}" if start else "" 1222 increment = expression.args.get("increment") 1223 increment = f" INCREMENT BY {increment}" if increment else "" 1224 minvalue = expression.args.get("minvalue") 1225 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1226 maxvalue = expression.args.get("maxvalue") 1227 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1228 cycle = expression.args.get("cycle") 1229 cycle_sql = "" 1230 1231 if cycle is not None: 1232 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1233 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1234 1235 sequence_opts = "" 1236 if start or increment or cycle_sql: 1237 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1238 sequence_opts = f" ({sequence_opts.strip()})" 1239 1240 expr = self.sql(expression, "expression") 1241 expr = f"({expr})" if expr else "IDENTITY" 1242 1243 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1245 def generatedasrowcolumnconstraint_sql( 1246 self, expression: exp.GeneratedAsRowColumnConstraint 1247 ) -> str: 1248 start = "START" if expression.args.get("start") else "END" 1249 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1250 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:
1260 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1261 desc = expression.args.get("desc") 1262 if desc is not None: 1263 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1264 options = self.expressions(expression, key="options", flat=True, sep=" ") 1265 options = f" {options}" if options else "" 1266 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1268 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1269 this = self.sql(expression, "this") 1270 this = f" {this}" if this else "" 1271 index_type = expression.args.get("index_type") 1272 index_type = f" USING {index_type}" if index_type else "" 1273 on_conflict = self.sql(expression, "on_conflict") 1274 on_conflict = f" {on_conflict}" if on_conflict else "" 1275 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1276 options = self.expressions(expression, key="options", flat=True, sep=" ") 1277 options = f" {options}" if options else "" 1278 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1280 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1281 input_ = expression.args.get("input_") 1282 output = expression.args.get("output") 1283 variadic = expression.args.get("variadic") 1284 1285 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1286 if variadic: 1287 return "VARIADIC" 1288 1289 if input_ and output: 1290 return f"IN{self.INOUT_SEPARATOR}OUT" 1291 if input_: 1292 return "IN" 1293 if output: 1294 return "OUT" 1295 1296 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1301 def create_sql(self, expression: exp.Create) -> str: 1302 kind = self.sql(expression, "kind") 1303 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1304 1305 properties = expression.args.get("properties") 1306 1307 if ( 1308 kind == "TRIGGER" 1309 and properties 1310 and properties.expressions 1311 and isinstance(properties.expressions[0], exp.TriggerProperties) 1312 and properties.expressions[0].args.get("constraint") 1313 ): 1314 kind = f"CONSTRAINT {kind}" 1315 1316 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1317 1318 this = self.createable_sql(expression, properties_locs) 1319 1320 properties_sql = "" 1321 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1322 exp.Properties.Location.POST_WITH 1323 ): 1324 props_ast = exp.Properties( 1325 expressions=[ 1326 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1327 *properties_locs[exp.Properties.Location.POST_WITH], 1328 ] 1329 ) 1330 props_ast.parent = expression 1331 properties_sql = self.sql(props_ast) 1332 1333 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1334 properties_sql = self.sep() + properties_sql 1335 elif not self.pretty: 1336 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1337 properties_sql = f" {properties_sql}" 1338 1339 begin = " BEGIN" if expression.args.get("begin") else "" 1340 1341 expression_sql = self.sql(expression, "expression") 1342 if expression_sql: 1343 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1344 1345 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1346 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1347 ): 1348 postalias_props_sql = "" 1349 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1350 postalias_props_sql = self.properties( 1351 exp.Properties( 1352 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1353 ), 1354 wrapped=False, 1355 ) 1356 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1357 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1358 1359 postindex_props_sql = "" 1360 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1361 postindex_props_sql = self.properties( 1362 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1363 wrapped=False, 1364 prefix=" ", 1365 ) 1366 1367 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1368 indexes = f" {indexes}" if indexes else "" 1369 index_sql = indexes + postindex_props_sql 1370 1371 replace = " OR REPLACE" if expression.args.get("replace") else "" 1372 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1373 unique = " UNIQUE" if expression.args.get("unique") else "" 1374 1375 clustered = expression.args.get("clustered") 1376 if clustered is None: 1377 clustered_sql = "" 1378 elif clustered: 1379 clustered_sql = " CLUSTERED COLUMNSTORE" 1380 else: 1381 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1382 1383 postcreate_props_sql = "" 1384 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1385 postcreate_props_sql = self.properties( 1386 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1387 sep=" ", 1388 prefix=" ", 1389 wrapped=False, 1390 ) 1391 1392 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1393 1394 postexpression_props_sql = "" 1395 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1396 postexpression_props_sql = self.properties( 1397 exp.Properties( 1398 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1399 ), 1400 sep=" ", 1401 prefix=" ", 1402 wrapped=False, 1403 ) 1404 1405 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1406 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1407 no_schema_binding = ( 1408 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1409 ) 1410 1411 clone = self.sql(expression, "clone") 1412 clone = f" {clone}" if clone else "" 1413 1414 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1415 properties_expression = f"{expression_sql}{properties_sql}" 1416 else: 1417 properties_expression = f"{properties_sql}{expression_sql}" 1418 1419 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1420 return self.prepend_ctes(expression, expression_sql)
1422 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1423 start = self.sql(expression, "start") 1424 start = f"START WITH {start}" if start else "" 1425 increment = self.sql(expression, "increment") 1426 increment = f" INCREMENT BY {increment}" if increment else "" 1427 minvalue = self.sql(expression, "minvalue") 1428 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1429 maxvalue = self.sql(expression, "maxvalue") 1430 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1431 owned = self.sql(expression, "owned") 1432 owned = f" OWNED BY {owned}" if owned else "" 1433 1434 cache = expression.args.get("cache") 1435 if cache is None: 1436 cache_str = "" 1437 elif cache is True: 1438 cache_str = " CACHE" 1439 else: 1440 cache_str = f" CACHE {cache}" 1441 1442 options = self.expressions(expression, key="options", flat=True, sep=" ") 1443 options = f" {options}" if options else "" 1444 1445 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1447 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1448 timing = expression.args.get("timing", "") 1449 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1450 timing_events = f"{timing} {events}".strip() if timing or events else "" 1451 1452 parts = [timing_events, "ON", self.sql(expression, "table")] 1453 1454 if referenced_table := expression.args.get("referenced_table"): 1455 parts.extend(["FROM", self.sql(referenced_table)]) 1456 1457 if deferrable := expression.args.get("deferrable"): 1458 parts.append(deferrable) 1459 1460 if initially := expression.args.get("initially"): 1461 parts.append(f"INITIALLY {initially}") 1462 1463 if referencing := expression.args.get("referencing"): 1464 parts.append(self.sql(referencing)) 1465 1466 if for_each := expression.args.get("for_each"): 1467 parts.append(f"FOR EACH {for_each}") 1468 1469 if when := expression.args.get("when"): 1470 parts.append(f"WHEN ({self.sql(when)})") 1471 1472 parts.append(self.sql(expression, "execute")) 1473 1474 return self.sep().join(parts)
1476 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1477 parts = [] 1478 1479 if old_alias := expression.args.get("old"): 1480 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1481 1482 if new_alias := expression.args.get("new"): 1483 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1484 1485 return f"REFERENCING {' '.join(parts)}"
1494 def clone_sql(self, expression: exp.Clone) -> str: 1495 this = self.sql(expression, "this") 1496 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1497 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1498 return f"{shallow}{keyword} {this}"
1500 def describe_sql(self, expression: exp.Describe) -> str: 1501 style = expression.args.get("style") 1502 style = f" {style}" if style else "" 1503 partition = self.sql(expression, "partition") 1504 partition = f" {partition}" if partition else "" 1505 format = self.sql(expression, "format") 1506 format = f" {format}" if format else "" 1507 as_json = " AS JSON" if expression.args.get("as_json") else "" 1508 1509 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1521 def with_sql(self, expression: exp.With) -> str: 1522 sql = self.expressions(expression, flat=True) 1523 recursive = ( 1524 "RECURSIVE " 1525 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1526 else "" 1527 ) 1528 search = self.sql(expression, "search") 1529 search = f" {search}" if search else "" 1530 1531 return f"WITH {recursive}{sql}{search}"
1533 def cte_sql(self, expression: exp.CTE) -> str: 1534 alias = expression.args.get("alias") 1535 if alias: 1536 alias.add_comments(expression.pop_comments()) 1537 1538 alias_sql = self.sql(expression, "alias") 1539 1540 materialized = expression.args.get("materialized") 1541 if materialized is False: 1542 materialized = "NOT MATERIALIZED " 1543 elif materialized: 1544 materialized = "MATERIALIZED " 1545 1546 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1547 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1548 1549 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1551 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1552 alias = self.sql(expression, "this") 1553 columns = self.expressions(expression, key="columns", flat=True) 1554 columns = f"({columns})" if columns else "" 1555 1556 if ( 1557 columns 1558 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1559 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1560 ): 1561 columns = "" 1562 self.unsupported("Named columns are not supported in table alias.") 1563 1564 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1565 alias = self._next_name() 1566 1567 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1575 def hexstring_sql( 1576 self, expression: exp.HexString, binary_function_repr: str | None = None 1577 ) -> str: 1578 this = self.sql(expression, "this") 1579 is_integer_type = expression.args.get("is_integer") 1580 1581 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1582 not self.dialect.HEX_START and not binary_function_repr 1583 ): 1584 # Integer representation will be returned if: 1585 # - The read dialect treats the hex value as integer literal but not the write 1586 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1587 return f"{int(this, 16)}" 1588 1589 if not is_integer_type: 1590 # Read dialect treats the hex value as BINARY/BLOB 1591 if binary_function_repr: 1592 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1593 return self.func(binary_function_repr, exp.Literal.string(this)) 1594 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1595 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1596 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1597 1598 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1600 def bytestring_sql(self, expression: exp.ByteString) -> str: 1601 this = self.sql(expression, "this") 1602 if self.dialect.BYTE_START: 1603 escaped_byte_string = self.escape_str( 1604 this, 1605 escape_backslash=False, 1606 delimiter=self.dialect.BYTE_END, 1607 escaped_delimiter=self._escaped_byte_quote_end, 1608 is_byte_string=True, 1609 ) 1610 is_bytes = expression.args.get("is_bytes", False) 1611 delimited_byte_string = ( 1612 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1613 ) 1614 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1615 return self.sql( 1616 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1617 ) 1618 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1619 return self.sql( 1620 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1621 ) 1622 1623 return delimited_byte_string 1624 1625 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1626 return self.sql(exp.Literal.string(this)) 1627 1628 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1629 return ""
1631 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1632 this = self.sql(expression, "this") 1633 escape = expression.args.get("escape") 1634 1635 if self.dialect.UNICODE_START: 1636 escape_substitute = r"\\\1" 1637 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1638 else: 1639 escape_substitute = r"\\u\1" 1640 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1641 1642 if escape: 1643 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1644 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1645 else: 1646 escape_pattern = ESCAPED_UNICODE_RE 1647 escape_sql = "" 1648 1649 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1650 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1651 1652 return f"{left_quote}{this}{right_quote}{escape_sql}"
1654 def rawstring_sql(self, expression: exp.RawString) -> str: 1655 string = expression.this 1656 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1657 string = string.replace("\\", "\\\\") 1658 1659 string = self.escape_str(string, escape_backslash=False) 1660 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:
1668 def datatype_param_bound_limiter( 1669 self, 1670 expression: exp.DataType, 1671 type_value: exp.DType, 1672 defaults: tuple[int, ...], 1673 bounds: tuple[int | None, ...], 1674 ) -> exp.DataType: 1675 params = expression.expressions 1676 1677 if not params: 1678 if defaults: 1679 expression.set( 1680 "expressions", 1681 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1682 ) 1683 return expression 1684 1685 if not bounds: 1686 return expression 1687 1688 for i, param in enumerate(params): 1689 bound = bounds[i] if i < len(bounds) else None 1690 if bound is None: 1691 continue 1692 1693 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1694 if ( 1695 isinstance(param_value, exp.Literal) 1696 and param_value.is_number 1697 and int(param_value.to_py()) > bound 1698 ): 1699 self.unsupported( 1700 f"{type_value.value} parameter {param_value.name} exceeds " 1701 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1702 ) 1703 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1704 1705 return expression
1707 def datatype_sql(self, expression: exp.DataType) -> str: 1708 nested = "" 1709 values = "" 1710 1711 expr_nested = expression.args.get("nested") 1712 type_value = expression.this 1713 1714 if ( 1715 not expr_nested 1716 and isinstance(type_value, exp.DType) 1717 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1718 ): 1719 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1720 1721 interior = ( 1722 self.expressions( 1723 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1724 ) 1725 if expr_nested and self.pretty 1726 else self.expressions(expression, flat=True) 1727 ) 1728 1729 if type_value in self.UNSUPPORTED_TYPES: 1730 self.unsupported( 1731 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1732 ) 1733 1734 type_sql: t.Any = "" 1735 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1736 type_sql = self.sql(expression, "kind") 1737 elif type_value == exp.DType.CHARACTER_SET: 1738 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1739 else: 1740 type_sql = ( 1741 self.TYPE_MAPPING.get(type_value, type_value.value) 1742 if isinstance(type_value, exp.DType) 1743 else type_value 1744 ) 1745 1746 if interior: 1747 if expr_nested: 1748 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1749 if expression.args.get("values") is not None: 1750 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1751 values = self.expressions(expression, key="values", flat=True) 1752 values = f"{delimiters[0]}{values}{delimiters[1]}" 1753 elif type_value == exp.DType.INTERVAL: 1754 nested = f" {interior}" 1755 else: 1756 nested = f"({interior})" 1757 1758 type_sql = f"{type_sql}{nested}{values}" 1759 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1760 exp.DType.TIMETZ, 1761 exp.DType.TIMESTAMPTZ, 1762 ): 1763 type_sql = f"{type_sql} WITH TIME ZONE" 1764 1765 collate = self.sql(expression, "collate") 1766 if collate: 1767 type_sql = f"{type_sql} COLLATE {collate}" 1768 1769 return type_sql
1771 def directory_sql(self, expression: exp.Directory) -> str: 1772 local = "LOCAL " if expression.args.get("local") else "" 1773 row_format = self.sql(expression, "row_format") 1774 row_format = f" {row_format}" if row_format else "" 1775 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1777 def delete_sql(self, expression: exp.Delete) -> str: 1778 hint = self.sql(expression, "hint") 1779 this = self.sql(expression, "this") 1780 this = f" FROM {this}" if this else "" 1781 using = self.expressions(expression, key="using") 1782 using = f" USING {using}" if using else "" 1783 cluster = self.sql(expression, "cluster") 1784 cluster = f" {cluster}" if cluster else "" 1785 where = self.sql(expression, "where") 1786 returning = self.sql(expression, "returning") 1787 order = self.sql(expression, "order") 1788 limit = self.sql(expression, "limit") 1789 tables = self.expressions(expression, key="tables") 1790 tables = f" {tables}" if tables else "" 1791 if self.RETURNING_END: 1792 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1793 else: 1794 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1795 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1797 def drop_sql(self, expression: exp.Drop) -> str: 1798 this = self.sql(expression, "this") 1799 expressions = self.expressions(expression, flat=True) 1800 expressions = f" ({expressions})" if expressions else "" 1801 kind = expression.args["kind"] 1802 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1803 iceberg = ( 1804 " ICEBERG" 1805 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1806 else "" 1807 ) 1808 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1809 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1810 on_cluster = self.sql(expression, "cluster") 1811 on_cluster = f" {on_cluster}" if on_cluster else "" 1812 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1813 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1814 cascade = " CASCADE" if expression.args.get("cascade") else "" 1815 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1816 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1817 purge = " PURGE" if expression.args.get("purge") else "" 1818 sync = " SYNC" if expression.args.get("sync") else "" 1819 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1821 def set_operation(self, expression: exp.SetOperation) -> str: 1822 op_type = type(expression) 1823 op_name = op_type.key.upper() 1824 1825 distinct = expression.args.get("distinct") 1826 if ( 1827 distinct is False 1828 and op_type in (exp.Except, exp.Intersect) 1829 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1830 ): 1831 self.unsupported(f"{op_name} ALL is not supported") 1832 1833 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1834 1835 if distinct is None: 1836 distinct = default_distinct 1837 if distinct is None: 1838 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1839 1840 if distinct is default_distinct: 1841 distinct_or_all = "" 1842 else: 1843 distinct_or_all = " DISTINCT" if distinct else " ALL" 1844 1845 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1846 side_kind = f"{side_kind} " if side_kind else "" 1847 1848 by_name = " BY NAME" if expression.args.get("by_name") else "" 1849 on = self.expressions(expression, key="on", flat=True) 1850 on = f" ON ({on})" if on else "" 1851 1852 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1854 def set_operations(self, expression: exp.SetOperation) -> str: 1855 if not self.SET_OP_MODIFIERS: 1856 limit = expression.args.get("limit") 1857 order = expression.args.get("order") 1858 1859 if limit or order: 1860 select = self._move_ctes_to_top_level( 1861 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1862 ) 1863 1864 if limit: 1865 select = select.limit(limit.pop(), copy=False) 1866 if order: 1867 select = select.order_by(order.pop(), copy=False) 1868 return self.sql(select) 1869 1870 sqls: list[str] = [] 1871 stack: list[str | exp.Expr] = [expression] 1872 1873 while stack: 1874 node = stack.pop() 1875 1876 if isinstance(node, exp.SetOperation): 1877 stack.append(node.expression) 1878 stack.append( 1879 self.maybe_comment( 1880 self.set_operation(node), comments=node.comments, separated=True 1881 ) 1882 ) 1883 stack.append(node.this) 1884 else: 1885 sqls.append(self.sql(node)) 1886 1887 this = self.sep().join(sqls) 1888 this = self.query_modifiers(expression, this) 1889 return self.prepend_ctes(expression, this)
1891 def fetch_sql(self, expression: exp.Fetch) -> str: 1892 direction = expression.args.get("direction") 1893 direction = f" {direction}" if direction else "" 1894 count = self.sql(expression, "count") 1895 count = f" {count}" if count else "" 1896 limit_options = self.sql(expression, "limit_options") 1897 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1898 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1900 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1901 percent = " PERCENT" if expression.args.get("percent") else "" 1902 rows = " ROWS" if expression.args.get("rows") else "" 1903 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1904 if not with_ties and rows: 1905 with_ties = " ONLY" 1906 return f"{percent}{rows}{with_ties}"
1908 def filter_sql(self, expression: exp.Filter) -> str: 1909 if self.AGGREGATE_FILTER_SUPPORTED: 1910 this = self.sql(expression, "this") 1911 where = self.sql(expression, "expression").strip() 1912 return f"{this} FILTER({where})" 1913 1914 agg = expression.this 1915 agg_arg = agg.this 1916 cond = expression.expression.this 1917 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1918 return self.sql(agg)
1927 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1928 using = self.sql(expression, "using") 1929 using = f" USING {using}" if using else "" 1930 columns = self.expressions(expression, key="columns", flat=True) 1931 columns = f"({columns})" if columns else "" 1932 partition_by = self.expressions(expression, key="partition_by", flat=True) 1933 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1934 where = self.sql(expression, "where") 1935 include = self.expressions(expression, key="include", flat=True) 1936 if include: 1937 include = f" INCLUDE ({include})" 1938 with_storage = self.expressions(expression, key="with_storage", flat=True) 1939 with_storage = f" WITH ({with_storage})" if with_storage else "" 1940 tablespace = self.sql(expression, "tablespace") 1941 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1942 on = self.sql(expression, "on") 1943 on = f" ON {on}" if on else "" 1944 1945 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1947 def index_sql(self, expression: exp.Index) -> str: 1948 unique = "UNIQUE " if expression.args.get("unique") else "" 1949 primary = "PRIMARY " if expression.args.get("primary") else "" 1950 amp = "AMP " if expression.args.get("amp") else "" 1951 name = self.sql(expression, "this") 1952 name = f"{name} " if name else "" 1953 table = self.sql(expression, "table") 1954 table = f"{self.INDEX_ON} {table}" if table else "" 1955 1956 index = "INDEX " if not table else "" 1957 1958 params = self.sql(expression, "params") 1959 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1961 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1962 this = expression.this 1963 if this and this.is_string: 1964 resolved = maybe_parse(this.name).sql(self.dialect) 1965 if "expressions" in expression.args: 1966 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1967 # We can't safely emit the call to other dialects since name/arg semantics may differ 1968 self.unsupported( 1969 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1970 ) 1971 return resolved 1972 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1973 return self.func("IDENTIFIER", this)
1975 def identifier_sql(self, expression: exp.Identifier) -> str: 1976 text = expression.name 1977 lower = text.lower() 1978 quoted = expression.quoted 1979 text = lower if self.normalize and not quoted else text 1980 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1981 if ( 1982 quoted 1983 or self.dialect.can_quote(expression, self.identify) 1984 or lower in self.RESERVED_KEYWORDS 1985 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1986 ): 1987 text = ( 1988 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1989 ) 1990 return text
2005 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2006 input_format = self.sql(expression, "input_format") 2007 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2008 output_format = self.sql(expression, "output_format") 2009 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2010 return self.sep().join((input_format, output_format))
2020 def properties_sql(self, expression: exp.Properties) -> str: 2021 root_properties = [] 2022 with_properties = [] 2023 2024 for p in expression.expressions: 2025 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2026 if p_loc == exp.Properties.Location.POST_WITH: 2027 with_properties.append(p) 2028 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2029 root_properties.append(p) 2030 2031 root_props_ast = exp.Properties(expressions=root_properties) 2032 root_props_ast.parent = expression.parent 2033 2034 with_props_ast = exp.Properties(expressions=with_properties) 2035 with_props_ast.parent = expression.parent 2036 2037 root_props = self.root_properties(root_props_ast) 2038 with_props = self.with_properties(with_props_ast) 2039 2040 if root_props and with_props and not self.pretty: 2041 with_props = " " + with_props 2042 2043 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2050 def properties( 2051 self, 2052 properties: exp.Properties, 2053 prefix: str = "", 2054 sep: str = ", ", 2055 suffix: str = "", 2056 wrapped: bool = True, 2057 ) -> str: 2058 if properties.expressions: 2059 expressions = self.expressions(properties, sep=sep, indent=False) 2060 if expressions: 2061 expressions = self.wrap(expressions) if wrapped else expressions 2062 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2063 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2068 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2069 properties_locs = defaultdict(list) 2070 for p in properties.expressions: 2071 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2072 if p_loc != exp.Properties.Location.UNSUPPORTED: 2073 properties_locs[p_loc].append(p) 2074 else: 2075 self.unsupported(f"Unsupported property {p.key}") 2076 2077 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2084 def property_sql(self, expression: exp.Property) -> str: 2085 property_cls = expression.__class__ 2086 if property_cls == exp.Property: 2087 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2088 2089 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2090 if not property_name: 2091 self.unsupported(f"Unsupported property {expression.key}") 2092 2093 return f"{property_name}={self.sql(expression, 'this')}"
2098 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2099 if self.SUPPORTS_CREATE_TABLE_LIKE: 2100 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2101 options = f" {options}" if options else "" 2102 2103 like = f"LIKE {self.sql(expression, 'this')}{options}" 2104 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2105 like = f"({like})" 2106 2107 return like 2108 2109 if expression.expressions: 2110 self.unsupported("Transpilation of LIKE property options is unsupported") 2111 2112 select = exp.select("*").from_(expression.this).limit(0) 2113 return f"AS {self.sql(select)}"
2120 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2121 no = "NO " if expression.args.get("no") else "" 2122 local = expression.args.get("local") 2123 local = f"{local} " if local else "" 2124 dual = "DUAL " if expression.args.get("dual") else "" 2125 before = "BEFORE " if expression.args.get("before") else "" 2126 after = "AFTER " if expression.args.get("after") else "" 2127 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:
2143 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2144 if expression.args.get("no"): 2145 return "NO MERGEBLOCKRATIO" 2146 if expression.args.get("default"): 2147 return "DEFAULT MERGEBLOCKRATIO" 2148 2149 percent = " PERCENT" if expression.args.get("percent") else "" 2150 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2157 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2158 default = expression.args.get("default") 2159 minimum = expression.args.get("minimum") 2160 maximum = expression.args.get("maximum") 2161 if default or minimum or maximum: 2162 if default: 2163 prop = "DEFAULT" 2164 elif minimum: 2165 prop = "MINIMUM" 2166 else: 2167 prop = "MAXIMUM" 2168 return f"{prop} DATABLOCKSIZE" 2169 units = expression.args.get("units") 2170 units = f" {units}" if units else "" 2171 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2173 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2174 autotemp = expression.args.get("autotemp") 2175 always = expression.args.get("always") 2176 default = expression.args.get("default") 2177 manual = expression.args.get("manual") 2178 never = expression.args.get("never") 2179 2180 if autotemp is not None: 2181 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2182 elif always: 2183 prop = "ALWAYS" 2184 elif default: 2185 prop = "DEFAULT" 2186 elif manual: 2187 prop = "MANUAL" 2188 elif never: 2189 prop = "NEVER" 2190 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2192 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2193 no = expression.args.get("no") 2194 no = " NO" if no else "" 2195 concurrent = expression.args.get("concurrent") 2196 concurrent = " CONCURRENT" if concurrent else "" 2197 target = self.sql(expression, "target") 2198 target = f" {target}" if target else "" 2199 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2201 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2202 if isinstance(expression.this, list): 2203 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2204 if expression.this: 2205 modulus = self.sql(expression, "this") 2206 remainder = self.sql(expression, "expression") 2207 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2208 2209 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2210 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2211 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2213 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2214 this = self.sql(expression, "this") 2215 2216 for_values_or_default = expression.expression 2217 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2218 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2219 else: 2220 for_values_or_default = " DEFAULT" 2221 2222 return f"PARTITION OF {this}{for_values_or_default}"
2224 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2225 kind = expression.args.get("kind") 2226 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2227 for_or_in = expression.args.get("for_or_in") 2228 for_or_in = f" {for_or_in}" if for_or_in else "" 2229 lock_type = expression.args.get("lock_type") 2230 override = " OVERRIDE" if expression.args.get("override") else "" 2231 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2233 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2234 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2235 statistics = expression.args.get("statistics") 2236 statistics_sql = "" 2237 if statistics is not None: 2238 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2239 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2241 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2242 this = self.sql(expression, "this") 2243 this = f"HISTORY_TABLE={this}" if this else "" 2244 data_consistency: str | None = self.sql(expression, "data_consistency") 2245 data_consistency = ( 2246 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2247 ) 2248 retention_period: str | None = self.sql(expression, "retention_period") 2249 retention_period = ( 2250 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2251 ) 2252 2253 if this: 2254 on_sql = self.func("ON", this, data_consistency, retention_period) 2255 else: 2256 on_sql = "ON" if expression.args.get("on") else "OFF" 2257 2258 sql = f"SYSTEM_VERSIONING={on_sql}" 2259 2260 return f"WITH({sql})" if expression.args.get("with_") else sql
2262 def insert_sql(self, expression: exp.Insert) -> str: 2263 hint = self.sql(expression, "hint") 2264 overwrite = expression.args.get("overwrite") 2265 2266 if isinstance(expression.this, exp.Directory): 2267 this = " OVERWRITE" if overwrite else " INTO" 2268 else: 2269 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2270 2271 stored = self.sql(expression, "stored") 2272 stored = f" {stored}" if stored else "" 2273 alternative = expression.args.get("alternative") 2274 alternative = f" OR {alternative}" if alternative else "" 2275 ignore = " IGNORE" if expression.args.get("ignore") else "" 2276 is_function = expression.args.get("is_function") 2277 if is_function: 2278 this = f"{this} FUNCTION" 2279 this = f"{this} {self.sql(expression, 'this')}" 2280 2281 exists = " IF EXISTS" if expression.args.get("exists") else "" 2282 where = self.sql(expression, "where") 2283 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2284 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2285 on_conflict = self.sql(expression, "conflict") 2286 on_conflict = f" {on_conflict}" if on_conflict else "" 2287 by_name = " BY NAME" if expression.args.get("by_name") else "" 2288 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2289 returning = self.sql(expression, "returning") 2290 2291 if self.RETURNING_END: 2292 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2293 else: 2294 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2295 2296 partition_by = self.sql(expression, "partition") 2297 partition_by = f" {partition_by}" if partition_by else "" 2298 settings = self.sql(expression, "settings") 2299 settings = f" {settings}" if settings else "" 2300 2301 source = self.sql(expression, "source") 2302 source = f"TABLE {source}" if source else "" 2303 2304 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2305 return self.prepend_ctes(expression, sql)
2323 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2324 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2325 2326 constraint = self.sql(expression, "constraint") 2327 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2328 2329 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2330 if conflict_keys: 2331 conflict_keys = f"({conflict_keys})" 2332 2333 index_predicate = self.sql(expression, "index_predicate") 2334 conflict_keys = f"{conflict_keys}{index_predicate} " 2335 2336 action = self.sql(expression, "action") 2337 2338 expressions = self.expressions(expression, flat=True) 2339 if expressions: 2340 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2341 expressions = f" {set_keyword}{expressions}" 2342 2343 where = self.sql(expression, "where") 2344 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2349 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2350 fields = self.sql(expression, "fields") 2351 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2352 escaped = self.sql(expression, "escaped") 2353 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2354 items = self.sql(expression, "collection_items") 2355 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2356 keys = self.sql(expression, "map_keys") 2357 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2358 lines = self.sql(expression, "lines") 2359 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2360 null = self.sql(expression, "null") 2361 null = f" NULL DEFINED AS {null}" if null else "" 2362 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2390 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2391 table = self.table_parts(expression) 2392 only = "ONLY " if expression.args.get("only") else "" 2393 partition = self.sql(expression, "partition") 2394 partition = f" {partition}" if partition else "" 2395 version = self.sql(expression, "version") 2396 version = f" {version}" if version else "" 2397 alias = self.sql(expression, "alias") 2398 alias = f"{sep}{alias}" if alias else "" 2399 2400 sample = self.sql(expression, "sample") 2401 post_alias = "" 2402 pre_alias = "" 2403 2404 if self.dialect.ALIAS_POST_TABLESAMPLE: 2405 pre_alias = sample 2406 else: 2407 post_alias = sample 2408 2409 if self.dialect.ALIAS_POST_VERSION: 2410 pre_alias = f"{pre_alias}{version}" 2411 else: 2412 post_alias = f"{post_alias}{version}" 2413 2414 hints = self.expressions(expression, key="hints", sep=" ") 2415 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2416 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2417 joins = self.indent( 2418 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2419 ) 2420 laterals = self.expressions(expression, key="laterals", sep="") 2421 2422 file_format = self.sql(expression, "format") 2423 pattern = self.sql(expression, "pattern") 2424 if file_format: 2425 pattern = f", PATTERN => {pattern}" if pattern else "" 2426 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2427 elif pattern: 2428 file_format = f" (PATTERN => {pattern})" 2429 2430 ordinality = expression.args.get("ordinality") or "" 2431 if ordinality: 2432 ordinality = f" WITH ORDINALITY{alias}" 2433 alias = "" 2434 2435 when = self.sql(expression, "when") 2436 if when: 2437 table = f"{table} {when}" 2438 2439 changes = self.sql(expression, "changes") 2440 changes = f" {changes}" if changes else "" 2441 2442 rows_from = self.expressions(expression, key="rows_from") 2443 if rows_from: 2444 table = f"ROWS FROM {self.wrap(rows_from)}" 2445 2446 indexed = expression.args.get("indexed") 2447 if indexed is not None: 2448 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2449 else: 2450 indexed = "" 2451 2452 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2454 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2455 table = self.func("TABLE", expression.this) 2456 alias = self.sql(expression, "alias") 2457 alias = f" AS {alias}" if alias else "" 2458 sample = self.sql(expression, "sample") 2459 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2460 joins = self.indent( 2461 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2462 ) 2463 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2465 def tablesample_sql( 2466 self, 2467 expression: exp.TableSample, 2468 tablesample_keyword: str | None = None, 2469 ) -> str: 2470 method = self.sql(expression, "method") 2471 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2472 numerator = self.sql(expression, "bucket_numerator") 2473 denominator = self.sql(expression, "bucket_denominator") 2474 field = self.sql(expression, "bucket_field") 2475 field = f" ON {field}" if field else "" 2476 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2477 seed = self.sql(expression, "seed") 2478 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2479 2480 size = self.sql(expression, "size") 2481 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2482 size = f"{size} ROWS" 2483 2484 percent = self.sql(expression, "percent") 2485 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2486 percent = f"{percent} PERCENT" 2487 2488 expr = f"{bucket}{percent}{size}" 2489 if self.TABLESAMPLE_REQUIRES_PARENS: 2490 expr = f"({expr})" 2491 2492 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2569 def pivot_sql(self, expression: exp.Pivot) -> str: 2570 expressions = self.expressions(expression, flat=True) 2571 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2572 2573 group = self.sql(expression, "group") 2574 2575 if expression.this: 2576 this = self.sql(expression, "this") 2577 if not expressions: 2578 sql = f"UNPIVOT {this}" 2579 else: 2580 on = f"{self.seg('ON')} {expressions}" 2581 into = self.sql(expression, "into") 2582 into = f"{self.seg('INTO')} {into}" if into else "" 2583 using = self.expressions(expression, key="using", flat=True) 2584 using = f"{self.seg('USING')} {using}" if using else "" 2585 sql = f"{direction} {this}{on}{into}{using}{group}" 2586 return self.prepend_ctes(expression, sql) 2587 2588 if not expression.unpivot: 2589 # Wrap IN-list values with explicit aliases where the target dialect would differ 2590 new_field_exprs = self._pivot_in_value_aliases(expression) 2591 if new_field_exprs is not None: 2592 expression.fields[0].set("expressions", new_field_exprs) 2593 2594 alias = self.sql(expression, "alias") 2595 alias = f" AS {alias}" if alias else "" 2596 2597 fields = self.expressions( 2598 expression, 2599 "fields", 2600 sep=" ", 2601 dynamic=True, 2602 new_line=True, 2603 skip_first=True, 2604 skip_last=True, 2605 ) 2606 2607 include_nulls = expression.args.get("include_nulls") 2608 if include_nulls is not None: 2609 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2610 else: 2611 nulls = "" 2612 2613 default_on_null = self.sql(expression, "default_on_null") 2614 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2615 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2616 return self.prepend_ctes(expression, sql)
2659 def update_sql(self, expression: exp.Update) -> str: 2660 hint = self.sql(expression, "hint") 2661 this = self.sql(expression, "this") 2662 join_sql, from_sql = self._update_from_joins_sql(expression) 2663 set_sql = self.expressions(expression, flat=True) 2664 where_sql = self.sql(expression, "where") 2665 returning = self.sql(expression, "returning") 2666 order = self.sql(expression, "order") 2667 limit = self.sql(expression, "limit") 2668 if self.RETURNING_END: 2669 expression_sql = f"{from_sql}{where_sql}{returning}" 2670 else: 2671 expression_sql = f"{returning}{from_sql}{where_sql}" 2672 options = self.expressions(expression, key="options") 2673 options = f" OPTION({options})" if options else "" 2674 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2675 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2677 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2678 values_as_table = values_as_table and self.VALUES_AS_TABLE 2679 2680 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2681 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2682 args = self.expressions(expression) 2683 alias = self.sql(expression, "alias") 2684 values = f"VALUES{self.seg('')}{args}" 2685 values = ( 2686 f"({values})" 2687 if self.WRAP_DERIVED_VALUES 2688 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2689 else values 2690 ) 2691 values = self.query_modifiers(expression, values) 2692 return f"{values} AS {alias}" if alias else values 2693 2694 # Converts `VALUES...` expression into a series of select unions. 2695 alias_node = expression.args.get("alias") 2696 column_names = alias_node and alias_node.columns 2697 2698 selects: list[exp.Query] = [] 2699 2700 for i, tup in enumerate(expression.expressions): 2701 row = tup.expressions 2702 2703 if i == 0 and column_names: 2704 row = [ 2705 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2706 ] 2707 2708 selects.append(exp.Select(expressions=row)) 2709 2710 if self.pretty: 2711 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2712 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2713 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2714 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2715 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2716 2717 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2718 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2719 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2724 @unsupported_args("expressions") 2725 def into_sql(self, expression: exp.Into) -> str: 2726 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2727 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2728 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2741 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2742 this = self.sql(expression, "this") 2743 2744 columns = self.expressions(expression, flat=True) 2745 2746 from_sql = self.sql(expression, "from_index") 2747 from_sql = f" FROM {from_sql}" if from_sql else "" 2748 2749 properties = expression.args.get("properties") 2750 properties_sql = ( 2751 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2752 ) 2753 2754 return f"{this}({columns}){from_sql}{properties_sql}"
2763 def group_sql(self, expression: exp.Group) -> str: 2764 group_by_all = expression.args.get("all") 2765 if group_by_all is True: 2766 modifier = " ALL" 2767 elif group_by_all is False: 2768 modifier = " DISTINCT" 2769 else: 2770 modifier = "" 2771 2772 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2773 2774 grouping_sets = self.expressions(expression, key="grouping_sets") 2775 cube = self.expressions(expression, key="cube") 2776 rollup = self.expressions(expression, key="rollup") 2777 2778 groupings = csv( 2779 self.seg(grouping_sets) if grouping_sets else "", 2780 self.seg(cube) if cube else "", 2781 self.seg(rollup) if rollup else "", 2782 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2783 sep=self.GROUPINGS_SEP, 2784 ) 2785 2786 if ( 2787 expression.expressions 2788 and groupings 2789 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2790 ): 2791 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2792 2793 return f"{group_by}{groupings}"
2799 def connect_sql(self, expression: exp.Connect) -> str: 2800 start = self.sql(expression, "start") 2801 start = self.seg(f"START WITH {start}") if start else "" 2802 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2803 connect = self.sql(expression, "connect") 2804 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2805 return start + connect
2810 def join_sql(self, expression: exp.Join) -> str: 2811 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2812 side = None 2813 else: 2814 side = expression.side 2815 2816 op_sql = " ".join( 2817 op 2818 for op in ( 2819 expression.method, 2820 "GLOBAL" if expression.args.get("global_") else None, 2821 side, 2822 expression.kind, 2823 expression.hint if self.JOIN_HINTS else None, 2824 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2825 ) 2826 if op 2827 ) 2828 match_cond = self.sql(expression, "match_condition") 2829 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2830 on_sql = self.sql(expression, "on") 2831 using = expression.args.get("using") 2832 2833 if not on_sql and using: 2834 on_sql = csv(*(self.sql(column) for column in using)) 2835 2836 this = expression.this 2837 this_sql = self.sql(this) 2838 2839 exprs = self.expressions(expression) 2840 if exprs: 2841 this_sql = f"{this_sql},{self.seg(exprs)}" 2842 2843 if on_sql: 2844 on_sql = self.indent(on_sql, skip_first=True) 2845 space = self.seg(" " * self.pad) if self.pretty else " " 2846 if using: 2847 on_sql = f"{space}USING ({on_sql})" 2848 else: 2849 on_sql = f"{space}ON {on_sql}" 2850 elif not op_sql: 2851 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2852 return f" {this_sql}" 2853 2854 return f", {this_sql}" 2855 2856 if op_sql != "STRAIGHT_JOIN": 2857 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2858 2859 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2860 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:
2867 def lateral_op(self, expression: exp.Lateral) -> str: 2868 cross_apply = expression.args.get("cross_apply") 2869 2870 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2871 if cross_apply is True: 2872 op = "INNER JOIN " 2873 elif cross_apply is False: 2874 op = "LEFT JOIN " 2875 else: 2876 op = "" 2877 2878 return f"{op}LATERAL"
2880 def lateral_sql(self, expression: exp.Lateral) -> str: 2881 this = self.sql(expression, "this") 2882 2883 if expression.args.get("view"): 2884 alias = expression.args["alias"] 2885 columns = self.expressions(alias, key="columns", flat=True) 2886 table = f" {alias.name}" if alias.name else "" 2887 columns = f" AS {columns}" if columns else "" 2888 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2889 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2890 2891 alias = self.sql(expression, "alias") 2892 alias = f" AS {alias}" if alias else "" 2893 2894 ordinality = expression.args.get("ordinality") or "" 2895 if ordinality: 2896 ordinality = f" WITH ORDINALITY{alias}" 2897 alias = "" 2898 2899 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2901 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2902 this = self.sql(expression, "this") 2903 2904 args = [ 2905 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2906 for e in (expression.args.get(k) for k in ("offset", "expression")) 2907 if e 2908 ] 2909 2910 args_sql = ", ".join(self.sql(e) for e in args) 2911 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2912 expressions = self.expressions(expression, flat=True) 2913 limit_options = self.sql(expression, "limit_options") 2914 expressions = f" BY {expressions}" if expressions else "" 2915 2916 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2918 def offset_sql(self, expression: exp.Offset) -> str: 2919 this = self.sql(expression, "this") 2920 value = expression.expression 2921 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2922 expressions = self.expressions(expression, flat=True) 2923 expressions = f" BY {expressions}" if expressions else "" 2924 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2926 def setitem_sql(self, expression: exp.SetItem) -> str: 2927 kind = self.sql(expression, "kind") 2928 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2929 kind = "" 2930 else: 2931 kind = f"{kind} " if kind else "" 2932 this = self.sql(expression, "this") 2933 expressions = self.expressions(expression) 2934 collate = self.sql(expression, "collate") 2935 collate = f" COLLATE {collate}" if collate else "" 2936 global_ = "GLOBAL " if expression.args.get("global_") else "" 2937 return f"{global_}{kind}{this}{expressions}{collate}"
2944 def queryband_sql(self, expression: exp.QueryBand) -> str: 2945 this = self.sql(expression, "this") 2946 update = " UPDATE" if expression.args.get("update") else "" 2947 scope = self.sql(expression, "scope") 2948 scope = f" FOR {scope}" if scope else "" 2949 2950 return f"QUERY_BAND = {this}{update}{scope}"
2955 def lock_sql(self, expression: exp.Lock) -> str: 2956 if not self.LOCKING_READS_SUPPORTED: 2957 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2958 return "" 2959 2960 update = expression.args["update"] 2961 key = expression.args.get("key") 2962 if update: 2963 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2964 else: 2965 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2966 expressions = self.expressions(expression, flat=True) 2967 expressions = f" OF {expressions}" if expressions else "" 2968 wait = expression.args.get("wait") 2969 2970 if wait is not None: 2971 if isinstance(wait, exp.Literal): 2972 wait = f" WAIT {self.sql(wait)}" 2973 else: 2974 wait = " NOWAIT" if wait else " SKIP LOCKED" 2975 2976 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:
2984 def escape_str( 2985 self, 2986 text: str, 2987 escape_backslash: bool = True, 2988 delimiter: str | None = None, 2989 escaped_delimiter: str | None = None, 2990 is_byte_string: bool = False, 2991 ) -> str: 2992 if is_byte_string: 2993 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2994 else: 2995 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2996 2997 if supports_escape_sequences: 2998 text = "".join( 2999 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3000 for ch in text 3001 ) 3002 3003 delimiter = delimiter or self.dialect.QUOTE_END 3004 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3005 3006 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3008 def loaddata_sql(self, expression: exp.LoadData) -> str: 3009 is_overwrite = expression.args.get("overwrite") 3010 overwrite = " OVERWRITE" if is_overwrite else "" 3011 this = self.sql(expression, "this") 3012 3013 files = expression.args.get("files") 3014 if files: 3015 files_sql = self.expressions(files, flat=True) 3016 files_sql = f"FILES{self.wrap(files_sql)}" 3017 if is_overwrite: 3018 this = f" {this}" 3019 elif expression.args.get("temp"): 3020 this = f" INTO TEMP TABLE {this}" 3021 else: 3022 this = f" INTO TABLE {this}" 3023 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3024 3025 local = " LOCAL" if expression.args.get("local") else "" 3026 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3027 this = f" INTO TABLE {this}" 3028 partition = self.sql(expression, "partition") 3029 partition = f" {partition}" if partition else "" 3030 input_format = self.sql(expression, "input_format") 3031 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3032 serde = self.sql(expression, "serde") 3033 serde = f" SERDE {serde}" if serde else "" 3034 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3048 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3049 this = self.sql(expression, "this") 3050 this = f"{this} " if this else this 3051 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3052 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3054 def withfill_sql(self, expression: exp.WithFill) -> str: 3055 from_sql = self.sql(expression, "from_") 3056 from_sql = f" FROM {from_sql}" if from_sql else "" 3057 to_sql = self.sql(expression, "to") 3058 to_sql = f" TO {to_sql}" if to_sql else "" 3059 step_sql = self.sql(expression, "step") 3060 step_sql = f" STEP {step_sql}" if step_sql else "" 3061 interpolated_values = [ 3062 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3063 if isinstance(e, exp.Alias) 3064 else self.sql(e, "this") 3065 for e in expression.args.get("interpolate") or [] 3066 ] 3067 interpolate = ( 3068 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3069 ) 3070 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3122 def ordered_sql(self, expression: exp.Ordered) -> str: 3123 desc = expression.args.get("desc") 3124 asc = not desc 3125 3126 nulls_first = expression.args.get("nulls_first") 3127 nulls_last = not nulls_first 3128 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3129 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3130 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3131 3132 this = self.sql(expression, "this") 3133 3134 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3135 nulls_sort_change = "" 3136 if nulls_first and ( 3137 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3138 ): 3139 nulls_sort_change = " NULLS FIRST" 3140 elif ( 3141 nulls_last 3142 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3143 and not nulls_are_last 3144 ): 3145 nulls_sort_change = " NULLS LAST" 3146 3147 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3148 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3149 window = expression.find_ancestor(exp.Window, exp.Select) 3150 3151 if isinstance(window, exp.Window): 3152 window_this = window.this 3153 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3154 window_this = window_this.this 3155 spec = window.args.get("spec") 3156 else: 3157 window_this = None 3158 spec = None 3159 3160 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3161 # without a spec or with a ROWS spec, but not with RANGE 3162 if not ( 3163 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3164 and (not spec or spec.text("kind").upper() == "ROWS") 3165 ): 3166 if window_this and spec: 3167 self.unsupported( 3168 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3169 ) 3170 nulls_sort_change = "" 3171 elif self.NULL_ORDERING_SUPPORTED is False and ( 3172 (asc and nulls_sort_change == " NULLS LAST") 3173 or (desc and nulls_sort_change == " NULLS FIRST") 3174 ): 3175 # BigQuery does not allow these ordering/nulls combinations when used under 3176 # an aggregation func or under a window containing one 3177 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3178 3179 if isinstance(ancestor, exp.Window): 3180 ancestor = ancestor.this 3181 if isinstance(ancestor, exp.AggFunc): 3182 self.unsupported( 3183 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3184 ) 3185 nulls_sort_change = "" 3186 elif self.NULL_ORDERING_SUPPORTED is None: 3187 if expression.this.is_int: 3188 self.unsupported( 3189 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3190 ) 3191 elif not isinstance(expression.this, exp.Rand): 3192 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3193 target = self.sql(resolved) if resolved is not None else this 3194 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3195 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3196 nulls_sort_change = "" 3197 3198 with_fill = self.sql(expression, "with_fill") 3199 with_fill = f" {with_fill}" if with_fill else "" 3200 3201 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3211 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3212 partition = self.partition_by_sql(expression) 3213 order = self.sql(expression, "order") 3214 measures = self.expressions(expression, key="measures") 3215 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3216 rows = self.sql(expression, "rows") 3217 rows = self.seg(rows) if rows else "" 3218 after = self.sql(expression, "after") 3219 after = self.seg(after) if after else "" 3220 pattern = self.sql(expression, "pattern") 3221 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3222 definition_sqls = [ 3223 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3224 for definition in expression.args.get("define", []) 3225 ] 3226 definitions = self.expressions(sqls=definition_sqls) 3227 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3228 body = "".join( 3229 ( 3230 partition, 3231 order, 3232 measures, 3233 rows, 3234 after, 3235 pattern, 3236 define, 3237 ) 3238 ) 3239 alias = self.sql(expression, "alias") 3240 alias = f" {alias}" if alias else "" 3241 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3243 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3244 limit = expression.args.get("limit") 3245 3246 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3247 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3248 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3249 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3250 3251 return csv( 3252 *sqls, 3253 *[self.sql(join) for join in expression.args.get("joins") or []], 3254 self.sql(expression, "match"), 3255 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3256 self.sql(expression, "prewhere"), 3257 self.sql(expression, "where"), 3258 self.sql(expression, "connect"), 3259 self.sql(expression, "group"), 3260 self.sql(expression, "having"), 3261 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3262 self.sql(expression, "order"), 3263 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3264 *self.after_limit_modifiers(expression), 3265 self.options_modifier(expression), 3266 self.sql(expression, "for_"), 3267 sep="", 3268 )
3274 def forclause_sql(self, expression: exp.ForClause) -> str: 3275 kind = expression.args["kind"] 3276 if kind == "BROWSE": 3277 return f"{self.sep()}FOR BROWSE" 3278 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3279 # the target dialect doesn't support QueryOption, so we drop the clause. 3280 options = self.expressions(expression, key="expressions") 3281 if not options: 3282 return "" 3283 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]:
3302 def select_sql(self, expression: exp.Select) -> str: 3303 into = expression.args.get("into") 3304 if not self.SUPPORTS_SELECT_INTO and into: 3305 into.pop() 3306 3307 hint = self.sql(expression, "hint") 3308 distinct = self.sql(expression, "distinct") 3309 distinct = f" {distinct}" if distinct else "" 3310 kind = self.sql(expression, "kind") 3311 3312 limit = expression.args.get("limit") 3313 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3314 top = self.limit_sql(limit, top=True) 3315 limit.pop() 3316 else: 3317 top = "" 3318 3319 expressions = self.expressions(expression) 3320 3321 if kind: 3322 if kind in self.SELECT_KINDS: 3323 kind = f" AS {kind}" 3324 else: 3325 if kind == "STRUCT": 3326 expressions = self.expressions( 3327 sqls=[ 3328 self.sql( 3329 exp.Struct( 3330 expressions=[ 3331 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3332 if isinstance(e, exp.Alias) 3333 else e 3334 for e in expression.expressions 3335 ] 3336 ) 3337 ) 3338 ] 3339 ) 3340 kind = "" 3341 3342 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3343 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3344 3345 exclude = expression.args.get("exclude") 3346 3347 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3348 exclude_sql = self.expressions(sqls=exclude, flat=True) 3349 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3350 3351 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3352 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3353 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3354 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3355 sql = self.query_modifiers( 3356 expression, 3357 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3358 self.sql(expression, "into", comment=False), 3359 self.sql(expression, "from_", comment=False), 3360 ) 3361 3362 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3363 if expression.args.get("with_"): 3364 sql = self.maybe_comment(sql, expression) 3365 expression.pop_comments() 3366 3367 sql = self.prepend_ctes(expression, sql) 3368 3369 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3370 expression.set("exclude", None) 3371 subquery = expression.subquery(copy=False) 3372 star = exp.Star(except_=exclude) 3373 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3374 3375 if not self.SUPPORTS_SELECT_INTO and into: 3376 if into.args.get("temporary"): 3377 table_kind = " TEMPORARY" 3378 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3379 table_kind = " UNLOGGED" 3380 else: 3381 table_kind = "" 3382 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3383 3384 return sql
3396 def star_sql(self, expression: exp.Star) -> str: 3397 except_ = self.expressions(expression, key="except_", flat=True) 3398 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3399 replace = self.expressions(expression, key="replace", flat=True) 3400 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3401 rename = self.expressions(expression, key="rename", flat=True) 3402 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3403 ilike = self.sql(expression, "ilike") 3404 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3405 return f"*{ilike}{except_}{replace}{rename}"
3421 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3422 alias = self.sql(expression, "alias") 3423 alias = f"{sep}{alias}" if alias else "" 3424 sample = self.sql(expression, "sample") 3425 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3426 alias = f"{sample}{alias}" 3427 3428 # Set to None so it's not generated again by self.query_modifiers() 3429 expression.set("sample", None) 3430 3431 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3432 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3433 return self.prepend_ctes(expression, sql)
3439 def unnest_sql(self, expression: exp.Unnest) -> str: 3440 args = self.expressions(expression, flat=True) 3441 3442 alias = expression.args.get("alias") 3443 offset = expression.args.get("offset") 3444 3445 if self.UNNEST_WITH_ORDINALITY: 3446 if alias and isinstance(offset, exp.Expr): 3447 alias.append("columns", offset) 3448 expression.set("offset", None) 3449 3450 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3451 columns = alias.columns 3452 alias = self.sql(columns[0]) if columns else "" 3453 else: 3454 alias = self.sql(alias) 3455 3456 alias = f" AS {alias}" if alias else alias 3457 if self.UNNEST_WITH_ORDINALITY: 3458 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3459 else: 3460 if isinstance(offset, exp.Expr): 3461 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3462 elif offset: 3463 suffix = f"{alias} WITH OFFSET" 3464 else: 3465 suffix = alias 3466 3467 return f"UNNEST({args}){suffix}"
3476 def window_sql(self, expression: exp.Window) -> str: 3477 this = self.sql(expression, "this") 3478 partition = self.partition_by_sql(expression) 3479 order = expression.args.get("order") 3480 order = self.order_sql(order, flat=True) if order else "" 3481 spec = self.sql(expression, "spec") 3482 alias = self.sql(expression, "alias") 3483 over = self.sql(expression, "over") or "OVER" 3484 3485 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3486 3487 first = expression.args.get("first") 3488 if first is None: 3489 first = "" 3490 else: 3491 first = "FIRST" if first else "LAST" 3492 3493 if not partition and not order and not spec and alias: 3494 return f"{this} {alias}" 3495 3496 args = self.format_args( 3497 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3498 ) 3499 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3505 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3506 kind = self.sql(expression, "kind") 3507 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3508 end = ( 3509 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3510 or "CURRENT ROW" 3511 ) 3512 3513 window_spec = f"{kind} BETWEEN {start} AND {end}" 3514 3515 exclude = self.sql(expression, "exclude") 3516 if exclude: 3517 if self.SUPPORTS_WINDOW_EXCLUDE: 3518 window_spec += f" EXCLUDE {exclude}" 3519 else: 3520 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3521 3522 return window_spec
3529 def between_sql(self, expression: exp.Between) -> str: 3530 this = self.sql(expression, "this") 3531 low = self.sql(expression, "low") 3532 high = self.sql(expression, "high") 3533 symmetric = expression.args.get("symmetric") 3534 3535 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3536 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3537 3538 flag = ( 3539 " SYMMETRIC" 3540 if symmetric 3541 else " ASYMMETRIC" 3542 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3543 else "" # silently drop ASYMMETRIC – semantics identical 3544 ) 3545 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]:
3547 def bracket_offset_expressions( 3548 self, expression: exp.Bracket, index_offset: int | None = None 3549 ) -> list[exp.Expr]: 3550 if expression.args.get("json_access"): 3551 return expression.expressions 3552 3553 return apply_index_offset( 3554 expression.this, 3555 expression.expressions, 3556 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3557 dialect=self.dialect, 3558 )
3571 def any_sql(self, expression: exp.Any) -> str: 3572 this = self.sql(expression, "this") 3573 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3574 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3575 this = self.wrap(this) 3576 return f"ANY{this}" 3577 return f"ANY {this}"
3582 def case_sql(self, expression: exp.Case) -> str: 3583 this = self.sql(expression, "this") 3584 statements = [f"CASE {this}" if this else "CASE"] 3585 3586 for e in expression.args["ifs"]: 3587 statements.append(f"WHEN {self.sql(e, 'this')}") 3588 statements.append(f"THEN {self.sql(e, 'true')}") 3589 3590 default = self.sql(expression, "default") 3591 3592 if default: 3593 statements.append(f"ELSE {default}") 3594 3595 statements.append("END") 3596 3597 if self.pretty and self.too_wide(statements): 3598 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3599 3600 return " ".join(statements)
3612 def extract_sql(self, expression: exp.Extract) -> str: 3613 import sqlglot.dialects.dialect 3614 3615 this = ( 3616 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3617 if self.NORMALIZE_EXTRACT_DATE_PARTS 3618 else expression.this 3619 ) 3620 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3621 expression_sql = self.sql(expression, "expression") 3622 3623 return f"EXTRACT({this_sql} FROM {expression_sql})"
3625 def trim_sql(self, expression: exp.Trim) -> str: 3626 trim_type = self.sql(expression, "position") 3627 3628 if trim_type == "LEADING": 3629 func_name = "LTRIM" 3630 elif trim_type == "TRAILING": 3631 func_name = "RTRIM" 3632 else: 3633 func_name = "TRIM" 3634 3635 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3637 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3638 args = expression.expressions 3639 if isinstance(expression, exp.ConcatWs): 3640 args = args[1:] # Skip the delimiter 3641 3642 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3643 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3644 3645 concat_coalesce = ( 3646 self.dialect.CONCAT_WS_COALESCE 3647 if isinstance(expression, exp.ConcatWs) 3648 else self.dialect.CONCAT_COALESCE 3649 ) 3650 3651 if not concat_coalesce and expression.args.get("coalesce"): 3652 3653 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3654 if not e.type: 3655 import sqlglot.optimizer.annotate_types 3656 3657 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3658 3659 if e.is_string or e.is_type(exp.DType.ARRAY): 3660 return e 3661 3662 return exp.func("coalesce", e, exp.Literal.string("")) 3663 3664 args = [_wrap_with_coalesce(e) for e in args] 3665 3666 return args
3668 def concat_sql(self, expression: exp.Concat) -> str: 3669 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3670 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3671 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3672 # instead of coalescing them to empty string. 3673 import sqlglot.dialects.dialect 3674 3675 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3676 3677 expressions = self.convert_concat_args(expression) 3678 3679 # Some dialects don't allow a single-argument CONCAT call 3680 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3681 return self.sql(expressions[0]) 3682 3683 return self.func("CONCAT", *expressions)
3685 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3686 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3687 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3688 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3689 all_args = expression.expressions 3690 expression.set("coalesce", True) 3691 return self.sql( 3692 exp.case() 3693 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3694 .else_(expression) 3695 ) 3696 3697 return self.func( 3698 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3699 )
3705 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3706 expressions = self.expressions(expression, flat=True) 3707 expressions = f" ({expressions})" if expressions else "" 3708 reference = self.sql(expression, "reference") 3709 reference = f" {reference}" if reference else "" 3710 delete = self.sql(expression, "delete") 3711 delete = f" ON DELETE {delete}" if delete else "" 3712 update = self.sql(expression, "update") 3713 update = f" ON UPDATE {update}" if update else "" 3714 options = self.expressions(expression, key="options", flat=True, sep=" ") 3715 options = f" {options}" if options else "" 3716 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3718 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3719 this = self.sql(expression, "this") 3720 this = f" {this}" if this else "" 3721 expressions = self.expressions(expression, flat=True) 3722 include = self.sql(expression, "include") 3723 options = self.expressions(expression, key="options", flat=True, sep=" ") 3724 options = f" {options}" if options else "" 3725 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3730 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3731 if self.MATCH_AGAINST_TABLE_PREFIX: 3732 expressions = [] 3733 for expr in expression.expressions: 3734 if isinstance(expr, exp.Table): 3735 expressions.append(f"TABLE {self.sql(expr)}") 3736 else: 3737 expressions.append(expr) 3738 else: 3739 expressions = expression.expressions 3740 3741 modifier = expression.args.get("modifier") 3742 modifier = f" {modifier}" if modifier else "" 3743 return ( 3744 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3745 )
3758 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3759 if isinstance(expression, exp.JSONPathPart): 3760 transform = self.TRANSFORMS.get(expression.__class__) 3761 if not callable(transform): 3762 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3763 return "" 3764 3765 return transform(self, expression) 3766 3767 if isinstance(expression, int): 3768 return str(expression) 3769 3770 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3771 escaped = expression.replace("'", "\\'") 3772 escaped = f"\\'{expression}\\'" 3773 else: 3774 escaped = expression.replace('"', '\\"') 3775 escaped = f'"{escaped}"' 3776 3777 return escaped
3782 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3783 # Output the Teradata column FORMAT override. 3784 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3785 this = self.sql(expression, "this") 3786 fmt = self.sql(expression, "format") 3787 return f"{this} (FORMAT {fmt})"
3815 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3816 null_handling = expression.args.get("null_handling") 3817 null_handling = f" {null_handling}" if null_handling else "" 3818 return_type = self.sql(expression, "return_type") 3819 return_type = f" RETURNING {return_type}" if return_type else "" 3820 strict = " STRICT" if expression.args.get("strict") else "" 3821 return self.func( 3822 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3823 )
3825 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3826 this = self.sql(expression, "this") 3827 order = self.sql(expression, "order") 3828 null_handling = expression.args.get("null_handling") 3829 null_handling = f" {null_handling}" if null_handling else "" 3830 return_type = self.sql(expression, "return_type") 3831 return_type = f" RETURNING {return_type}" if return_type else "" 3832 strict = " STRICT" if expression.args.get("strict") else "" 3833 return self.func( 3834 "JSON_ARRAYAGG", 3835 this, 3836 suffix=f"{order}{null_handling}{return_type}{strict})", 3837 )
3839 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3840 path = self.sql(expression, "path") 3841 path = f" PATH {path}" if path else "" 3842 nested_schema = self.sql(expression, "nested_schema") 3843 3844 if nested_schema: 3845 return f"NESTED{path} {nested_schema}" 3846 3847 this = self.sql(expression, "this") 3848 kind = self.sql(expression, "kind") 3849 kind = f" {kind}" if kind else "" 3850 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3851 3852 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3853 return f"{this}{kind}{format_json}{path}{ordinality}"
3858 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3859 this = self.sql(expression, "this") 3860 path = self.sql(expression, "path") 3861 path = f", {path}" if path else "" 3862 error_handling = expression.args.get("error_handling") 3863 error_handling = f" {error_handling}" if error_handling else "" 3864 empty_handling = expression.args.get("empty_handling") 3865 empty_handling = f" {empty_handling}" if empty_handling else "" 3866 schema = self.sql(expression, "schema") 3867 return self.func( 3868 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3869 )
3871 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3872 this = self.sql(expression, "this") 3873 kind = self.sql(expression, "kind") 3874 path = self.sql(expression, "path") 3875 path = f" {path}" if path else "" 3876 as_json = " AS JSON" if expression.args.get("as_json") else "" 3877 return f"{this} {kind}{path}{as_json}"
3879 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3880 this = self.sql(expression, "this") 3881 path = self.sql(expression, "path") 3882 path = f", {path}" if path else "" 3883 expressions = self.expressions(expression) 3884 with_ = ( 3885 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3886 if expressions 3887 else "" 3888 ) 3889 return f"OPENJSON({this}{path}){with_}"
3891 def in_sql(self, expression: exp.In) -> str: 3892 query = expression.args.get("query") 3893 unnest = expression.args.get("unnest") 3894 field = expression.args.get("field") 3895 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3896 3897 if query: 3898 in_sql = self.sql(query) 3899 elif unnest: 3900 in_sql = self.in_unnest_op(unnest) 3901 elif field: 3902 in_sql = self.sql(field) 3903 else: 3904 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3905 3906 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3911 def interval_sql(self, expression: exp.Interval) -> str: 3912 unit_expression = expression.args.get("unit") 3913 unit = self.sql(unit_expression) if unit_expression else "" 3914 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3915 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3916 unit = f" {unit}" if unit else "" 3917 3918 if self.SINGLE_STRING_INTERVAL: 3919 this = expression.this.name if expression.this else "" 3920 if this: 3921 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3922 return f"INTERVAL '{this}'{unit}" 3923 return f"INTERVAL '{this}{unit}'" 3924 return f"INTERVAL{unit}" 3925 3926 this = self.sql(expression, "this") 3927 if this: 3928 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3929 this = f" {this}" if unwrapped else f" ({this})" 3930 3931 return f"INTERVAL{this}{unit}"
3936 def reference_sql(self, expression: exp.Reference) -> str: 3937 this = self.sql(expression, "this") 3938 expressions = self.expressions(expression, flat=True) 3939 expressions = f"({expressions})" if expressions else "" 3940 options = self.expressions(expression, key="options", flat=True, sep=" ") 3941 options = f" {options}" if options else "" 3942 return f"REFERENCES {this}{expressions}{options}"
3944 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3945 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3946 parent = expression.parent 3947 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3948 3949 return self.func( 3950 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3951 )
3971 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3972 alias = expression.args["alias"] 3973 3974 parent = expression.parent 3975 pivot = parent and parent.parent 3976 3977 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3978 identifier_alias = isinstance(alias, exp.Identifier) 3979 literal_alias = isinstance(alias, exp.Literal) 3980 3981 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3982 alias.replace(exp.Literal.string(alias.output_name)) 3983 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3984 alias.replace(exp.to_identifier(alias.output_name)) 3985 3986 return self.alias_sql(expression)
def
fromiso8601timestamp_sql( self, expression: sqlglot.expressions.temporal.FromISO8601Timestamp) -> 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:
4024 def connector_sql( 4025 self, 4026 expression: exp.Connector, 4027 op: str, 4028 stack: list[str | exp.Expr] | None = None, 4029 ) -> str: 4030 if stack is not None: 4031 if expression.expressions: 4032 stack.append(self.expressions(expression, sep=f" {op} ")) 4033 else: 4034 stack.append(expression.right) 4035 if expression.comments and self.comments: 4036 op = self.maybe_comment(op, comments=expression.comments) 4037 stack.extend((op, expression.left)) 4038 return op 4039 4040 stack = [expression] 4041 sqls: list[str] = [] 4042 ops = set() 4043 4044 while stack: 4045 node = stack.pop() 4046 if isinstance(node, exp.Connector): 4047 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4048 else: 4049 sql = self.sql(node) 4050 if sqls and sqls[-1] in ops: 4051 sqls[-1] += f" {sql}" 4052 else: 4053 sqls.append(sql) 4054 4055 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4056 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4076 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4077 format_sql = self.sql(expression, "format") 4078 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4079 to_sql = self.sql(expression, "to") 4080 to_sql = f" {to_sql}" if to_sql else "" 4081 action = self.sql(expression, "action") 4082 action = f" {action}" if action else "" 4083 default = self.sql(expression, "default") 4084 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4085 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4111 def comment_sql(self, expression: exp.Comment) -> str: 4112 this = self.sql(expression, "this") 4113 kind = expression.args["kind"] 4114 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4115 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4116 expression_sql = self.sql(expression, "expression") 4117 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4119 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4120 this = self.sql(expression, "this") 4121 delete = " DELETE" if expression.args.get("delete") else "" 4122 recompress = self.sql(expression, "recompress") 4123 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4124 to_disk = self.sql(expression, "to_disk") 4125 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4126 to_volume = self.sql(expression, "to_volume") 4127 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4128 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4130 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4131 where = self.sql(expression, "where") 4132 group = self.sql(expression, "group") 4133 aggregates = self.expressions(expression, key="aggregates") 4134 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4135 4136 if not (where or group or aggregates) and len(expression.expressions) == 1: 4137 return f"TTL {self.expressions(expression, flat=True)}" 4138 4139 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4158 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4159 this = self.sql(expression, "this") 4160 4161 dtype = self.sql(expression, "dtype") 4162 if dtype: 4163 collate = self.sql(expression, "collate") 4164 collate = f" COLLATE {collate}" if collate else "" 4165 using = self.sql(expression, "using") 4166 using = f" USING {using}" if using else "" 4167 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4168 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4169 4170 default = self.sql(expression, "default") 4171 if default: 4172 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4173 4174 comment = self.sql(expression, "comment") 4175 if comment: 4176 return f"ALTER COLUMN {this} COMMENT {comment}" 4177 4178 visible = expression.args.get("visible") 4179 if visible: 4180 return f"ALTER COLUMN {this} SET {visible}" 4181 4182 allow_null = expression.args.get("allow_null") 4183 drop = expression.args.get("drop") 4184 4185 if not drop and not allow_null: 4186 self.unsupported("Unsupported ALTER COLUMN syntax") 4187 4188 if allow_null is not None: 4189 keyword = "DROP" if drop else "SET" 4190 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4191 4192 return f"ALTER COLUMN {this} DROP DEFAULT"
4194 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4195 this = self.sql(expression, "this") 4196 rename_from = self.sql(expression, "rename_from") 4197 if rename_from: 4198 if not self.SUPPORTS_CHANGE_COLUMN: 4199 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4200 return f"CHANGE COLUMN {rename_from} {this}" 4201 if not self.SUPPORTS_MODIFY_COLUMN: 4202 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4203 return f"MODIFY COLUMN {this}"
4219 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4220 compound = " COMPOUND" if expression.args.get("compound") else "" 4221 this = self.sql(expression, "this") 4222 expressions = self.expressions(expression, flat=True) 4223 expressions = f"({expressions})" if expressions else "" 4224 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4226 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4227 if not self.RENAME_TABLE_WITH_DB: 4228 # Remove db from tables 4229 expression = expression.transform( 4230 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4231 ).assert_is(exp.AlterRename) 4232 this = self.sql(expression, "this") 4233 to_kw = " TO" if include_to else "" 4234 return f"RENAME{to_kw} {this}"
4249 def alter_sql(self, expression: exp.Alter) -> str: 4250 actions = expression.args["actions"] 4251 4252 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4253 actions[0], exp.ColumnDef 4254 ): 4255 actions_sql = self.expressions(expression, key="actions", flat=True) 4256 actions_sql = f"ADD {actions_sql}" 4257 else: 4258 actions_list = [] 4259 for action in actions: 4260 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4261 action_sql = self.add_column_sql(action) 4262 else: 4263 action_sql = self.sql(action) 4264 if isinstance(action, exp.Query): 4265 action_sql = f"AS {action_sql}" 4266 4267 actions_list.append(action_sql) 4268 4269 actions_sql = self.format_args(*actions_list).lstrip("\n") 4270 4271 iceberg = ( 4272 "ICEBERG " 4273 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4274 else "" 4275 ) 4276 exists = " IF EXISTS" if expression.args.get("exists") else "" 4277 on_cluster = self.sql(expression, "cluster") 4278 on_cluster = f" {on_cluster}" if on_cluster else "" 4279 only = " ONLY" if expression.args.get("only") else "" 4280 options = self.expressions(expression, key="options") 4281 options = f", {options}" if options else "" 4282 kind = self.sql(expression, "kind") 4283 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4284 check = " WITH CHECK" if expression.args.get("check") else "" 4285 cascade = ( 4286 " CASCADE" 4287 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4288 else "" 4289 ) 4290 this = self.sql(expression, "this") 4291 this = f" {this}" if this else "" 4292 4293 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4300 def add_column_sql(self, expression: exp.Expr) -> str: 4301 sql = self.sql(expression) 4302 if isinstance(expression, exp.Schema): 4303 column_text = " COLUMNS" 4304 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4305 column_text = " COLUMN" 4306 else: 4307 column_text = "" 4308 4309 return f"ADD{column_text} {sql}"
4322 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4323 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4324 location = self.sql(expression, "location") 4325 location = f" {location}" if location else "" 4326 return f"ADD {exists}{self.sql(expression.this)}{location}"
4328 def distinct_sql(self, expression: exp.Distinct) -> str: 4329 this = self.expressions(expression, flat=True) 4330 4331 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4332 case = exp.case() 4333 for arg in expression.expressions: 4334 case = case.when(arg.is_(exp.null()), exp.null()) 4335 this = self.sql(case.else_(f"({this})")) 4336 4337 this = f" {this}" if this else "" 4338 4339 on = self.sql(expression, "on") 4340 on = f" ON {on}" if on else "" 4341 return f"DISTINCT{this}{on}"
4368 def div_sql(self, expression: exp.Div) -> str: 4369 l, r = expression.left, expression.right 4370 4371 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4372 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4373 4374 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4375 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4376 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4377 4378 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4379 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4380 return self.sql( 4381 exp.cast( 4382 l / r, 4383 to=exp.DType.BIGINT, 4384 ) 4385 ) 4386 4387 return self.binary(expression, "/")
4412 def escape_sql(self, expression: exp.Escape) -> str: 4413 this = expression.this 4414 if ( 4415 isinstance(this, (exp.Like, exp.ILike)) 4416 and isinstance(this.expression, (exp.All, exp.Any)) 4417 and not self.SUPPORTS_LIKE_QUANTIFIERS 4418 ): 4419 return self._like_sql(this, escape=expression) 4420 return self.binary(expression, "ESCAPE")
4537 def log_sql(self, expression: exp.Log) -> str: 4538 this = expression.this 4539 expr = expression.expression 4540 4541 if self.dialect.LOG_BASE_FIRST is False: 4542 this, expr = expr, this 4543 elif self.dialect.LOG_BASE_FIRST is None and expr: 4544 if this.name in ("2", "10"): 4545 return self.func(f"LOG{this.name}", expr) 4546 4547 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4548 4549 return self.func("LOG", this, expr)
4558 def binary(self, expression: exp.Binary, op: str) -> str: 4559 sqls: list[str] = [] 4560 stack: list[None | str | exp.Expr] = [expression] 4561 binary_type = type(expression) 4562 4563 while stack: 4564 node = stack.pop() 4565 4566 if type(node) is binary_type: 4567 op_func = node.args.get("operator") 4568 if op_func: 4569 op = f"OPERATOR({self.sql(op_func)})" 4570 4571 stack.append(node.args.get("expression")) 4572 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4573 stack.append(node.args.get("this")) 4574 else: 4575 sqls.append(self.sql(node)) 4576 4577 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4586 def function_fallback_sql(self, expression: exp.Func) -> str: 4587 args = [] 4588 4589 for key in expression.arg_types: 4590 arg_value = expression.args.get(key) 4591 4592 if isinstance(arg_value, list): 4593 for value in arg_value: 4594 args.append(value) 4595 elif arg_value is not None: 4596 args.append(arg_value) 4597 4598 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4599 name = expression.meta_get("name") or expression.sql_name() 4600 else: 4601 name = expression.sql_name() 4602 4603 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:
4616 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4617 arg_sqls = tuple( 4618 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4619 ) 4620 if self.pretty and self.too_wide(arg_sqls): 4621 return self.indent( 4622 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4623 ) 4624 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:
4629 def format_time( 4630 self, 4631 expression: exp.Expr, 4632 inverse_time_mapping: dict[str, str] | None = None, 4633 inverse_time_trie: dict | None = None, 4634 ) -> str | None: 4635 return format_time( 4636 self.sql(expression, "format"), 4637 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4638 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4639 )
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:
4641 def expressions( 4642 self, 4643 expression: exp.Expr | None = None, 4644 key: str | None = None, 4645 sqls: t.Collection[str | exp.Expr] | None = None, 4646 flat: bool = False, 4647 indent: bool = True, 4648 skip_first: bool = False, 4649 skip_last: bool = False, 4650 sep: str = ", ", 4651 prefix: str = "", 4652 dynamic: bool = False, 4653 new_line: bool = False, 4654 ) -> str: 4655 expressions = expression.args.get(key or "expressions") if expression else sqls 4656 4657 if not expressions: 4658 return "" 4659 4660 if flat: 4661 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4662 4663 num_sqls = len(expressions) 4664 result_sqls = [] 4665 4666 for i, e in enumerate(expressions): 4667 sql = self.sql(e, comment=False) 4668 if not sql: 4669 continue 4670 4671 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4672 4673 if self.pretty: 4674 if self.leading_comma: 4675 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4676 else: 4677 result_sqls.append( 4678 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4679 ) 4680 else: 4681 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4682 4683 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4684 if new_line: 4685 result_sqls.insert(0, "") 4686 result_sqls.append("") 4687 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4688 else: 4689 result_sql = "".join(result_sqls) 4690 4691 return ( 4692 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4693 if indent 4694 else result_sql 4695 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4697 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4698 flat = flat or isinstance(expression.parent, exp.Properties) 4699 expressions_sql = self.expressions(expression, flat=flat) 4700 if flat: 4701 return f"{op} {expressions_sql}" 4702 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4704 def naked_property(self, expression: exp.Property) -> str: 4705 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4706 if not property_name: 4707 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4708 return f"{property_name} {self.sql(expression, 'this')}"
4716 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4717 this = self.sql(expression, "this") 4718 expressions = self.no_identify(self.expressions, expression) 4719 expressions = ( 4720 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4721 ) 4722 return f"{this}{expressions}" if expressions.strip() != "" else this
4741 def when_sql(self, expression: exp.When) -> str: 4742 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4743 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4744 condition = self.sql(expression, "condition") 4745 condition = f" AND {condition}" if condition else "" 4746 4747 then_expression = expression.args.get("then") 4748 if isinstance(then_expression, exp.Insert): 4749 this = self.sql(then_expression, "this") 4750 this = f"INSERT {this}" if this else "INSERT" 4751 then = self.sql(then_expression, "expression") 4752 then = f"{this} VALUES {then}" if then else this 4753 elif isinstance(then_expression, exp.Update): 4754 if isinstance(then_expression.args.get("expressions"), exp.Star): 4755 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4756 else: 4757 expressions_sql = self.expressions(then_expression) 4758 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4759 else: 4760 then = self.sql(then_expression) 4761 4762 if isinstance(then_expression, (exp.Insert, exp.Update)): 4763 where = self.sql(then_expression, "where") 4764 if where and not self.SUPPORTS_MERGE_WHERE: 4765 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4766 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4767 where = "" 4768 then = f"{then}{where}" 4769 return f"WHEN {matched}{source}{condition} THEN {then}"
4774 def merge_sql(self, expression: exp.Merge) -> str: 4775 table = expression.this 4776 table_alias = "" 4777 4778 hints = table.args.get("hints") 4779 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4780 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4781 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4782 4783 this = self.sql(table) 4784 using = f"USING {self.sql(expression, 'using')}" 4785 whens = self.sql(expression, "whens") 4786 4787 on = self.sql(expression, "on") 4788 on = f"ON {on}" if on else "" 4789 4790 if not on: 4791 on = self.expressions(expression, key="using_cond") 4792 on = f"USING ({on})" if on else "" 4793 4794 returning = self.sql(expression, "returning") 4795 if returning: 4796 whens = f"{whens}{returning}" 4797 4798 sep = self.sep() 4799 4800 return self.prepend_ctes( 4801 expression, 4802 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4803 )
@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:
4809 @unsupported_args("default") 4810 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4811 if not self.SUPPORTS_TO_NUMBER: 4812 self.unsupported("Unsupported TO_NUMBER function") 4813 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4814 4815 fmt = expression.args.get("format") 4816 if not fmt: 4817 self.unsupported("Conversion format is required for TO_NUMBER") 4818 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4819 4820 return self.func("TO_NUMBER", expression.this, fmt)
4822 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4823 this = self.sql(expression, "this") 4824 kind = self.sql(expression, "kind") 4825 settings_sql = self.expressions(expression, key="settings", sep=" ") 4826 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4827 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:
4848 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4849 expressions = self.expressions(expression, flat=True) 4850 expressions = f" {self.wrap(expressions)}" if expressions else "" 4851 buckets = self.sql(expression, "buckets") 4852 kind = self.sql(expression, "kind") 4853 buckets = f" BUCKETS {buckets}" if buckets else "" 4854 order = self.sql(expression, "order") 4855 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4860 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4861 expressions = self.expressions(expression, key="expressions", flat=True) 4862 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4863 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4864 buckets = self.sql(expression, "buckets") 4865 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4867 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4868 this = self.sql(expression, "this") 4869 having = self.sql(expression, "having") 4870 4871 if having: 4872 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4873 4874 return self.func("ANY_VALUE", this)
4876 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4877 transform = self.func("TRANSFORM", *expression.expressions) 4878 row_format_before = self.sql(expression, "row_format_before") 4879 row_format_before = f" {row_format_before}" if row_format_before else "" 4880 record_writer = self.sql(expression, "record_writer") 4881 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4882 using = f" USING {self.sql(expression, 'command_script')}" 4883 schema = self.sql(expression, "schema") 4884 schema = f" AS {schema}" if schema else "" 4885 row_format_after = self.sql(expression, "row_format_after") 4886 row_format_after = f" {row_format_after}" if row_format_after else "" 4887 record_reader = self.sql(expression, "record_reader") 4888 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4889 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:
4891 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4892 key_block_size = self.sql(expression, "key_block_size") 4893 if key_block_size: 4894 return f"KEY_BLOCK_SIZE = {key_block_size}" 4895 4896 using = self.sql(expression, "using") 4897 if using: 4898 return f"USING {using}" 4899 4900 parser = self.sql(expression, "parser") 4901 if parser: 4902 return f"WITH PARSER {parser}" 4903 4904 comment = self.sql(expression, "comment") 4905 if comment: 4906 return f"COMMENT {comment}" 4907 4908 visible = expression.args.get("visible") 4909 if visible is not None: 4910 return "VISIBLE" if visible else "INVISIBLE" 4911 4912 engine_attr = self.sql(expression, "engine_attr") 4913 if engine_attr: 4914 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4915 4916 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4917 if secondary_engine_attr: 4918 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4919 4920 self.unsupported("Unsupported index constraint option.") 4921 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4927 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4928 kind = self.sql(expression, "kind") 4929 kind = f"{kind} INDEX" if kind else "INDEX" 4930 this = self.sql(expression, "this") 4931 this = f" {this}" if this else "" 4932 index_type = self.sql(expression, "index_type") 4933 index_type = f" USING {index_type}" if index_type else "" 4934 expressions = self.expressions(expression, flat=True) 4935 expressions = f" ({expressions})" if expressions else "" 4936 options = self.expressions(expression, key="options", sep=" ") 4937 options = f" {options}" if options else "" 4938 return f"{kind}{this}{index_type}{expressions}{options}"
4940 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4941 if self.NVL2_SUPPORTED: 4942 return self.function_fallback_sql(expression) 4943 4944 case = exp.Case().when( 4945 expression.this.is_(exp.null()).not_(copy=False), 4946 expression.args["true"], 4947 copy=False, 4948 ) 4949 else_cond = expression.args.get("false") 4950 if else_cond: 4951 case.else_(else_cond, copy=False) 4952 4953 return self.sql(case)
4955 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4956 this = self.sql(expression, "this") 4957 expr = self.sql(expression, "expression") 4958 position = self.sql(expression, "position") 4959 position = f", {position}" if position else "" 4960 iterator = self.sql(expression, "iterator") 4961 condition = self.sql(expression, "condition") 4962 condition = f" IF {condition}" if condition else "" 4963 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5013 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5014 this_sql = self.sql(expression, "this") 5015 if isinstance(expression.this, exp.Table): 5016 this_sql = f"TABLE {this_sql}" 5017 5018 return self.func( 5019 "FORECAST", 5020 this_sql, 5021 expression.args.get("data_col"), 5022 expression.args.get("timestamp_col"), 5023 expression.args.get("model"), 5024 expression.args.get("id_cols"), 5025 expression.args.get("horizon"), 5026 expression.args.get("forecast_end_timestamp"), 5027 expression.args.get("confidence_level"), 5028 expression.args.get("output_historical_time_series"), 5029 expression.args.get("context_window"), 5030 )
5032 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5033 this_sql = self.sql(expression, "this") 5034 if isinstance(expression.this, exp.Table): 5035 this_sql = f"TABLE {this_sql}" 5036 5037 return self.func( 5038 "FEATURES_AT_TIME", 5039 this_sql, 5040 expression.args.get("time"), 5041 expression.args.get("num_rows"), 5042 expression.args.get("ignore_feature_nulls"), 5043 )
5045 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5046 this_sql = self.sql(expression, "this") 5047 if isinstance(expression.this, exp.Table): 5048 this_sql = f"TABLE {this_sql}" 5049 5050 query_table = self.sql(expression, "query_table") 5051 if isinstance(expression.args["query_table"], exp.Table): 5052 query_table = f"TABLE {query_table}" 5053 5054 return self.func( 5055 "VECTOR_SEARCH", 5056 this_sql, 5057 expression.args.get("column_to_search"), 5058 query_table, 5059 expression.args.get("query_column_to_search"), 5060 expression.args.get("top_k"), 5061 expression.args.get("distance_type"), 5062 expression.args.get("options"), 5063 )
5075 def toarray_sql(self, expression: exp.ToArray) -> str: 5076 arg = expression.this 5077 if not arg.type: 5078 import sqlglot.optimizer.annotate_types 5079 5080 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5081 5082 if arg.is_type(exp.DType.ARRAY): 5083 return self.sql(arg) 5084 5085 cond_for_null = arg.is_(exp.null()) 5086 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5088 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5089 this = expression.this 5090 time_format = self.format_time(expression) 5091 5092 if time_format: 5093 return self.sql( 5094 exp.cast( 5095 exp.StrToTime(this=this, format=expression.args["format"]), 5096 exp.DType.TIME, 5097 ) 5098 ) 5099 5100 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5101 return self.sql(this) 5102 5103 return self.sql(exp.cast(this, exp.DType.TIME))
5105 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5106 this = expression.this 5107 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5108 return self.sql(this) 5109 5110 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5112 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5113 this = expression.this 5114 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5115 return self.sql(this) 5116 5117 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5119 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5120 this = expression.this 5121 time_format = self.format_time(expression) 5122 safe = expression.args.get("safe") 5123 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5124 return self.sql( 5125 exp.cast( 5126 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5127 exp.DType.DATE, 5128 ) 5129 ) 5130 5131 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5132 return self.sql(this) 5133 5134 if safe: 5135 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5136 5137 return self.sql(exp.cast(this, exp.DType.DATE))
5149 def lastday_sql(self, expression: exp.LastDay) -> str: 5150 if self.LAST_DAY_SUPPORTS_DATE_PART: 5151 return self.function_fallback_sql(expression) 5152 5153 unit = expression.text("unit") 5154 if unit and unit != "MONTH": 5155 self.unsupported("Date parts are not supported in LAST_DAY.") 5156 5157 return self.func("LAST_DAY", expression.this)
5169 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5170 if self.CAN_IMPLEMENT_ARRAY_ANY: 5171 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5172 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5173 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5174 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5175 5176 import sqlglot.dialects.dialect 5177 5178 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5179 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5180 self.unsupported("ARRAY_ANY is unsupported") 5181 5182 return self.function_fallback_sql(expression)
5184 def struct_sql(self, expression: exp.Struct) -> str: 5185 expression.set( 5186 "expressions", 5187 [ 5188 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5189 if isinstance(e, exp.PropertyEQ) 5190 else e 5191 for e in expression.expressions 5192 ], 5193 ) 5194 5195 return self.function_fallback_sql(expression)
5203 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5204 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5205 tables = f" {self.expressions(expression)}" 5206 5207 exists = " IF EXISTS" if expression.args.get("exists") else "" 5208 5209 on_cluster = self.sql(expression, "cluster") 5210 on_cluster = f" {on_cluster}" if on_cluster else "" 5211 5212 identity = self.sql(expression, "identity") 5213 identity = f" {identity} IDENTITY" if identity else "" 5214 5215 option = self.sql(expression, "option") 5216 option = f" {option}" if option else "" 5217 5218 partition = self.sql(expression, "partition") 5219 partition = f" {partition}" if partition else "" 5220 5221 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5225 def convert_sql(self, expression: exp.Convert) -> str: 5226 to = expression.this 5227 value = expression.expression 5228 style = expression.args.get("style") 5229 safe = expression.args.get("safe") 5230 strict = expression.args.get("strict") 5231 5232 if not to or not value: 5233 return "" 5234 5235 # Retrieve length of datatype and override to default if not specified 5236 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5237 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5238 5239 transformed: exp.Expr | None = None 5240 cast = exp.Cast if strict else exp.TryCast 5241 5242 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5243 if isinstance(style, exp.Literal) and style.is_int: 5244 import sqlglot.dialects.tsql 5245 5246 style_value = style.name 5247 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5248 if not converted_style: 5249 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5250 5251 fmt = exp.Literal.string(converted_style) 5252 5253 if to.this == exp.DType.DATE: 5254 transformed = exp.StrToDate(this=value, format=fmt) 5255 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5256 transformed = exp.StrToTime(this=value, format=fmt) 5257 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5258 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5259 elif to.this == exp.DType.TEXT: 5260 transformed = exp.TimeToStr(this=value, format=fmt) 5261 5262 if not transformed: 5263 transformed = cast(this=value, to=to, safe=safe) 5264 5265 return self.sql(transformed)
5346 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5347 option = self.sql(expression, "this") 5348 5349 if expression.expressions: 5350 upper = option.upper() 5351 5352 # Snowflake FILE_FORMAT options are separated by whitespace 5353 sep = " " if upper == "FILE_FORMAT" else ", " 5354 5355 # Databricks copy/format options do not set their list of values with EQ 5356 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5357 values = self.expressions(expression, flat=True, sep=sep) 5358 return f"{option}{op}({values})" 5359 5360 value = self.sql(expression, "expression") 5361 5362 if not value: 5363 return option 5364 5365 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5366 5367 return f"{option}{op}{value}"
5369 def credentials_sql(self, expression: exp.Credentials) -> str: 5370 cred_expr = expression.args.get("credentials") 5371 if isinstance(cred_expr, exp.Literal): 5372 # Redshift case: CREDENTIALS <string> 5373 credentials = self.sql(expression, "credentials") 5374 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5375 else: 5376 # Snowflake case: CREDENTIALS = (...) 5377 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5378 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5379 5380 storage = self.sql(expression, "storage") 5381 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5382 5383 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5384 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5385 5386 iam_role = self.sql(expression, "iam_role") 5387 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5388 5389 region = self.sql(expression, "region") 5390 region = f" REGION {region}" if region else "" 5391 5392 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5394 def copy_sql(self, expression: exp.Copy) -> str: 5395 this = self.sql(expression, "this") 5396 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5397 5398 credentials = self.sql(expression, "credentials") 5399 credentials = self.seg(credentials) if credentials else "" 5400 files = self.expressions(expression, key="files", flat=True) 5401 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5402 5403 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5404 params = self.expressions( 5405 expression, 5406 key="params", 5407 sep=sep, 5408 new_line=True, 5409 skip_last=True, 5410 skip_first=True, 5411 indent=self.COPY_PARAMS_ARE_WRAPPED, 5412 ) 5413 5414 if params: 5415 if self.COPY_PARAMS_ARE_WRAPPED: 5416 params = f" WITH ({params})" 5417 elif not self.pretty and (files or credentials): 5418 params = f" {params}" 5419 5420 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5425 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5426 on_sql = "ON" if expression.args.get("on") else "OFF" 5427 filter_col: str | None = self.sql(expression, "filter_column") 5428 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5429 retention_period: str | None = self.sql(expression, "retention_period") 5430 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5431 5432 if filter_col or retention_period: 5433 on_sql = self.func("ON", filter_col, retention_period) 5434 5435 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5437 def maskingpolicycolumnconstraint_sql( 5438 self, expression: exp.MaskingPolicyColumnConstraint 5439 ) -> str: 5440 this = self.sql(expression, "this") 5441 expressions = self.expressions(expression, flat=True) 5442 expressions = f" USING ({expressions})" if expressions else "" 5443 return f"MASKING POLICY {this}{expressions}"
5453 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5454 this = self.sql(expression, "this") 5455 expr = expression.expression 5456 5457 if isinstance(expr, exp.Func): 5458 # T-SQL's CLR functions are case sensitive 5459 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5460 else: 5461 expr = self.sql(expression, "expression") 5462 5463 return self.scope_resolution(expr, this)
5471 def rand_sql(self, expression: exp.Rand) -> str: 5472 lower = self.sql(expression, "lower") 5473 upper = self.sql(expression, "upper") 5474 5475 if lower and upper: 5476 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5477 return self.func("RAND", expression.this)
5479 def changes_sql(self, expression: exp.Changes) -> str: 5480 information = self.sql(expression, "information") 5481 information = f"INFORMATION => {information}" 5482 at_before = self.sql(expression, "at_before") 5483 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5484 end = self.sql(expression, "end") 5485 end = f"{self.seg('')}{end}" if end else "" 5486 5487 return f"CHANGES ({information}){at_before}{end}"
5489 def pad_sql(self, expression: exp.Pad) -> str: 5490 prefix = "L" if expression.args.get("is_left") else "R" 5491 5492 fill_pattern = self.sql(expression, "fill_pattern") or None 5493 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5494 fill_pattern = "' '" 5495 5496 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5502 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5503 generate_series = exp.GenerateSeries(**expression.args) 5504 5505 parent = expression.parent 5506 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5507 parent = parent.parent 5508 5509 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5510 return self.sql(exp.Unnest(expressions=[generate_series])) 5511 5512 if isinstance(parent, exp.Select): 5513 self.unsupported("GenerateSeries projection unnesting is not supported.") 5514 5515 return self.sql(generate_series)
5517 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5518 if self.SUPPORTS_CONVERT_TIMEZONE: 5519 return self.function_fallback_sql(expression) 5520 5521 source_tz = expression.args.get("source_tz") 5522 target_tz = expression.args.get("target_tz") 5523 timestamp = expression.args.get("timestamp") 5524 5525 if source_tz and timestamp: 5526 timestamp = exp.AtTimeZone( 5527 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5528 ) 5529 5530 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5531 5532 return self.sql(expr)
5534 def json_sql(self, expression: exp.JSON) -> str: 5535 this = self.sql(expression, "this") 5536 this = f" {this}" if this else "" 5537 5538 _with = expression.args.get("with_") 5539 5540 if _with is None: 5541 with_sql = "" 5542 elif not _with: 5543 with_sql = " WITHOUT" 5544 else: 5545 with_sql = " WITH" 5546 5547 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5548 5549 return f"JSON{this}{with_sql}{unique_sql}"
5551 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5552 path = self.sql(expression, "path") 5553 returning = self.sql(expression, "returning") 5554 returning = f" RETURNING {returning}" if returning else "" 5555 5556 on_condition = self.sql(expression, "on_condition") 5557 on_condition = f" {on_condition}" if on_condition else "" 5558 5559 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5565 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5566 else_ = "ELSE " if expression.args.get("else_") else "" 5567 condition = self.sql(expression, "expression") 5568 condition = f"WHEN {condition} THEN " if condition else else_ 5569 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5570 return f"{condition}{insert}"
5578 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5579 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5580 empty = expression.args.get("empty") 5581 empty = ( 5582 f"DEFAULT {empty} ON EMPTY" 5583 if isinstance(empty, exp.Expr) 5584 else self.sql(expression, "empty") 5585 ) 5586 5587 error = expression.args.get("error") 5588 error = ( 5589 f"DEFAULT {error} ON ERROR" 5590 if isinstance(error, exp.Expr) 5591 else self.sql(expression, "error") 5592 ) 5593 5594 if error and empty: 5595 error = ( 5596 f"{empty} {error}" 5597 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5598 else f"{error} {empty}" 5599 ) 5600 empty = "" 5601 5602 null = self.sql(expression, "null") 5603 5604 return f"{empty}{error}{null}"
5610 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5611 this = self.sql(expression, "this") 5612 path = self.sql(expression, "path") 5613 5614 passing = self.expressions(expression, "passing") 5615 passing = f" PASSING {passing}" if passing else "" 5616 5617 on_condition = self.sql(expression, "on_condition") 5618 on_condition = f" {on_condition}" if on_condition else "" 5619 5620 path = f"{path}{passing}{on_condition}" 5621 5622 return self.func("JSON_EXISTS", this, path)
5747 def overlay_sql(self, expression: exp.Overlay) -> str: 5748 this = self.sql(expression, "this") 5749 expr = self.sql(expression, "expression") 5750 from_sql = self.sql(expression, "from_") 5751 for_sql = self.sql(expression, "for_") 5752 for_sql = f" FOR {for_sql}" if for_sql else "" 5753 5754 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5761 def string_sql(self, expression: exp.String) -> str: 5762 this = expression.this 5763 zone = expression.args.get("zone") 5764 5765 if zone: 5766 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5767 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5768 # set for source_tz to transpile the time conversion before the STRING cast 5769 this = exp.ConvertTimezone( 5770 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5771 ) 5772 5773 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5783 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5784 filler = self.sql(expression, "this") 5785 filler = f" {filler}" if filler else "" 5786 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5787 return f"TRUNCATE{filler} {with_count}"
5789 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5790 if self.SUPPORTS_UNIX_SECONDS: 5791 return self.function_fallback_sql(expression) 5792 5793 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5794 5795 return self.sql( 5796 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5797 )
5799 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5800 dim = expression.expression 5801 5802 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5803 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5804 if not (dim.is_int and dim.name == "1"): 5805 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5806 dim = None 5807 5808 # If dimension is required but not specified, default initialize it 5809 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5810 dim = exp.Literal.number(1) 5811 5812 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5814 def attach_sql(self, expression: exp.Attach) -> str: 5815 this = self.sql(expression, "this") 5816 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5817 expressions = self.expressions(expression) 5818 expressions = f" ({expressions})" if expressions else "" 5819 5820 return f"ATTACH{exists_sql} {this}{expressions}"
5822 def detach_sql(self, expression: exp.Detach) -> str: 5823 kind = self.sql(expression, "kind") 5824 kind = f" {kind}" if kind else "" 5825 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5826 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5827 exists = " IF EXISTS" if expression.args.get("exists") else "" 5828 if exists: 5829 kind = kind or " DATABASE" 5830 5831 this = self.sql(expression, "this") 5832 this = f" {this}" if this else "" 5833 cluster = self.sql(expression, "cluster") 5834 cluster = f" {cluster}" if cluster else "" 5835 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5836 sync = " SYNC" if expression.args.get("sync") else "" 5837 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5850 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5851 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5852 encode = f"{encode} {self.sql(expression, 'this')}" 5853 5854 properties = expression.args.get("properties") 5855 if properties: 5856 encode = f"{encode} {self.properties(properties)}" 5857 5858 return encode
5860 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5861 this = self.sql(expression, "this") 5862 include = f"INCLUDE {this}" 5863 5864 column_def = self.sql(expression, "column_def") 5865 if column_def: 5866 include = f"{include} {column_def}" 5867 5868 alias = self.sql(expression, "alias") 5869 if alias: 5870 include = f"{include} AS {alias}" 5871 5872 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5885 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5886 partitions = self.expressions(expression, "partition_expressions") 5887 create = self.expressions(expression, "create_expressions") 5888 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5890 def partitionbyrangepropertydynamic_sql( 5891 self, expression: exp.PartitionByRangePropertyDynamic 5892 ) -> str: 5893 start = self.sql(expression, "start") 5894 end = self.sql(expression, "end") 5895 5896 every = expression.args["every"] 5897 if isinstance(every, exp.Interval) and every.this.is_string: 5898 every.this.replace(exp.Literal.number(every.name)) 5899 5900 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5913 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5914 kind = self.sql(expression, "kind") 5915 option = self.sql(expression, "option") 5916 option = f" {option}" if option else "" 5917 this = self.sql(expression, "this") 5918 this = f" {this}" if this else "" 5919 columns = self.expressions(expression) 5920 columns = f" {columns}" if columns else "" 5921 return f"{kind}{option} STATISTICS{this}{columns}"
5923 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5924 this = self.sql(expression, "this") 5925 columns = self.expressions(expression) 5926 inner_expression = self.sql(expression, "expression") 5927 inner_expression = f" {inner_expression}" if inner_expression else "" 5928 update_options = self.sql(expression, "update_options") 5929 update_options = f" {update_options} UPDATE" if update_options else "" 5930 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5941 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5942 kind = self.sql(expression, "kind") 5943 this = self.sql(expression, "this") 5944 this = f" {this}" if this else "" 5945 inner_expression = self.sql(expression, "expression") 5946 return f"VALIDATE {kind}{this}{inner_expression}"
5948 def analyze_sql(self, expression: exp.Analyze) -> str: 5949 options = self.expressions(expression, key="options", sep=" ") 5950 options = f" {options}" if options else "" 5951 kind = self.sql(expression, "kind") 5952 kind = f" {kind}" if kind else "" 5953 this = self.sql(expression, "this") 5954 this = f" {this}" if this else "" 5955 mode = self.sql(expression, "mode") 5956 mode = f" {mode}" if mode else "" 5957 properties = self.sql(expression, "properties") 5958 properties = f" {properties}" if properties else "" 5959 partition = self.sql(expression, "partition") 5960 partition = f" {partition}" if partition else "" 5961 inner_expression = self.sql(expression, "expression") 5962 inner_expression = f" {inner_expression}" if inner_expression else "" 5963 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5965 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5966 this = self.sql(expression, "this") 5967 namespaces = self.expressions(expression, key="namespaces") 5968 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5969 passing = self.expressions(expression, key="passing") 5970 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5971 columns = self.expressions(expression, key="columns") 5972 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5973 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5974 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5980 def export_sql(self, expression: exp.Export) -> str: 5981 this = self.sql(expression, "this") 5982 connection = self.sql(expression, "connection") 5983 connection = f"WITH CONNECTION {connection} " if connection else "" 5984 options = self.sql(expression, "options") 5985 return f"EXPORT DATA {connection}{options} AS {this}"
5991 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5992 variables = self.expressions(expression, "this") 5993 default = self.sql(expression, "default") 5994 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5995 5996 kind = self.sql(expression, "kind") 5997 if isinstance(expression.args.get("kind"), exp.Schema): 5998 kind = f"TABLE {kind}" 5999 6000 kind = f" {kind}" if kind else "" 6001 6002 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6004 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6005 kind = self.sql(expression, "kind") 6006 this = self.sql(expression, "this") 6007 set = self.sql(expression, "expression") 6008 using = self.sql(expression, "using") 6009 using = f" USING {using}" if using else "" 6010 6011 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6012 6013 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:
6036 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6037 # Snowflake GET/PUT statements: 6038 # PUT <file> <internalStage> <properties> 6039 # GET <internalStage> <file> <properties> 6040 props = expression.args.get("properties") 6041 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6042 this = self.sql(expression, "this") 6043 target = self.sql(expression, "target") 6044 6045 if isinstance(expression, exp.Put): 6046 return f"PUT {this} {target}{props_sql}" 6047 else: 6048 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6050 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6051 this = self.sql(expression, "this") 6052 expr = self.sql(expression, "expression") 6053 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6054 return f"TRANSLATE({this} USING {expr}{with_error})"
6056 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6057 if self.SUPPORTS_DECODE_CASE: 6058 return self.func("DECODE", *expression.expressions) 6059 6060 decode_expr, *expressions = expression.expressions 6061 6062 ifs = [] 6063 for search, result in zip(expressions[::2], expressions[1::2]): 6064 if isinstance(search, exp.Literal): 6065 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6066 elif isinstance(search, exp.Null): 6067 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6068 else: 6069 if isinstance(search, exp.Binary): 6070 search = exp.paren(search) 6071 6072 cond = exp.or_( 6073 decode_expr.eq(search), 6074 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6075 copy=False, 6076 ) 6077 ifs.append(exp.If(this=cond, true=result)) 6078 6079 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6080 return self.sql(case)
6082 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6083 this = self.sql(expression, "this") 6084 this = self.seg(this, sep="") 6085 dimensions = self.expressions( 6086 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6087 ) 6088 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6089 metrics = self.expressions( 6090 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6091 ) 6092 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6093 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6094 facts = self.seg(f"FACTS {facts}") if facts else "" 6095 where = self.sql(expression, "where") 6096 where = self.seg(f"WHERE {where}") if where else "" 6097 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6098 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6100 def getextract_sql(self, expression: exp.GetExtract) -> str: 6101 this = expression.this 6102 expr = expression.expression 6103 6104 if not this.type or not expression.type: 6105 import sqlglot.optimizer.annotate_types 6106 6107 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6108 6109 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6110 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6111 6112 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6129 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6130 method = self.sql(expression, "method") 6131 kind = expression.args.get("kind") 6132 if not kind: 6133 return f"REFRESH {method}" 6134 6135 every = self.sql(expression, "every") 6136 unit = self.sql(expression, "unit") 6137 every = f" EVERY {every} {unit}" if every else "" 6138 starts = self.sql(expression, "starts") 6139 starts = f" STARTS {starts}" if starts else "" 6140 6141 return f"REFRESH {method} ON {kind}{every}{starts}"
6150 def uuid_sql(self, expression: exp.Uuid) -> str: 6151 is_string = expression.args.get("is_string", False) 6152 uuid_func_sql = self.func("UUID") 6153 6154 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6155 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6156 6157 return uuid_func_sql
6159 def initcap_sql(self, expression: exp.Initcap) -> str: 6160 delimiters = expression.expression 6161 6162 if delimiters: 6163 # do not generate delimiters arg if we are round-tripping from default delimiters 6164 if ( 6165 delimiters.is_string 6166 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6167 ): 6168 delimiters = None 6169 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6170 self.unsupported("INITCAP does not support custom delimiters") 6171 delimiters = None 6172 6173 return self.func("INITCAP", expression.this, delimiters)
6183 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6184 this = expression.this.name.upper() 6185 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 6186 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 6187 return "WEEK" 6188 6189 return self.func("WEEK", expression.this)
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: