sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from decimal import Decimal 8from functools import reduce, wraps 9 10from sqlglot import exp 11from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 12from sqlglot.expressions import apply_index_offset 13from sqlglot.expressions.core import maybe_parse 14from sqlglot.helper import csv, name_sequence, seq_get 15from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 16from sqlglot.time import format_time 17from sqlglot.tokens import TokenType 18 19if t.TYPE_CHECKING: 20 from sqlglot._typing import E 21 from sqlglot.dialects.dialect import DialectType 22 23 G = t.TypeVar("G", bound="Generator") 24 GeneratorMethod = t.Callable[[G, E], str] 25 26logger = logging.getLogger("sqlglot") 27 28ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 29UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 30 31 32def unsupported_args( 33 *args: str | tuple[str, str], 34) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 35 """ 36 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 37 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 38 """ 39 diagnostic_by_arg: dict[str, str | None] = {} 40 for arg in args: 41 if isinstance(arg, str): 42 diagnostic_by_arg[arg] = None 43 else: 44 diagnostic_by_arg[arg[0]] = arg[1] 45 46 def decorator(func: GeneratorMethod) -> GeneratorMethod: 47 @wraps(func) 48 def _func(generator: G, expression: E) -> str: 49 expression_name = expression.__class__.__name__ 50 dialect_name = generator.dialect.__class__.__name__ 51 52 for arg_name, diagnostic in diagnostic_by_arg.items(): 53 if expression.args.get(arg_name): 54 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 55 arg_name, expression_name, dialect_name 56 ) 57 generator.unsupported(diagnostic) 58 59 return func(generator, expression) 60 61 return _func 62 63 return decorator 64 65 66AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 67 "windows": lambda self, e: ( 68 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 69 if e.args.get("windows") 70 else "" 71 ), 72 "qualify": lambda self, e: self.sql(e, "qualify"), 73} 74 75 76_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 77 78 79def _build_dispatch( 80 cls: type[Generator], 81) -> dict[type[exp.Expr], t.Callable[..., str]]: 82 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 83 84 for attr_name in dir(cls): 85 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 86 continue 87 88 expr_key = attr_name[:-4] 89 expr_cls = exp.EXPR_CLASSES.get(expr_key) 90 91 if expr_cls and expr_cls not in dispatch: 92 dispatch[expr_cls] = getattr(cls, attr_name) 93 94 return dispatch 95 96 97class Generator: 98 """ 99 Generator converts a given syntax tree to the corresponding SQL string. 100 101 Args: 102 pretty: Whether to format the produced SQL string. 103 Default: False. 104 identify: Determines when an identifier should be quoted. Possible values are: 105 False (default): Never quote, except in cases where it's mandatory by the dialect. 106 True: Always quote except for specials cases. 107 'safe': Only quote identifiers that are case insensitive. 108 normalize: Whether to normalize identifiers to lowercase. 109 Default: False. 110 pad: The pad size in a formatted string. For example, this affects the indentation of 111 a projection in a query, relative to its nesting level. 112 Default: 2. 113 indent: The indentation size in a formatted string. For example, this affects the 114 indentation of subqueries and filters under a `WHERE` clause. 115 Default: 2. 116 normalize_functions: How to normalize function names. Possible values are: 117 "upper" or True (default): Convert names to uppercase. 118 "lower": Convert names to lowercase. 119 False: Disables function name normalization. 120 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 121 Default ErrorLevel.WARN. 122 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 123 This is only relevant if unsupported_level is ErrorLevel.RAISE. 124 Default: 3 125 leading_comma: Whether the comma is leading or trailing in select expressions. 126 This is only relevant when generating in pretty mode. 127 Default: False 128 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 129 The default is on the smaller end because the length only represents a segment and not the true 130 line length. 131 Default: 80 132 comments: Whether to preserve comments in the output SQL code. 133 Default: True 134 """ 135 136 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 137 **JSON_PATH_PART_TRANSFORMS, 138 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 139 exp.AllowedValuesProperty: lambda self, e: ( 140 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 141 ), 142 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 143 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 144 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 145 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 146 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 147 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 148 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 149 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 150 exp.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 intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 339 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 340 AUTO_REFRESH_BARE_INTERVALS = False 341 342 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 343 LIMIT_FETCH = "ALL" 344 345 # Whether limit and fetch allows expresions or just limits 346 LIMIT_ONLY_LITERALS = False 347 348 # Whether a table is allowed to be renamed with a db 349 RENAME_TABLE_WITH_DB = True 350 351 # The separator for grouping sets and rollups 352 GROUPINGS_SEP = "," 353 354 # The string used for creating an index on a table 355 INDEX_ON = "ON" 356 357 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 358 INOUT_SEPARATOR = " " 359 360 # Whether join hints should be generated 361 JOIN_HINTS = True 362 363 # Whether directed joins are supported 364 DIRECTED_JOINS = False 365 366 # Whether table hints should be generated 367 TABLE_HINTS = True 368 369 # Whether query hints should be generated 370 QUERY_HINTS = True 371 372 # What kind of separator to use for query hints 373 QUERY_HINT_SEP = ", " 374 375 # Whether comparing against booleans (e.g. x IS TRUE) is supported 376 IS_BOOL_ALLOWED = True 377 378 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 379 DUPLICATE_KEY_UPDATE_WITH_SET = True 380 381 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 382 LIMIT_IS_TOP = False 383 384 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 385 RETURNING_END = True 386 387 # Whether to generate an unquoted value for EXTRACT's date part argument 388 EXTRACT_ALLOWS_QUOTES = True 389 390 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 391 TZ_TO_WITH_TIME_ZONE = False 392 393 # Whether the NVL2 function is supported 394 NVL2_SUPPORTED = True 395 396 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 397 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 398 399 # Whether VALUES statements can be used as derived tables. 400 # MySQL 5 and Redshift do not allow this, so when False, it will convert 401 # SELECT * VALUES into SELECT UNION 402 VALUES_AS_TABLE = True 403 404 # Whether the word COLUMN is included when adding a column with ALTER TABLE 405 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 406 407 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 408 UNNEST_WITH_ORDINALITY = True 409 410 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 411 SEMI_ANTI_JOIN_WITH_SIDE = True 412 413 # Whether to include the type of a computed column in the CREATE DDL 414 COMPUTED_COLUMN_WITH_TYPE = True 415 416 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 417 SUPPORTS_TABLE_COPY = True 418 419 # Whether parentheses are required around the table sample's expression 420 TABLESAMPLE_REQUIRES_PARENS = True 421 422 # Whether a table sample clause's size needs to be followed by the ROWS keyword 423 TABLESAMPLE_SIZE_IS_ROWS = True 424 425 # The keyword(s) to use when generating a sample clause 426 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 427 428 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 429 TABLESAMPLE_WITH_METHOD = True 430 431 # The keyword to use when specifying the seed of a sample clause 432 TABLESAMPLE_SEED_KEYWORD = "SEED" 433 434 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 435 HISTORICAL_DATA_POST_ALIAS = False 436 437 # Whether COLLATE is a function instead of a binary operator 438 COLLATE_IS_FUNC = False 439 440 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 441 DATA_TYPE_SPECIFIERS_ALLOWED = False 442 443 # Whether conditions require booleans WHERE x = 0 vs WHERE x 444 ENSURE_BOOLS = False 445 446 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 447 CTE_RECURSIVE_KEYWORD_REQUIRED = True 448 449 # Whether CONCAT requires >1 arguments 450 SUPPORTS_SINGLE_ARG_CONCAT = True 451 452 # Whether LAST_DAY function supports a date part argument 453 LAST_DAY_SUPPORTS_DATE_PART = True 454 455 # Whether named columns are allowed in table aliases 456 SUPPORTS_TABLE_ALIAS_COLUMNS = True 457 458 # Whether named columns are allowed in CTE definitions 459 SUPPORTS_NAMED_CTE_COLUMNS = True 460 461 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 462 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 463 464 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 465 PIVOT_ALIAS_WITH_AS = True 466 467 # What delimiter to use for separating JSON key/value pairs 468 JSON_KEY_VALUE_PAIR_SEP = ":" 469 470 # INSERT OVERWRITE TABLE x override 471 INSERT_OVERWRITE = " OVERWRITE TABLE" 472 473 # Whether the SELECT .. INTO syntax is used instead of CTAS 474 SUPPORTS_SELECT_INTO = False 475 476 # Whether UNLOGGED tables can be created 477 SUPPORTS_UNLOGGED_TABLES = False 478 479 # Whether the CREATE TABLE LIKE statement is supported 480 SUPPORTS_CREATE_TABLE_LIKE = True 481 482 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 483 SUPPORTS_MODIFY_COLUMN = False 484 485 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 486 SUPPORTS_CHANGE_COLUMN = False 487 488 # Whether ALTER COLUMN can set a column's nullability together with its type 489 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 490 491 # Whether ALTER COLUMN IF EXISTS is supported 492 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 493 494 # Whether the LikeProperty needs to be specified inside of the schema clause 495 LIKE_PROPERTY_INSIDE_SCHEMA = False 496 497 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 498 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 499 MULTI_ARG_DISTINCT = True 500 501 # Whether the JSON extraction operators expect a value of type JSON 502 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 503 504 # Whether bracketed keys like ["foo"] are supported in JSON paths 505 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 506 507 # Whether to escape keys using single quotes in JSON paths 508 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 509 510 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 511 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 512 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 513 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 514 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 515 516 # The JSONPathPart expressions supported by this dialect 517 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 518 519 # Whether any(f(x) for x in array) can be implemented by this dialect 520 CAN_IMPLEMENT_ARRAY_ANY = False 521 522 # Whether the function TO_NUMBER is supported 523 SUPPORTS_TO_NUMBER = True 524 525 # Whether EXCLUDE in window specification is supported 526 SUPPORTS_WINDOW_EXCLUDE = False 527 528 # Whether or not set op modifiers apply to the outer set op or select. 529 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 530 # True means limit 1 happens after the set op, False means it it happens on y. 531 SET_OP_MODIFIERS = True 532 533 # Whether parameters from COPY statement are wrapped in parentheses 534 COPY_PARAMS_ARE_WRAPPED = True 535 536 # Whether values of params are set with "=" token or empty space 537 COPY_PARAMS_EQ_REQUIRED = False 538 539 # Whether COPY statement has INTO keyword 540 COPY_HAS_INTO_KEYWORD = True 541 542 # Whether the conditional TRY(expression) function is supported 543 TRY_SUPPORTED = True 544 545 # Whether the UESCAPE syntax in unicode strings is supported 546 SUPPORTS_UESCAPE = True 547 548 # Function used to replace escaped unicode codes in unicode strings 549 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 550 551 # The keyword to use when generating a star projection with excluded columns 552 STAR_EXCEPT = "EXCEPT" 553 554 # The HEX function name 555 HEX_FUNC = "HEX" 556 557 # The keywords to use when prefixing & separating WITH based properties 558 WITH_PROPERTIES_PREFIX = "WITH" 559 560 # Whether to quote the generated expression of exp.JsonPath 561 QUOTE_JSON_PATH = True 562 563 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 564 PAD_FILL_PATTERN_IS_REQUIRED = False 565 566 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 567 SUPPORTS_EXPLODING_PROJECTIONS = True 568 569 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 570 ARRAY_CONCAT_IS_VAR_LEN = True 571 572 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 573 SUPPORTS_CONVERT_TIMEZONE = False 574 575 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 576 SUPPORTS_MEDIAN = True 577 578 # Whether UNIX_SECONDS(timestamp) is supported 579 SUPPORTS_UNIX_SECONDS = False 580 581 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 582 ALTER_SET_WRAPPED = False 583 584 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 585 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 586 # TODO: The normalization should be done by default once we've tested it across all dialects. 587 NORMALIZE_EXTRACT_DATE_PARTS = False 588 589 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 590 PARSE_JSON_NAME: str | None = "PARSE_JSON" 591 592 # The function name of the exp.ArraySize expression 593 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 594 595 # The syntax to use when altering the type of a column 596 ALTER_SET_TYPE = "SET DATA TYPE" 597 598 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 599 # None -> Doesn't support it at all 600 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 601 # True (Postgres) -> Explicitly requires it 602 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 603 604 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 605 SUPPORTS_DECODE_CASE = True 606 607 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 608 SUPPORTS_BETWEEN_FLAGS = False 609 610 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 611 SUPPORTS_LIKE_QUANTIFIERS = True 612 613 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 614 MATCH_AGAINST_TABLE_PREFIX: str | None = None 615 616 # Whether to include the VARIABLE keyword for SET assignments 617 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 618 619 # The keyword to use for default value assignment in DECLARE statements 620 DECLARE_DEFAULT_ASSIGNMENT = "=" 621 622 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 623 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 624 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 625 UPDATE_STATEMENT_SUPPORTS_FROM = True 626 627 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 628 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 629 630 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 631 # - Snowflake: DROP ICEBERG TABLE a.b; 632 # - DuckDB: DROP TABLE a.b; 633 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 634 635 TYPE_MAPPING: t.ClassVar = { 636 exp.DType.DATETIME2: "TIMESTAMP", 637 exp.DType.NCHAR: "CHAR", 638 exp.DType.NVARCHAR: "VARCHAR", 639 exp.DType.MEDIUMTEXT: "TEXT", 640 exp.DType.LONGTEXT: "TEXT", 641 exp.DType.TINYTEXT: "TEXT", 642 exp.DType.BLOB: "VARBINARY", 643 exp.DType.MEDIUMBLOB: "BLOB", 644 exp.DType.LONGBLOB: "BLOB", 645 exp.DType.TINYBLOB: "BLOB", 646 exp.DType.INET: "INET", 647 exp.DType.ROWVERSION: "VARBINARY", 648 exp.DType.SMALLDATETIME: "TIMESTAMP", 649 } 650 651 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 652 653 # mapping of DType to its default parameters, bounds 654 TYPE_PARAM_SETTINGS: t.ClassVar[ 655 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 656 ] = {} 657 658 TIME_PART_SINGULARS: t.ClassVar = { 659 "MICROSECONDS": "MICROSECOND", 660 "SECONDS": "SECOND", 661 "MINUTES": "MINUTE", 662 "HOURS": "HOUR", 663 "DAYS": "DAY", 664 "WEEKS": "WEEK", 665 "MONTHS": "MONTH", 666 "QUARTERS": "QUARTER", 667 "YEARS": "YEAR", 668 } 669 670 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 671 "cluster": lambda self, e: self.sql(e, "cluster"), 672 "distribute": lambda self, e: self.sql(e, "distribute"), 673 "sort": lambda self, e: self.sql(e, "sort"), 674 **AFTER_HAVING_MODIFIER_TRANSFORMS, 675 } 676 677 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 678 679 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 680 681 PARAMETER_TOKEN = "@" 682 NAMED_PLACEHOLDER_TOKEN = ":" 683 684 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 685 686 PROPERTIES_LOCATION: t.ClassVar = { 687 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 688 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 689 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 690 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 691 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 695 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 697 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 699 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 701 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 702 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 703 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 704 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 705 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 708 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 709 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 711 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 712 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 714 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 718 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 719 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 724 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 725 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 726 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 727 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 728 exp.HeapProperty: exp.Properties.Location.POST_WITH, 729 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 730 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 732 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 735 exp.JournalProperty: exp.Properties.Location.POST_NAME, 736 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 741 exp.LogProperty: exp.Properties.Location.POST_NAME, 742 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 743 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 744 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 745 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 747 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 748 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 750 exp.Order: exp.Properties.Location.POST_SCHEMA, 751 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 753 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 755 exp.Property: exp.Properties.Location.POST_WITH, 756 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 760 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 761 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 767 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 768 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 769 exp.Set: exp.Properties.Location.POST_SCHEMA, 770 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.SetProperty: exp.Properties.Location.POST_CREATE, 772 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 774 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 775 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 776 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 782 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 783 exp.Tags: exp.Properties.Location.POST_WITH, 784 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 785 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 787 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 789 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 790 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 791 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 792 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 794 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 795 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 796 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 797 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 798 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 801 } 802 803 # Keywords that can't be used as unquoted identifier names 804 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 805 806 # Exprs whose comments are separated from them for better formatting 807 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 808 exp.Command, 809 exp.Create, 810 exp.Describe, 811 exp.Delete, 812 exp.Drop, 813 exp.From, 814 exp.Insert, 815 exp.Join, 816 exp.MultitableInserts, 817 exp.Order, 818 exp.Group, 819 exp.Having, 820 exp.Select, 821 exp.SetOperation, 822 exp.Update, 823 exp.Where, 824 exp.With, 825 ) 826 827 # Exprs that should not have their comments generated in maybe_comment 828 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 829 exp.Binary, 830 exp.SetOperation, 831 ) 832 833 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 834 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 835 exp.Column, 836 exp.Literal, 837 exp.Neg, 838 exp.Paren, 839 ) 840 841 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 842 exp.DType.NVARCHAR, 843 exp.DType.VARCHAR, 844 exp.DType.CHAR, 845 exp.DType.NCHAR, 846 } 847 848 # Exprs that need to have all CTEs under them bubbled up to them 849 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 850 851 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 852 853 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 854 855 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 856 857 __slots__ = ( 858 "pretty", 859 "identify", 860 "normalize", 861 "pad", 862 "_indent", 863 "normalize_functions", 864 "unsupported_level", 865 "max_unsupported", 866 "leading_comma", 867 "max_text_width", 868 "comments", 869 "dialect", 870 "unsupported_messages", 871 "_escaped_quote_end", 872 "_escaped_byte_quote_end", 873 "_escaped_identifier_end", 874 "_next_name", 875 "_identifier_start", 876 "_identifier_end", 877 "_quote_json_path_key_using_brackets", 878 "_dispatch", 879 ) 880 881 def __init__( 882 self, 883 pretty: bool | int | None = None, 884 identify: str | bool = False, 885 normalize: bool = False, 886 pad: int = 2, 887 indent: int = 2, 888 normalize_functions: str | bool | None = None, 889 unsupported_level: ErrorLevel = ErrorLevel.WARN, 890 max_unsupported: int = 3, 891 leading_comma: bool = False, 892 max_text_width: int = 80, 893 comments: bool = True, 894 dialect: DialectType = None, 895 ): 896 import sqlglot 897 import sqlglot.dialects.dialect 898 899 self.pretty = pretty if pretty is not None else sqlglot.pretty 900 self.identify = identify 901 self.normalize = normalize 902 self.pad = pad 903 self._indent = indent 904 self.unsupported_level = unsupported_level 905 self.max_unsupported = max_unsupported 906 self.leading_comma = leading_comma 907 self.max_text_width = max_text_width 908 self.comments = comments 909 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 910 911 # This is both a Dialect property and a Generator argument, so we prioritize the latter 912 self.normalize_functions = ( 913 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 914 ) 915 916 self.unsupported_messages: list[str] = [] 917 self._escaped_quote_end: str = ( 918 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 919 ) 920 self._escaped_byte_quote_end: str = ( 921 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 922 if self.dialect.BYTE_END 923 else "" 924 ) 925 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 926 927 self._next_name = name_sequence("_t") 928 929 self._identifier_start = self.dialect.IDENTIFIER_START 930 self._identifier_end = self.dialect.IDENTIFIER_END 931 932 self._quote_json_path_key_using_brackets = True 933 934 cls = type(self) 935 dispatch = _DISPATCH_CACHE.get(cls) 936 if dispatch is None: 937 dispatch = _build_dispatch(cls) 938 _DISPATCH_CACHE[cls] = dispatch 939 self._dispatch = dispatch 940 941 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 942 """ 943 Generates the SQL string corresponding to the given syntax tree. 944 945 Args: 946 expression: The syntax tree. 947 copy: Whether to copy the expression. The generator performs mutations so 948 it is safer to copy. 949 950 Returns: 951 The SQL string corresponding to `expression`. 952 """ 953 if copy: 954 expression = expression.copy() 955 956 expression = self.preprocess(expression) 957 958 self.unsupported_messages = [] 959 sql = self.sql(expression).strip() 960 961 if self.pretty: 962 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 963 964 if self.unsupported_level == ErrorLevel.IGNORE: 965 return sql 966 967 if self.unsupported_level == ErrorLevel.WARN: 968 for msg in self.unsupported_messages: 969 logger.warning(msg) 970 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 971 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 972 973 return sql 974 975 def preprocess(self, expression: exp.Expr) -> exp.Expr: 976 """Apply generic preprocessing transformations to a given expression.""" 977 expression = self._move_ctes_to_top_level(expression) 978 979 if self.ENSURE_BOOLS: 980 import sqlglot.transforms 981 982 expression = sqlglot.transforms.ensure_bools(expression) 983 984 return expression 985 986 def _move_ctes_to_top_level(self, expression: E) -> E: 987 if ( 988 not expression.parent 989 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 990 and any(node.parent is not expression for node in expression.find_all(exp.With)) 991 ): 992 import sqlglot.transforms 993 994 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 995 return expression 996 997 def unsupported(self, message: str) -> None: 998 if self.unsupported_level == ErrorLevel.IMMEDIATE: 999 raise UnsupportedError(message) 1000 self.unsupported_messages.append(message) 1001 1002 def sep(self, sep: str = " ") -> str: 1003 return f"{sep.strip()}\n" if self.pretty else sep 1004 1005 def seg(self, sql: str, sep: str = " ") -> str: 1006 return f"{self.sep(sep)}{sql}" 1007 1008 def sanitize_comment(self, comment: str) -> str: 1009 comment = " " + comment if comment[0].strip() else comment 1010 comment = comment + " " if comment[-1].strip() else comment 1011 1012 # Escape block comment markers to prevent premature closure or unintended nesting. 1013 # This is necessary because single-line comments (--) are converted to block comments 1014 # (/* */) on output, and any */ in the original text would close the comment early. 1015 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1016 1017 return comment 1018 1019 def maybe_comment( 1020 self, 1021 sql: str, 1022 expression: exp.Expr | None = None, 1023 comments: list[str] | None = None, 1024 separated: bool = False, 1025 ) -> str: 1026 comments = ( 1027 ((expression and expression.comments) if comments is None else comments) # type: ignore 1028 if self.comments 1029 else None 1030 ) 1031 1032 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1033 return sql 1034 1035 comments_list = [ 1036 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1037 for comment in comments 1038 if comment 1039 ] 1040 1041 if not comments_list: 1042 return sql 1043 1044 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1045 comments_sql = self.sep().join(comments_list) 1046 return ( 1047 f"{self.sep()}{comments_sql}{sql}" 1048 if not sql or sql[0].isspace() 1049 else f"{comments_sql}{self.sep()}{sql}" 1050 ) 1051 1052 return f"{sql} {' '.join(comments_list)}" 1053 1054 def wrap(self, expression: exp.Expr | str) -> str: 1055 this_sql = ( 1056 self.sql(expression) 1057 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1058 else self.sql(expression, "this") 1059 ) 1060 if not this_sql: 1061 return "()" 1062 1063 this_sql = self.indent(this_sql, level=1, pad=0) 1064 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1065 1066 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1067 original = self.identify 1068 self.identify = False 1069 result = func(*args, **kwargs) 1070 self.identify = original 1071 return result 1072 1073 def normalize_func(self, name: str) -> str: 1074 if self.normalize_functions == "upper" or self.normalize_functions is True: 1075 return name.upper() 1076 if self.normalize_functions == "lower": 1077 return name.lower() 1078 return name 1079 1080 def indent( 1081 self, 1082 sql: str, 1083 level: int = 0, 1084 pad: int | None = None, 1085 skip_first: bool = False, 1086 skip_last: bool = False, 1087 ) -> str: 1088 if not self.pretty or not sql: 1089 return sql 1090 1091 pad = self.pad if pad is None else pad 1092 lines = sql.split("\n") 1093 1094 return "\n".join( 1095 ( 1096 line 1097 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1098 else f"{' ' * (level * self._indent + pad)}{line}" 1099 ) 1100 for i, line in enumerate(lines) 1101 ) 1102 1103 def sql( 1104 self, 1105 expression: str | exp.Expr | None, 1106 key: str | None = None, 1107 comment: bool = True, 1108 ) -> str: 1109 if not expression: 1110 return "" 1111 1112 if isinstance(expression, str): 1113 return expression 1114 1115 if key: 1116 value = expression.args.get(key) 1117 if value: 1118 return self.sql(value) 1119 return "" 1120 1121 handler = self._dispatch.get(expression.__class__) 1122 1123 if handler: 1124 sql = handler(self, expression) 1125 elif isinstance(expression, exp.Func): 1126 sql = self.function_fallback_sql(expression) 1127 elif isinstance(expression, exp.Property): 1128 sql = self.property_sql(expression) 1129 else: 1130 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1131 1132 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1133 1134 def uncache_sql(self, expression: exp.Uncache) -> str: 1135 table = self.sql(expression, "this") 1136 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1137 return f"UNCACHE TABLE{exists_sql} {table}" 1138 1139 def cache_sql(self, expression: exp.Cache) -> str: 1140 lazy = " LAZY" if expression.args.get("lazy") else "" 1141 table = self.sql(expression, "this") 1142 options = expression.args.get("options") 1143 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1144 sql = self.sql(expression, "expression") 1145 sql = f" AS{self.sep()}{sql}" if sql else "" 1146 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1147 return self.prepend_ctes(expression, sql) 1148 1149 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1150 default = "DEFAULT " if expression.args.get("default") else "" 1151 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1152 1153 def column_parts(self, expression: exp.Column) -> str: 1154 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1155 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1156 return self.sql(expression, "this") 1157 1158 return ".".join( 1159 self.sql(part) 1160 for part in ( 1161 expression.args.get("catalog"), 1162 expression.args.get("db"), 1163 expression.args.get("table"), 1164 expression.args.get("this"), 1165 ) 1166 if part 1167 ) 1168 1169 def column_sql(self, expression: exp.Column) -> str: 1170 join_mark = " (+)" if expression.args.get("join_mark") else "" 1171 1172 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1173 join_mark = "" 1174 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1175 1176 return f"{self.column_parts(expression)}{join_mark}" 1177 1178 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1179 return self.column_sql(expression) 1180 1181 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1182 this = self.sql(expression, "this") 1183 this = f" {this}" if this else "" 1184 position = self.sql(expression, "position") 1185 return f"{position}{this}" 1186 1187 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1188 column = self.sql(expression, "this") 1189 kind = self.sql(expression, "kind") 1190 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1191 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1192 kind = f"{sep}{kind}" if kind else "" 1193 constraints = f" {constraints}" if constraints else "" 1194 position = self.sql(expression, "position") 1195 position = f" {position}" if position else "" 1196 1197 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1198 kind = "" 1199 1200 return f"{exists}{column}{kind}{constraints}{position}" 1201 1202 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1203 this = self.sql(expression, "this") 1204 kind_sql = self.sql(expression, "kind").strip() 1205 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1206 1207 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1208 this = self.sql(expression, "this") 1209 if expression.args.get("not_null"): 1210 persisted = " PERSISTED NOT NULL" 1211 elif expression.args.get("persisted"): 1212 persisted = " PERSISTED" 1213 else: 1214 persisted = "" 1215 1216 return f"AS {this}{persisted}" 1217 1218 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1219 return self.token_sql(TokenType.AUTO_INCREMENT) 1220 1221 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1222 if isinstance(expression.this, list): 1223 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1224 else: 1225 this = self.sql(expression, "this") 1226 1227 return f"COMPRESS {this}" 1228 1229 def generatedasidentitycolumnconstraint_sql( 1230 self, expression: exp.GeneratedAsIdentityColumnConstraint 1231 ) -> str: 1232 this = "" 1233 if expression.this is not None: 1234 on_null = " ON NULL" if expression.args.get("on_null") else "" 1235 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1236 1237 start = expression.args.get("start") 1238 start = f"START WITH {start}" if start else "" 1239 increment = expression.args.get("increment") 1240 increment = f" INCREMENT BY {increment}" if increment else "" 1241 minvalue = expression.args.get("minvalue") 1242 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1243 maxvalue = expression.args.get("maxvalue") 1244 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1245 cycle = expression.args.get("cycle") 1246 cycle_sql = "" 1247 1248 if cycle is not None: 1249 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1250 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1251 1252 sequence_opts = "" 1253 if start or increment or cycle_sql: 1254 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1255 sequence_opts = f" ({sequence_opts.strip()})" 1256 1257 expr = self.sql(expression, "expression") 1258 expr = f"({expr})" if expr else "IDENTITY" 1259 1260 return f"GENERATED{this} AS {expr}{sequence_opts}" 1261 1262 def generatedasrowcolumnconstraint_sql( 1263 self, expression: exp.GeneratedAsRowColumnConstraint 1264 ) -> str: 1265 start = "START" if expression.args.get("start") else "END" 1266 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1267 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1268 1269 def periodforsystemtimeconstraint_sql( 1270 self, expression: exp.PeriodForSystemTimeConstraint 1271 ) -> str: 1272 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1273 1274 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1275 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1276 1277 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1278 desc = expression.args.get("desc") 1279 if desc is not None: 1280 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1281 options = self.expressions(expression, key="options", flat=True, sep=" ") 1282 options = f" {options}" if options else "" 1283 return f"PRIMARY KEY{options}" 1284 1285 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1286 this = self.sql(expression, "this") 1287 this = f" {this}" if this else "" 1288 index_type = expression.args.get("index_type") 1289 index_type = f" USING {index_type}" if index_type else "" 1290 on_conflict = self.sql(expression, "on_conflict") 1291 on_conflict = f" {on_conflict}" if on_conflict else "" 1292 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1293 options = self.expressions(expression, key="options", flat=True, sep=" ") 1294 options = f" {options}" if options else "" 1295 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1296 1297 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1298 input_ = expression.args.get("input_") 1299 output = expression.args.get("output") 1300 variadic = expression.args.get("variadic") 1301 1302 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1303 if variadic: 1304 return "VARIADIC" 1305 1306 if input_ and output: 1307 return f"IN{self.INOUT_SEPARATOR}OUT" 1308 if input_: 1309 return "IN" 1310 if output: 1311 return "OUT" 1312 1313 return "" 1314 1315 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1316 return self.sql(expression, "this") 1317 1318 def create_sql(self, expression: exp.Create) -> str: 1319 kind = self.sql(expression, "kind") 1320 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1321 1322 properties = expression.args.get("properties") 1323 1324 if ( 1325 kind == "TRIGGER" 1326 and properties 1327 and properties.expressions 1328 and isinstance(properties.expressions[0], exp.TriggerProperties) 1329 and properties.expressions[0].args.get("constraint") 1330 ): 1331 kind = f"CONSTRAINT {kind}" 1332 1333 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1334 1335 this = self.createable_sql(expression, properties_locs) 1336 1337 properties_sql = "" 1338 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1339 exp.Properties.Location.POST_WITH 1340 ): 1341 props_ast = exp.Properties( 1342 expressions=[ 1343 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1344 *properties_locs[exp.Properties.Location.POST_WITH], 1345 ] 1346 ) 1347 props_ast.parent = expression 1348 properties_sql = self.sql(props_ast) 1349 1350 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1351 properties_sql = self.sep() + properties_sql 1352 elif not self.pretty: 1353 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1354 properties_sql = f" {properties_sql}" 1355 1356 begin = " BEGIN" if expression.args.get("begin") else "" 1357 1358 expression_sql = self.sql(expression, "expression") 1359 if expression_sql: 1360 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1361 1362 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1363 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1364 ): 1365 postalias_props_sql = "" 1366 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1367 postalias_props_sql = self.properties( 1368 exp.Properties( 1369 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1370 ), 1371 wrapped=False, 1372 ) 1373 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1374 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1375 1376 postindex_props_sql = "" 1377 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1378 postindex_props_sql = self.properties( 1379 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1380 wrapped=False, 1381 prefix=" ", 1382 ) 1383 1384 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1385 indexes = f" {indexes}" if indexes else "" 1386 index_sql = indexes + postindex_props_sql 1387 1388 replace = " OR REPLACE" if expression.args.get("replace") else "" 1389 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1390 unique = " UNIQUE" if expression.args.get("unique") else "" 1391 1392 clustered = expression.args.get("clustered") 1393 if clustered is None: 1394 clustered_sql = "" 1395 elif clustered: 1396 clustered_sql = " CLUSTERED COLUMNSTORE" 1397 else: 1398 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1399 1400 postcreate_props_sql = "" 1401 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1402 postcreate_props_sql = self.properties( 1403 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1404 sep=" ", 1405 prefix=" ", 1406 wrapped=False, 1407 ) 1408 1409 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1410 1411 postexpression_props_sql = "" 1412 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1413 postexpression_props_sql = self.properties( 1414 exp.Properties( 1415 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1416 ), 1417 sep=" ", 1418 prefix=" ", 1419 wrapped=False, 1420 ) 1421 1422 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1423 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1424 no_schema_binding = ( 1425 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1426 ) 1427 1428 clone = self.sql(expression, "clone") 1429 clone = f" {clone}" if clone else "" 1430 1431 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1432 properties_expression = f"{expression_sql}{properties_sql}" 1433 else: 1434 properties_expression = f"{properties_sql}{expression_sql}" 1435 1436 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1437 return self.prepend_ctes(expression, expression_sql) 1438 1439 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1440 start = self.sql(expression, "start") 1441 start = f"START WITH {start}" if start else "" 1442 increment = self.sql(expression, "increment") 1443 increment = f" INCREMENT BY {increment}" if increment else "" 1444 minvalue = self.sql(expression, "minvalue") 1445 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1446 maxvalue = self.sql(expression, "maxvalue") 1447 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1448 owned = self.sql(expression, "owned") 1449 owned = f" OWNED BY {owned}" if owned else "" 1450 1451 cache = expression.args.get("cache") 1452 if cache is None: 1453 cache_str = "" 1454 elif cache is True: 1455 cache_str = " CACHE" 1456 else: 1457 cache_str = f" CACHE {cache}" 1458 1459 options = self.expressions(expression, key="options", flat=True, sep=" ") 1460 options = f" {options}" if options else "" 1461 1462 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1463 1464 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1465 timing = expression.args.get("timing", "") 1466 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1467 timing_events = f"{timing} {events}".strip() if timing or events else "" 1468 1469 parts = [timing_events, "ON", self.sql(expression, "table")] 1470 1471 if referenced_table := expression.args.get("referenced_table"): 1472 parts.extend(["FROM", self.sql(referenced_table)]) 1473 1474 if deferrable := expression.args.get("deferrable"): 1475 parts.append(deferrable) 1476 1477 if initially := expression.args.get("initially"): 1478 parts.append(f"INITIALLY {initially}") 1479 1480 if referencing := expression.args.get("referencing"): 1481 parts.append(self.sql(referencing)) 1482 1483 if for_each := expression.args.get("for_each"): 1484 parts.append(f"FOR EACH {for_each}") 1485 1486 if when := expression.args.get("when"): 1487 parts.append(f"WHEN ({self.sql(when)})") 1488 1489 parts.append(self.sql(expression, "execute")) 1490 1491 return self.sep().join(parts) 1492 1493 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1494 parts = [] 1495 1496 if old_alias := expression.args.get("old"): 1497 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1498 1499 if new_alias := expression.args.get("new"): 1500 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1501 1502 return f"REFERENCING {' '.join(parts)}" 1503 1504 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1505 columns = expression.args.get("columns") 1506 if columns: 1507 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1508 1509 return self.sql(expression, "this") 1510 1511 def clone_sql(self, expression: exp.Clone) -> str: 1512 this = self.sql(expression, "this") 1513 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1514 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1515 return f"{shallow}{keyword} {this}" 1516 1517 def describe_sql(self, expression: exp.Describe) -> str: 1518 style = expression.args.get("style") 1519 style = f" {style}" if style else "" 1520 partition = self.sql(expression, "partition") 1521 partition = f" {partition}" if partition else "" 1522 format = self.sql(expression, "format") 1523 format = f" {format}" if format else "" 1524 as_json = " AS JSON" if expression.args.get("as_json") else "" 1525 1526 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1527 1528 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1529 tag = self.sql(expression, "tag") 1530 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1531 1532 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1533 with_ = self.sql(expression, "with_") 1534 if with_: 1535 sql = f"{with_}{self.sep()}{sql}" 1536 return sql 1537 1538 def with_sql(self, expression: exp.With) -> str: 1539 udfs = self.expressions(expression, key="udfs", flat=True) 1540 udfs = f"WITH {udfs}" if udfs else "" 1541 1542 sql = self.expressions(expression, flat=True) 1543 1544 recursive = ( 1545 "RECURSIVE " 1546 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1547 else "" 1548 ) 1549 search = self.sql(expression, "search") 1550 search = f" {search}" if search else "" 1551 1552 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1553 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1554 1555 def cte_sql(self, expression: exp.CTE) -> str: 1556 alias = expression.args.get("alias") 1557 if alias: 1558 alias.add_comments(expression.pop_comments()) 1559 1560 alias_sql = self.sql(expression, "alias") 1561 1562 materialized = expression.args.get("materialized") 1563 if materialized is False: 1564 materialized = "NOT MATERIALIZED " 1565 elif materialized: 1566 materialized = "MATERIALIZED " 1567 1568 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1569 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1570 1571 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1572 1573 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1574 alias = self.sql(expression, "this") 1575 columns = self.expressions(expression, key="columns", flat=True) 1576 columns = f"({columns})" if columns else "" 1577 1578 if ( 1579 columns 1580 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1581 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1582 ): 1583 columns = "" 1584 self.unsupported("Named columns are not supported in table alias.") 1585 1586 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1587 alias = self._next_name() 1588 1589 return f"{alias}{columns}" 1590 1591 def bitstring_sql(self, expression: exp.BitString) -> str: 1592 this = self.sql(expression, "this") 1593 if self.dialect.BIT_START: 1594 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1595 return f"{int(this, 2)}" 1596 1597 def hexstring_sql( 1598 self, expression: exp.HexString, binary_function_repr: str | None = None 1599 ) -> str: 1600 this = self.sql(expression, "this") 1601 is_integer_type = expression.args.get("is_integer") 1602 1603 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1604 not self.dialect.HEX_START and not binary_function_repr 1605 ): 1606 # Integer representation will be returned if: 1607 # - The read dialect treats the hex value as integer literal but not the write 1608 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1609 return f"{int(this, 16)}" 1610 1611 if not is_integer_type: 1612 # Read dialect treats the hex value as BINARY/BLOB 1613 if binary_function_repr: 1614 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1615 return self.func(binary_function_repr, exp.Literal.string(this)) 1616 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1617 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1618 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1619 1620 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1621 1622 def bytestring_sql(self, expression: exp.ByteString) -> str: 1623 this = self.sql(expression, "this") 1624 if self.dialect.BYTE_START: 1625 escaped_byte_string = self.escape_str( 1626 this, 1627 escape_backslash=False, 1628 delimiter=self.dialect.BYTE_END, 1629 escaped_delimiter=self._escaped_byte_quote_end, 1630 is_byte_string=True, 1631 ) 1632 is_bytes = expression.args.get("is_bytes", False) 1633 delimited_byte_string = ( 1634 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1635 ) 1636 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1637 return self.sql( 1638 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1639 ) 1640 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1641 return self.sql( 1642 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1643 ) 1644 1645 return delimited_byte_string 1646 1647 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1648 return self.sql(exp.Literal.string(this)) 1649 1650 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1651 return "" 1652 1653 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1654 this = self.sql(expression, "this") 1655 escape = expression.args.get("escape") 1656 unicode_start = self.dialect.UNICODE_START 1657 1658 if unicode_start: 1659 escape_substitute = r"\\\1" 1660 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1661 else: 1662 escape_substitute = r"\\u\1" 1663 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1664 1665 if escape: 1666 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1667 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1668 else: 1669 escape_pattern = ESCAPED_UNICODE_RE 1670 escape_sql = "" 1671 1672 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1673 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1674 1675 if unicode_start: 1676 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1677 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1678 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1679 else: 1680 this = self.escape_str(this, escape_backslash=False) 1681 1682 return f"{left_quote}{this}{right_quote}{escape_sql}" 1683 1684 def rawstring_sql(self, expression: exp.RawString) -> str: 1685 string = expression.this 1686 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1687 string = string.replace("\\", "\\\\") 1688 1689 string = self.escape_str(string, escape_backslash=False) 1690 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1691 1692 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1693 this = self.sql(expression, "this") 1694 specifier = self.sql(expression, "expression") 1695 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1696 return f"{this}{specifier}" 1697 1698 def datatype_param_bound_limiter( 1699 self, 1700 expression: exp.DataType, 1701 type_value: exp.DType, 1702 defaults: tuple[int, ...], 1703 bounds: tuple[int | None, ...], 1704 ) -> exp.DataType: 1705 params = expression.expressions 1706 1707 if not params: 1708 if defaults: 1709 expression.set( 1710 "expressions", 1711 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1712 ) 1713 return expression 1714 1715 if not bounds: 1716 return expression 1717 1718 for i, param in enumerate(params): 1719 bound = bounds[i] if i < len(bounds) else None 1720 if bound is None: 1721 continue 1722 1723 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1724 value = ( 1725 param_value.to_py() 1726 if isinstance(param_value, exp.Literal) and param_value.is_number 1727 else None 1728 ) 1729 if isinstance(value, (int, Decimal)) and value > bound: 1730 self.unsupported( 1731 f"{type_value.value} parameter {param_value.name} exceeds " 1732 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1733 ) 1734 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1735 1736 return expression 1737 1738 def datatype_sql(self, expression: exp.DataType) -> str: 1739 nested = "" 1740 values = "" 1741 1742 expr_nested = expression.args.get("nested") 1743 type_value = expression.this 1744 1745 if ( 1746 not expr_nested 1747 and isinstance(type_value, exp.DType) 1748 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1749 ): 1750 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1751 1752 interior = ( 1753 self.expressions( 1754 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1755 ) 1756 if expr_nested and self.pretty 1757 else self.expressions(expression, flat=True) 1758 ) 1759 1760 if type_value in self.UNSUPPORTED_TYPES: 1761 self.unsupported( 1762 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1763 ) 1764 1765 type_sql: t.Any = "" 1766 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1767 type_sql = self.sql(expression, "kind") 1768 elif type_value == exp.DType.CHARACTER_SET: 1769 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1770 else: 1771 type_sql = ( 1772 self.TYPE_MAPPING.get(type_value, type_value.value) 1773 if isinstance(type_value, exp.DType) 1774 else type_value 1775 ) 1776 1777 if interior: 1778 if expr_nested: 1779 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1780 if expression.args.get("values") is not None: 1781 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1782 values = self.expressions(expression, key="values", flat=True) 1783 values = f"{delimiters[0]}{values}{delimiters[1]}" 1784 elif type_value == exp.DType.INTERVAL: 1785 nested = f" {interior}" 1786 else: 1787 nested = f"({interior})" 1788 1789 type_sql = f"{type_sql}{nested}{values}" 1790 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1791 exp.DType.TIMETZ, 1792 exp.DType.TIMESTAMPTZ, 1793 ): 1794 type_sql = f"{type_sql} WITH TIME ZONE" 1795 1796 collate = self.sql(expression, "collate") 1797 if collate: 1798 type_sql = f"{type_sql} COLLATE {collate}" 1799 1800 return type_sql 1801 1802 def directory_sql(self, expression: exp.Directory) -> str: 1803 local = "LOCAL " if expression.args.get("local") else "" 1804 row_format = self.sql(expression, "row_format") 1805 row_format = f" {row_format}" if row_format else "" 1806 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1807 1808 def delete_sql(self, expression: exp.Delete) -> str: 1809 hint = self.sql(expression, "hint") 1810 this = self.sql(expression, "this") 1811 this = f" FROM {this}" if this else "" 1812 using = self.expressions(expression, key="using") 1813 using = f" USING {using}" if using else "" 1814 cluster = self.sql(expression, "cluster") 1815 cluster = f" {cluster}" if cluster else "" 1816 where = self.sql(expression, "where") 1817 returning = self.sql(expression, "returning") 1818 order = self.sql(expression, "order") 1819 limit = self.sql(expression, "limit") 1820 tables = self.expressions(expression, key="tables") 1821 tables = f" {tables}" if tables else "" 1822 if self.RETURNING_END: 1823 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1824 else: 1825 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1826 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1827 1828 def drop_sql(self, expression: exp.Drop) -> str: 1829 this = self.sql(expression, "this") 1830 expressions = self.expressions(expression, flat=True) 1831 expressions = f" ({expressions})" if expressions else "" 1832 kind = expression.args["kind"] 1833 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1834 iceberg = ( 1835 " ICEBERG" 1836 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1837 else "" 1838 ) 1839 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1840 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1841 on_cluster = self.sql(expression, "cluster") 1842 on_cluster = f" {on_cluster}" if on_cluster else "" 1843 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1844 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1845 cascade = " CASCADE" if expression.args.get("cascade") else "" 1846 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1847 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1848 purge = " PURGE" if expression.args.get("purge") else "" 1849 sync = " SYNC" if expression.args.get("sync") else "" 1850 force = " FORCE" if expression.args.get("force") else "" 1851 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1852 1853 def set_operation(self, expression: exp.SetOperation) -> str: 1854 op_type = type(expression) 1855 op_name = op_type.key.upper() 1856 1857 distinct = expression.args.get("distinct") 1858 if ( 1859 distinct is False 1860 and op_type in (exp.Except, exp.Intersect) 1861 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1862 ): 1863 self.unsupported(f"{op_name} ALL is not supported") 1864 1865 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1866 1867 if distinct is None: 1868 distinct = default_distinct 1869 if distinct is None: 1870 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1871 1872 if distinct is default_distinct: 1873 distinct_or_all = "" 1874 else: 1875 distinct_or_all = " DISTINCT" if distinct else " ALL" 1876 1877 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1878 side_kind = f"{side_kind} " if side_kind else "" 1879 1880 by_name = " BY NAME" if expression.args.get("by_name") else "" 1881 on = self.expressions(expression, key="on", flat=True) 1882 on = f" ON ({on})" if on else "" 1883 1884 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1885 1886 def set_operations(self, expression: exp.SetOperation) -> str: 1887 if not self.SET_OP_MODIFIERS: 1888 limit = expression.args.get("limit") 1889 order = expression.args.get("order") 1890 1891 if limit or order: 1892 select = self._move_ctes_to_top_level( 1893 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1894 ) 1895 1896 if limit: 1897 select = select.limit(limit.pop(), copy=False) 1898 if order: 1899 select = select.order_by(order.pop(), copy=False) 1900 return self.sql(select) 1901 1902 sqls: list[str] = [] 1903 stack: list[str | exp.Expr] = [expression] 1904 1905 while stack: 1906 node = stack.pop() 1907 1908 if isinstance(node, exp.SetOperation): 1909 stack.append(node.expression) 1910 stack.append( 1911 self.maybe_comment( 1912 self.set_operation(node), comments=node.comments, separated=True 1913 ) 1914 ) 1915 stack.append(node.this) 1916 else: 1917 sqls.append(self.sql(node)) 1918 1919 this = self.sep().join(sqls) 1920 this = self.query_modifiers(expression, this) 1921 return self.prepend_ctes(expression, this) 1922 1923 def fetch_sql(self, expression: exp.Fetch) -> str: 1924 direction = expression.args.get("direction") 1925 direction = f" {direction}" if direction else "" 1926 count = self.sql(expression, "count") 1927 count = f" {count}" if count else "" 1928 limit_options = self.sql(expression, "limit_options") 1929 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1930 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1931 1932 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1933 percent = " PERCENT" if expression.args.get("percent") else "" 1934 rows = " ROWS" if expression.args.get("rows") else "" 1935 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1936 if not with_ties and rows: 1937 with_ties = " ONLY" 1938 return f"{percent}{rows}{with_ties}" 1939 1940 def filter_sql(self, expression: exp.Filter) -> str: 1941 this = self.sql(expression, "this") 1942 where = self.sql(expression, "expression").strip() 1943 return f"{this} FILTER({where})" 1944 1945 def hint_sql(self, expression: exp.Hint) -> str: 1946 if not self.QUERY_HINTS: 1947 self.unsupported("Hints are not supported") 1948 return "" 1949 1950 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1951 1952 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1953 using = self.sql(expression, "using") 1954 using = f" USING {using}" if using else "" 1955 columns = self.expressions(expression, key="columns", flat=True) 1956 columns = f"({columns})" if columns else "" 1957 partition_by = self.expressions(expression, key="partition_by", flat=True) 1958 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1959 where = self.sql(expression, "where") 1960 include = self.expressions(expression, key="include", flat=True) 1961 if include: 1962 include = f" INCLUDE ({include})" 1963 with_storage = self.expressions(expression, key="with_storage", flat=True) 1964 with_storage = f" WITH ({with_storage})" if with_storage else "" 1965 tablespace = self.sql(expression, "tablespace") 1966 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1967 on = self.sql(expression, "on") 1968 on = f" ON {on}" if on else "" 1969 1970 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1971 1972 def index_sql(self, expression: exp.Index) -> str: 1973 unique = "UNIQUE " if expression.args.get("unique") else "" 1974 primary = "PRIMARY " if expression.args.get("primary") else "" 1975 amp = "AMP " if expression.args.get("amp") else "" 1976 name = self.sql(expression, "this") 1977 name = f"{name} " if name else "" 1978 table = self.sql(expression, "table") 1979 table = f"{self.INDEX_ON} {table}" if table else "" 1980 1981 index = "INDEX " if not table else "" 1982 1983 params = self.sql(expression, "params") 1984 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1985 1986 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1987 this = expression.this 1988 if this and this.is_string: 1989 resolved = maybe_parse(this.name).sql(self.dialect) 1990 if "expressions" in expression.args: 1991 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1992 # We can't safely emit the call to other dialects since name/arg semantics may differ 1993 self.unsupported( 1994 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1995 ) 1996 return resolved 1997 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1998 return self.func("IDENTIFIER", this) 1999 2000 def identifier_sql(self, expression: exp.Identifier) -> str: 2001 text = expression.name 2002 lower = text.lower() 2003 quoted = expression.quoted 2004 text = lower if self.normalize and not quoted else text 2005 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2006 if ( 2007 quoted 2008 or self.dialect.can_quote(expression, self.identify) 2009 or lower in self.RESERVED_KEYWORDS 2010 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2011 ): 2012 text = ( 2013 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2014 ) 2015 return text 2016 2017 def hex_sql(self, expression: exp.Hex) -> str: 2018 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2019 if self.dialect.HEX_LOWERCASE: 2020 text = self.func("LOWER", text) 2021 2022 return text 2023 2024 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2025 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2026 if not self.dialect.HEX_LOWERCASE: 2027 text = self.func("LOWER", text) 2028 return text 2029 2030 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2031 input_format = self.sql(expression, "input_format") 2032 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2033 output_format = self.sql(expression, "output_format") 2034 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2035 return self.sep().join((input_format, output_format)) 2036 2037 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2038 string = self.sql(exp.Literal.string(expression.name)) 2039 return f"{prefix}{string}" 2040 2041 def partition_sql(self, expression: exp.Partition) -> str: 2042 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2043 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2044 2045 def properties_sql(self, expression: exp.Properties) -> str: 2046 root_properties = [] 2047 with_properties = [] 2048 2049 for p in expression.expressions: 2050 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2051 if p_loc == exp.Properties.Location.POST_WITH: 2052 with_properties.append(p) 2053 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2054 root_properties.append(p) 2055 2056 root_props_ast = exp.Properties(expressions=root_properties) 2057 root_props_ast.parent = expression.parent 2058 2059 with_props_ast = exp.Properties(expressions=with_properties) 2060 with_props_ast.parent = expression.parent 2061 2062 root_props = self.root_properties(root_props_ast) 2063 with_props = self.with_properties(with_props_ast) 2064 2065 if root_props and with_props and not self.pretty: 2066 with_props = " " + with_props 2067 2068 return root_props + with_props 2069 2070 def root_properties(self, properties: exp.Properties) -> str: 2071 if properties.expressions: 2072 return self.expressions(properties, indent=False, sep=" ") 2073 return "" 2074 2075 def properties( 2076 self, 2077 properties: exp.Properties, 2078 prefix: str = "", 2079 sep: str = ", ", 2080 suffix: str = "", 2081 wrapped: bool = True, 2082 ) -> str: 2083 if properties.expressions: 2084 expressions = self.expressions(properties, sep=sep, indent=False) 2085 if expressions: 2086 expressions = self.wrap(expressions) if wrapped else expressions 2087 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2088 return "" 2089 2090 def with_properties(self, properties: exp.Properties) -> str: 2091 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2092 2093 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2094 properties_locs = defaultdict(list) 2095 for p in properties.expressions: 2096 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2097 if p_loc != exp.Properties.Location.UNSUPPORTED: 2098 properties_locs[p_loc].append(p) 2099 else: 2100 self.unsupported(f"Unsupported property {p.key}") 2101 2102 return properties_locs 2103 2104 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2105 if isinstance(expression.this, exp.Dot): 2106 return self.sql(expression, "this") 2107 return f"'{expression.name}'" if string_key else expression.name 2108 2109 def property_sql(self, expression: exp.Property) -> str: 2110 property_cls = expression.__class__ 2111 if property_cls == exp.Property: 2112 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2113 2114 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2115 if not property_name: 2116 self.unsupported(f"Unsupported property {expression.key}") 2117 2118 return f"{property_name}={self.sql(expression, 'this')}" 2119 2120 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2121 return f"UUID {self.sql(expression, 'this')}" 2122 2123 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2124 if self.SUPPORTS_CREATE_TABLE_LIKE: 2125 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2126 options = f" {options}" if options else "" 2127 2128 like = f"LIKE {self.sql(expression, 'this')}{options}" 2129 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2130 like = f"({like})" 2131 2132 return like 2133 2134 if expression.expressions: 2135 self.unsupported("Transpilation of LIKE property options is unsupported") 2136 2137 select = exp.select("*").from_(expression.this).limit(0) 2138 return f"AS {self.sql(select)}" 2139 2140 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2141 no = "NO " if expression.args.get("no") else "" 2142 protection = " PROTECTION" if expression.args.get("protection") else "" 2143 return f"{no}FALLBACK{protection}" 2144 2145 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2146 no = "NO " if expression.args.get("no") else "" 2147 local = expression.args.get("local") 2148 local = f"{local} " if local else "" 2149 dual = "DUAL " if expression.args.get("dual") else "" 2150 before = "BEFORE " if expression.args.get("before") else "" 2151 after = "AFTER " if expression.args.get("after") else "" 2152 return f"{no}{local}{dual}{before}{after}JOURNAL" 2153 2154 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2155 freespace = self.sql(expression, "this") 2156 percent = " PERCENT" if expression.args.get("percent") else "" 2157 return f"FREESPACE={freespace}{percent}" 2158 2159 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2160 if expression.args.get("default"): 2161 property = "DEFAULT" 2162 elif expression.args.get("on"): 2163 property = "ON" 2164 else: 2165 property = "OFF" 2166 return f"CHECKSUM={property}" 2167 2168 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2169 if expression.args.get("no"): 2170 return "NO MERGEBLOCKRATIO" 2171 if expression.args.get("default"): 2172 return "DEFAULT MERGEBLOCKRATIO" 2173 2174 percent = " PERCENT" if expression.args.get("percent") else "" 2175 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2176 2177 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2178 expressions = self.expressions(expression, flat=True) 2179 expressions = f"({expressions})" if expressions else "" 2180 return f"USING {self.sql(expression, 'this')}{expressions}" 2181 2182 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2183 default = expression.args.get("default") 2184 minimum = expression.args.get("minimum") 2185 maximum = expression.args.get("maximum") 2186 if default or minimum or maximum: 2187 if default: 2188 prop = "DEFAULT" 2189 elif minimum: 2190 prop = "MINIMUM" 2191 else: 2192 prop = "MAXIMUM" 2193 return f"{prop} DATABLOCKSIZE" 2194 units = expression.args.get("units") 2195 units = f" {units}" if units else "" 2196 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2197 2198 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2199 autotemp = expression.args.get("autotemp") 2200 always = expression.args.get("always") 2201 default = expression.args.get("default") 2202 manual = expression.args.get("manual") 2203 never = expression.args.get("never") 2204 2205 if autotemp is not None: 2206 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2207 elif always: 2208 prop = "ALWAYS" 2209 elif default: 2210 prop = "DEFAULT" 2211 elif manual: 2212 prop = "MANUAL" 2213 elif never: 2214 prop = "NEVER" 2215 return f"BLOCKCOMPRESSION={prop}" 2216 2217 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2218 no = expression.args.get("no") 2219 no = " NO" if no else "" 2220 concurrent = expression.args.get("concurrent") 2221 concurrent = " CONCURRENT" if concurrent else "" 2222 target = self.sql(expression, "target") 2223 target = f" {target}" if target else "" 2224 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2225 2226 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2227 if isinstance(expression.this, list): 2228 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2229 if expression.this: 2230 modulus = self.sql(expression, "this") 2231 remainder = self.sql(expression, "expression") 2232 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2233 2234 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2235 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2236 return f"FROM ({from_expressions}) TO ({to_expressions})" 2237 2238 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2239 this = self.sql(expression, "this") 2240 2241 for_values_or_default = expression.expression 2242 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2243 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2244 else: 2245 for_values_or_default = " DEFAULT" 2246 2247 return f"PARTITION OF {this}{for_values_or_default}" 2248 2249 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2250 kind = expression.args.get("kind") 2251 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2252 for_or_in = expression.args.get("for_or_in") 2253 for_or_in = f" {for_or_in}" if for_or_in else "" 2254 lock_type = expression.args.get("lock_type") 2255 override = " OVERRIDE" if expression.args.get("override") else "" 2256 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2257 2258 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2259 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2260 statistics = expression.args.get("statistics") 2261 statistics_sql = "" 2262 if statistics is not None: 2263 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2264 return f"{data_sql}{statistics_sql}" 2265 2266 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2267 this = self.sql(expression, "this") 2268 this = f"HISTORY_TABLE={this}" if this else "" 2269 data_consistency: str | None = self.sql(expression, "data_consistency") 2270 data_consistency = ( 2271 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2272 ) 2273 retention_period: str | None = self.sql(expression, "retention_period") 2274 retention_period = ( 2275 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2276 ) 2277 2278 if this: 2279 on_sql = self.func("ON", this, data_consistency, retention_period) 2280 else: 2281 on_sql = "ON" if expression.args.get("on") else "OFF" 2282 2283 sql = f"SYSTEM_VERSIONING={on_sql}" 2284 2285 return f"WITH({sql})" if expression.args.get("with_") else sql 2286 2287 def insert_sql(self, expression: exp.Insert) -> str: 2288 hint = self.sql(expression, "hint") 2289 overwrite = expression.args.get("overwrite") 2290 2291 if isinstance(expression.this, exp.Directory): 2292 this = " OVERWRITE" if overwrite else " INTO" 2293 else: 2294 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2295 2296 stored = self.sql(expression, "stored") 2297 stored = f" {stored}" if stored else "" 2298 alternative = expression.args.get("alternative") 2299 alternative = f" OR {alternative}" if alternative else "" 2300 ignore = " IGNORE" if expression.args.get("ignore") else "" 2301 is_function = expression.args.get("is_function") 2302 if is_function: 2303 this = f"{this} FUNCTION" 2304 this = f"{this} {self.sql(expression, 'this')}" 2305 2306 exists = " IF EXISTS" if expression.args.get("exists") else "" 2307 where = self.sql(expression, "where") 2308 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2309 using = self.expressions(expression, key="using", flat=True) 2310 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2311 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2312 on_conflict = self.sql(expression, "conflict") 2313 on_conflict = f" {on_conflict}" if on_conflict else "" 2314 by_name = " BY NAME" if expression.args.get("by_name") else "" 2315 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2316 returning = self.sql(expression, "returning") 2317 2318 if self.RETURNING_END: 2319 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2320 else: 2321 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2322 2323 partition_by = self.sql(expression, "partition") 2324 partition_by = f" {partition_by}" if partition_by else "" 2325 settings = self.sql(expression, "settings") 2326 settings = f" {settings}" if settings else "" 2327 2328 source = self.sql(expression, "source") 2329 source = f"TABLE {source}" if source else "" 2330 2331 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2332 return self.prepend_ctes(expression, sql) 2333 2334 def introducer_sql(self, expression: exp.Introducer) -> str: 2335 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2336 2337 def kill_sql(self, expression: exp.Kill) -> str: 2338 kind = self.sql(expression, "kind") 2339 kind = f" {kind}" if kind else "" 2340 this = self.sql(expression, "this") 2341 this = f" {this}" if this else "" 2342 return f"KILL{kind}{this}" 2343 2344 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2345 return expression.name 2346 2347 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2348 return expression.name 2349 2350 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2351 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2352 2353 constraint = self.sql(expression, "constraint") 2354 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2355 2356 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2357 if conflict_keys: 2358 conflict_keys = f"({conflict_keys})" 2359 2360 index_predicate = self.sql(expression, "index_predicate") 2361 conflict_keys = f"{conflict_keys}{index_predicate} " 2362 2363 action = self.sql(expression, "action") 2364 2365 expressions = self.expressions(expression, flat=True) 2366 if expressions: 2367 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2368 expressions = f" {set_keyword}{expressions}" 2369 2370 where = self.sql(expression, "where") 2371 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2372 2373 def returning_sql(self, expression: exp.Returning) -> str: 2374 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2375 2376 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2377 fields = self.sql(expression, "fields") 2378 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2379 escaped = self.sql(expression, "escaped") 2380 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2381 items = self.sql(expression, "collection_items") 2382 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2383 keys = self.sql(expression, "map_keys") 2384 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2385 lines = self.sql(expression, "lines") 2386 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2387 null = self.sql(expression, "null") 2388 null = f" NULL DEFINED AS {null}" if null else "" 2389 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2390 2391 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2392 return f"WITH ({self.expressions(expression, flat=True)})" 2393 2394 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2395 this = f"{self.sql(expression, 'this')} INDEX" 2396 target = self.sql(expression, "target") 2397 target = f" FOR {target}" if target else "" 2398 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2399 2400 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2401 this = self.sql(expression, "this") 2402 kind = self.sql(expression, "kind") 2403 expr = self.sql(expression, "expression") 2404 return f"{this} ({kind} => {expr})" 2405 2406 def table_parts(self, expression: exp.Table) -> str: 2407 return ".".join( 2408 self.sql(part) 2409 for part in ( 2410 expression.args.get("catalog"), 2411 expression.args.get("db"), 2412 expression.args.get("this"), 2413 ) 2414 if part is not None 2415 ) 2416 2417 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2418 table = self.table_parts(expression) 2419 only = "ONLY " if expression.args.get("only") else "" 2420 partition = self.sql(expression, "partition") 2421 partition = f" {partition}" if partition else "" 2422 version = self.sql(expression, "version") 2423 version = f" {version}" if version else "" 2424 alias = self.sql(expression, "alias") 2425 alias = f"{sep}{alias}" if alias else "" 2426 2427 sample = self.sql(expression, "sample") 2428 post_alias = "" 2429 pre_alias = "" 2430 2431 if self.dialect.ALIAS_POST_TABLESAMPLE: 2432 pre_alias = sample 2433 else: 2434 post_alias = sample 2435 2436 if self.dialect.ALIAS_POST_VERSION: 2437 pre_alias = f"{pre_alias}{version}" 2438 else: 2439 post_alias = f"{post_alias}{version}" 2440 2441 hints = self.expressions(expression, key="hints", sep=" ") 2442 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2443 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2444 joins = self.indent( 2445 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2446 ) 2447 laterals = self.expressions(expression, key="laterals", sep="") 2448 2449 file_format = self.sql(expression, "format") 2450 pattern = self.sql(expression, "pattern") 2451 if file_format: 2452 pattern = f", PATTERN => {pattern}" if pattern else "" 2453 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2454 elif pattern: 2455 file_format = f" (PATTERN => {pattern})" 2456 2457 ordinality = expression.args.get("ordinality") or "" 2458 if ordinality: 2459 ordinality = f" WITH ORDINALITY{alias}" 2460 alias = "" 2461 2462 when = self.sql(expression, "when") 2463 if when: 2464 if self.HISTORICAL_DATA_POST_ALIAS: 2465 alias = f"{alias} {when}" 2466 else: 2467 table = f"{table} {when}" 2468 2469 changes = self.sql(expression, "changes") 2470 changes = f" {changes}" if changes else "" 2471 2472 rows_from = self.expressions(expression, key="rows_from") 2473 if rows_from: 2474 table = f"ROWS FROM {self.wrap(rows_from)}" 2475 2476 indexed = expression.args.get("indexed") 2477 if indexed is not None: 2478 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2479 else: 2480 indexed = "" 2481 2482 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2483 2484 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2485 table = self.func("TABLE", expression.this) 2486 alias = self.sql(expression, "alias") 2487 alias = f" AS {alias}" if alias else "" 2488 sample = self.sql(expression, "sample") 2489 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2490 joins = self.indent( 2491 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2492 ) 2493 return f"{table}{alias}{pivots}{sample}{joins}" 2494 2495 def tablesample_sql( 2496 self, 2497 expression: exp.TableSample, 2498 tablesample_keyword: str | None = None, 2499 ) -> str: 2500 method = self.sql(expression, "method") 2501 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2502 numerator = self.sql(expression, "bucket_numerator") 2503 denominator = self.sql(expression, "bucket_denominator") 2504 field = self.sql(expression, "bucket_field") 2505 field = f" ON {field}" if field else "" 2506 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2507 seed = self.sql(expression, "seed") 2508 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2509 2510 size = self.sql(expression, "size") 2511 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2512 size = f"{size} ROWS" 2513 2514 percent = self.sql(expression, "percent") 2515 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2516 percent = f"{percent} PERCENT" 2517 2518 expr = f"{bucket}{percent}{size}" 2519 if self.TABLESAMPLE_REQUIRES_PARENS: 2520 expr = f"({expr})" 2521 2522 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2523 2524 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2525 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2526 # the stored column name differs from the target dialect's natural output. 2527 columns = expression.args.get("columns") 2528 if not columns or len(expression.fields) != 1: 2529 return None 2530 2531 args = expression.args 2532 parser_cls = self.dialect.parser_class 2533 2534 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2535 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2536 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2537 2538 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2539 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2540 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2541 2542 if ( 2543 src_identify_pivot_strings == tgt_identify_pivot_strings 2544 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2545 and src_pivot_column_naming == tgt_pivot_column_naming 2546 ): 2547 return None 2548 2549 in_exprs = expression.fields[0].expressions 2550 step = len(columns) // len(in_exprs) 2551 2552 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2553 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2554 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2555 first_stored = columns[0].name 2556 2557 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2558 if not first_stored.startswith(first_base): 2559 return None 2560 2561 suffix = first_stored[len(first_base) :] 2562 2563 # Whether the target dialect would append an agg-name suffix for this pivot. 2564 # Spark single-agg uniquely drops the agg alias entirely. 2565 target_has_suffix = ( 2566 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2567 ) and any(a.alias for a in expression.expressions) 2568 source_has_suffix = suffix != "" 2569 2570 new_exprs: list[exp.Expression] = [] 2571 modified = False 2572 for val_idx, e in enumerate(in_exprs): 2573 if isinstance(e, exp.PivotAlias): 2574 new_exprs.append(e) 2575 continue 2576 2577 i = val_idx * step 2578 stored_full = columns[i].name 2579 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2580 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2581 2582 # Source had a suffix, but target won't apply one 2583 if source_has_suffix and not target_has_suffix: 2584 new_exprs.append( 2585 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2586 ) 2587 modified = True 2588 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2589 elif stored_value != target_value: 2590 new_exprs.append( 2591 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2592 ) 2593 modified = True 2594 else: 2595 new_exprs.append(e) 2596 2597 return new_exprs if modified else None 2598 2599 def pivot_sql(self, expression: exp.Pivot) -> str: 2600 expressions = self.expressions(expression, flat=True) 2601 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2602 2603 group = self.sql(expression, "group") 2604 2605 if expression.this: 2606 this = self.sql(expression, "this") 2607 if not expressions: 2608 sql = f"UNPIVOT {this}" 2609 else: 2610 on = f"{self.seg('ON')} {expressions}" 2611 into = self.sql(expression, "into") 2612 into = f"{self.seg('INTO')} {into}" if into else "" 2613 using = self.expressions(expression, key="using", flat=True) 2614 using = f"{self.seg('USING')} {using}" if using else "" 2615 sql = f"{direction} {this}{on}{into}{using}{group}" 2616 return self.prepend_ctes(expression, sql) 2617 2618 if not expression.unpivot: 2619 # Wrap IN-list values with explicit aliases where the target dialect would differ 2620 new_field_exprs = self._pivot_in_value_aliases(expression) 2621 if new_field_exprs is not None: 2622 expression.fields[0].set("expressions", new_field_exprs) 2623 2624 alias = self.sql(expression, "alias") 2625 if alias: 2626 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2627 2628 fields = self.expressions( 2629 expression, 2630 "fields", 2631 sep=" ", 2632 dynamic=True, 2633 new_line=True, 2634 skip_first=True, 2635 skip_last=True, 2636 ) 2637 2638 include_nulls = expression.args.get("include_nulls") 2639 if include_nulls is not None: 2640 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2641 else: 2642 nulls = "" 2643 2644 default_on_null = self.sql(expression, "default_on_null") 2645 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2646 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2647 return self.prepend_ctes(expression, sql) 2648 2649 def version_sql(self, expression: exp.Version) -> str: 2650 this = f"FOR {expression.name}" 2651 kind = expression.text("kind") 2652 expr = self.sql(expression, "expression") 2653 return f"{this} {kind} {expr}" 2654 2655 def tuple_sql(self, expression: exp.Tuple) -> str: 2656 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2657 2658 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2659 """ 2660 Returns (join_sql, from_sql) for UPDATE statements. 2661 - join_sql: placed after UPDATE table, before SET 2662 - from_sql: placed after SET clause (standard position) 2663 Dialects like MySQL need to convert FROM to JOIN syntax. 2664 """ 2665 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2666 return ("", self.sql(expression, "from_")) 2667 2668 # Qualify unqualified columns in SET clause with the target table 2669 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2670 target_table = expression.this 2671 if isinstance(target_table, exp.Table): 2672 target_name = exp.to_identifier(target_table.alias_or_name) 2673 for eq in expression.expressions: 2674 col = eq.this 2675 if isinstance(col, exp.Column) and not col.table: 2676 col.set("table", target_name) 2677 2678 table = from_expr.this 2679 if nested_joins := table.args.get("joins", []): 2680 table.set("joins", None) 2681 2682 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2683 for nested in nested_joins: 2684 if not nested.args.get("on") and not nested.args.get("using"): 2685 nested.set("on", exp.true()) 2686 join_sql += self.sql(nested) 2687 2688 return (join_sql, "") 2689 2690 def update_sql(self, expression: exp.Update) -> str: 2691 hint = self.sql(expression, "hint") 2692 this = self.sql(expression, "this") 2693 join_sql, from_sql = self._update_from_joins_sql(expression) 2694 set_sql = self.expressions(expression, flat=True) 2695 where_sql = self.sql(expression, "where") 2696 returning = self.sql(expression, "returning") 2697 order = self.sql(expression, "order") 2698 limit = self.sql(expression, "limit") 2699 if self.RETURNING_END: 2700 expression_sql = f"{from_sql}{where_sql}{returning}" 2701 else: 2702 expression_sql = f"{returning}{from_sql}{where_sql}" 2703 options = self.expressions(expression, key="options") 2704 options = f" OPTION({options})" if options else "" 2705 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2706 return self.prepend_ctes(expression, sql) 2707 2708 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2709 values_as_table = values_as_table and self.VALUES_AS_TABLE 2710 2711 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2712 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2713 args = self.expressions(expression) 2714 alias = self.sql(expression, "alias") 2715 values = f"VALUES{self.seg('')}{args}" 2716 values = ( 2717 f"({values})" 2718 if self.WRAP_DERIVED_VALUES 2719 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2720 else values 2721 ) 2722 values = self.query_modifiers(expression, values) 2723 return f"{values} AS {alias}" if alias else values 2724 2725 # Converts `VALUES...` expression into a series of select unions. 2726 alias_node = expression.args.get("alias") 2727 column_names = alias_node and alias_node.columns 2728 2729 selects: list[exp.Query] = [] 2730 2731 for i, tup in enumerate(expression.expressions): 2732 row = tup.expressions 2733 2734 if i == 0 and column_names: 2735 row = [ 2736 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2737 ] 2738 2739 selects.append(exp.Select(expressions=row)) 2740 2741 if self.pretty: 2742 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2743 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2744 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2745 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2746 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2747 2748 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2749 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2750 return f"({unions}){alias}" 2751 2752 def var_sql(self, expression: exp.Var) -> str: 2753 return self.sql(expression, "this") 2754 2755 @unsupported_args("expressions") 2756 def into_sql(self, expression: exp.Into) -> str: 2757 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2758 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2759 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2760 2761 def from_sql(self, expression: exp.From) -> str: 2762 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2763 2764 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2765 grouping_sets = self.expressions(expression, indent=False) 2766 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2767 2768 def rollup_sql(self, expression: exp.Rollup) -> str: 2769 expressions = self.expressions(expression, indent=False) 2770 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2771 2772 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2773 this = self.sql(expression, "this") 2774 2775 columns = self.expressions(expression, flat=True) 2776 2777 from_sql = self.sql(expression, "from_index") 2778 from_sql = f" FROM {from_sql}" if from_sql else "" 2779 2780 properties = expression.args.get("properties") 2781 properties_sql = ( 2782 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2783 ) 2784 2785 return f"{this}({columns}){from_sql}{properties_sql}" 2786 2787 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2788 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2789 2790 def cube_sql(self, expression: exp.Cube) -> str: 2791 expressions = self.expressions(expression, indent=False) 2792 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2793 2794 def group_sql(self, expression: exp.Group) -> str: 2795 group_by_all = expression.args.get("all") 2796 if group_by_all is True: 2797 modifier = " ALL" 2798 elif group_by_all is False: 2799 modifier = " DISTINCT" 2800 else: 2801 modifier = "" 2802 2803 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2804 2805 grouping_sets = self.expressions(expression, key="grouping_sets") 2806 cube = self.expressions(expression, key="cube") 2807 rollup = self.expressions(expression, key="rollup") 2808 2809 groupings = csv( 2810 self.seg(grouping_sets) if grouping_sets else "", 2811 self.seg(cube) if cube else "", 2812 self.seg(rollup) if rollup else "", 2813 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2814 sep=self.GROUPINGS_SEP, 2815 ) 2816 2817 if ( 2818 expression.expressions 2819 and groupings 2820 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2821 ): 2822 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2823 2824 return f"{group_by}{groupings}" 2825 2826 def having_sql(self, expression: exp.Having) -> str: 2827 this = self.indent(self.sql(expression, "this")) 2828 return f"{self.seg('HAVING')}{self.sep()}{this}" 2829 2830 def connect_sql(self, expression: exp.Connect) -> str: 2831 start = self.sql(expression, "start") 2832 start = self.seg(f"START WITH {start}") if start else "" 2833 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2834 connect = self.sql(expression, "connect") 2835 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2836 return start + connect 2837 2838 def prior_sql(self, expression: exp.Prior) -> str: 2839 return f"PRIOR {self.sql(expression, 'this')}" 2840 2841 def join_sql(self, expression: exp.Join) -> str: 2842 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2843 side = None 2844 else: 2845 side = expression.side 2846 2847 op_sql = " ".join( 2848 op 2849 for op in ( 2850 expression.method, 2851 "GLOBAL" if expression.args.get("global_") else None, 2852 side, 2853 expression.kind, 2854 expression.hint if self.JOIN_HINTS else None, 2855 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2856 ) 2857 if op 2858 ) 2859 match_cond = self.sql(expression, "match_condition") 2860 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2861 on_sql = self.sql(expression, "on") 2862 using = expression.args.get("using") 2863 2864 if not on_sql and using: 2865 on_sql = csv(*(self.sql(column) for column in using)) 2866 2867 this = expression.this 2868 this_sql = self.sql(this) 2869 2870 exprs = self.expressions(expression) 2871 if exprs: 2872 this_sql = f"{this_sql},{self.seg(exprs)}" 2873 2874 if on_sql: 2875 on_sql = self.indent(on_sql, skip_first=True) 2876 space = self.seg(" " * self.pad) if self.pretty else " " 2877 if using: 2878 on_sql = f"{space}USING ({on_sql})" 2879 else: 2880 on_sql = f"{space}ON {on_sql}" 2881 elif not op_sql: 2882 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2883 return f" {this_sql}" 2884 2885 return f", {this_sql}" 2886 2887 if op_sql != "STRAIGHT_JOIN": 2888 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2889 2890 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2891 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2892 2893 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2894 args = self.expressions(expression, flat=True) 2895 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2896 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2897 2898 def lateral_op(self, expression: exp.Lateral) -> str: 2899 cross_apply = expression.args.get("cross_apply") 2900 2901 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2902 if cross_apply is True: 2903 op = "INNER JOIN " 2904 elif cross_apply is False: 2905 op = "LEFT JOIN " 2906 else: 2907 op = "" 2908 2909 return f"{op}LATERAL" 2910 2911 def lateral_sql(self, expression: exp.Lateral) -> str: 2912 this = self.sql(expression, "this") 2913 2914 if expression.args.get("view"): 2915 alias = expression.args["alias"] 2916 columns = self.expressions(alias, key="columns", flat=True) 2917 table = f" {alias.name}" if alias.name else "" 2918 columns = f" AS {columns}" if columns else "" 2919 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2920 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2921 2922 alias = self.sql(expression, "alias") 2923 alias = f" AS {alias}" if alias else "" 2924 2925 ordinality = expression.args.get("ordinality") or "" 2926 if ordinality: 2927 ordinality = f" WITH ORDINALITY{alias}" 2928 alias = "" 2929 2930 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2931 2932 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2933 this = self.sql(expression, "this") 2934 2935 args = [ 2936 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2937 for e in (expression.args.get(k) for k in ("offset", "expression")) 2938 if e 2939 ] 2940 2941 args_sql = ", ".join(self.sql(e) for e in args) 2942 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2943 expressions = self.expressions(expression, flat=True) 2944 limit_options = self.sql(expression, "limit_options") 2945 expressions = f" BY {expressions}" if expressions else "" 2946 2947 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2948 2949 def offset_sql(self, expression: exp.Offset) -> str: 2950 this = self.sql(expression, "this") 2951 value = expression.expression 2952 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2953 expressions = self.expressions(expression, flat=True) 2954 expressions = f" BY {expressions}" if expressions else "" 2955 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2956 2957 def setitem_sql(self, expression: exp.SetItem) -> str: 2958 kind = self.sql(expression, "kind") 2959 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2960 kind = "" 2961 else: 2962 kind = f"{kind} " if kind else "" 2963 this = self.sql(expression, "this") 2964 expressions = self.expressions(expression) 2965 collate = self.sql(expression, "collate") 2966 collate = f" COLLATE {collate}" if collate else "" 2967 global_ = "GLOBAL " if expression.args.get("global_") else "" 2968 return f"{global_}{kind}{this}{expressions}{collate}" 2969 2970 def set_sql(self, expression: exp.Set) -> str: 2971 expressions = f" {self.expressions(expression, flat=True)}" 2972 tag = " TAG" if expression.args.get("tag") else "" 2973 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2974 2975 def queryband_sql(self, expression: exp.QueryBand) -> str: 2976 this = self.sql(expression, "this") 2977 update = " UPDATE" if expression.args.get("update") else "" 2978 scope = self.sql(expression, "scope") 2979 scope = f" FOR {scope}" if scope else "" 2980 2981 return f"QUERY_BAND = {this}{update}{scope}" 2982 2983 def pragma_sql(self, expression: exp.Pragma) -> str: 2984 return f"PRAGMA {self.sql(expression, 'this')}" 2985 2986 def lock_sql(self, expression: exp.Lock) -> str: 2987 if not self.LOCKING_READS_SUPPORTED: 2988 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2989 return "" 2990 2991 update = expression.args["update"] 2992 key = expression.args.get("key") 2993 if update: 2994 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2995 else: 2996 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2997 expressions = self.expressions(expression, flat=True) 2998 expressions = f" OF {expressions}" if expressions else "" 2999 wait = expression.args.get("wait") 3000 3001 if wait is not None: 3002 if isinstance(wait, exp.Literal): 3003 wait = f" WAIT {self.sql(wait)}" 3004 else: 3005 wait = " NOWAIT" if wait else " SKIP LOCKED" 3006 3007 return f"{lock_type}{expressions}{wait or ''}" 3008 3009 def literal_sql(self, expression: exp.Literal) -> str: 3010 text = expression.this or "" 3011 if expression.is_string: 3012 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3013 return text 3014 3015 def escape_str( 3016 self, 3017 text: str, 3018 escape_backslash: bool = True, 3019 delimiter: str | None = None, 3020 escaped_delimiter: str | None = None, 3021 is_byte_string: bool = False, 3022 ) -> str: 3023 if is_byte_string: 3024 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3025 else: 3026 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3027 3028 if supports_escape_sequences: 3029 text = "".join( 3030 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3031 for ch in text 3032 ) 3033 3034 delimiter = delimiter or self.dialect.QUOTE_END 3035 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3036 3037 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3038 3039 def loaddata_sql(self, expression: exp.LoadData) -> str: 3040 is_overwrite = expression.args.get("overwrite") 3041 overwrite = " OVERWRITE" if is_overwrite else "" 3042 this = self.sql(expression, "this") 3043 3044 files = expression.args.get("files") 3045 if files: 3046 files_sql = self.expressions(files, flat=True) 3047 files_sql = f"FILES{self.wrap(files_sql)}" 3048 if is_overwrite: 3049 this = f" {this}" 3050 elif expression.args.get("temp"): 3051 this = f" INTO TEMP TABLE {this}" 3052 else: 3053 this = f" INTO TABLE {this}" 3054 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3055 3056 local = " LOCAL" if expression.args.get("local") else "" 3057 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3058 this = f" INTO TABLE {this}" 3059 partition = self.sql(expression, "partition") 3060 partition = f" {partition}" if partition else "" 3061 input_format = self.sql(expression, "input_format") 3062 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3063 serde = self.sql(expression, "serde") 3064 serde = f" SERDE {serde}" if serde else "" 3065 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3066 3067 def null_sql(self, *_) -> str: 3068 return "NULL" 3069 3070 def boolean_sql(self, expression: exp.Boolean) -> str: 3071 return "TRUE" if expression.this else "FALSE" 3072 3073 def booland_sql(self, expression: exp.Booland) -> str: 3074 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3075 3076 def boolor_sql(self, expression: exp.Boolor) -> str: 3077 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3078 3079 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3080 this = self.sql(expression, "this") 3081 this = f"{this} " if this else this 3082 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3083 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3084 3085 def withfill_sql(self, expression: exp.WithFill) -> str: 3086 from_sql = self.sql(expression, "from_") 3087 from_sql = f" FROM {from_sql}" if from_sql else "" 3088 to_sql = self.sql(expression, "to") 3089 to_sql = f" TO {to_sql}" if to_sql else "" 3090 step_sql = self.sql(expression, "step") 3091 step_sql = f" STEP {step_sql}" if step_sql else "" 3092 interpolated_values = [ 3093 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3094 if isinstance(e, exp.Alias) 3095 else self.sql(e, "this") 3096 for e in expression.args.get("interpolate") or [] 3097 ] 3098 interpolate = ( 3099 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3100 ) 3101 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3102 3103 def cluster_sql(self, expression: exp.Cluster) -> str: 3104 return self.op_expressions("CLUSTER BY", expression) 3105 3106 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3107 if expression.this: 3108 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3109 return "" 3110 expressions = self.expressions(expression, flat=True) 3111 return f"CLUSTER BY ({expressions})" 3112 3113 def distribute_sql(self, expression: exp.Distribute) -> str: 3114 return self.op_expressions("DISTRIBUTE BY", expression) 3115 3116 def sort_sql(self, expression: exp.Sort) -> str: 3117 return self.op_expressions("SORT BY", expression) 3118 3119 def _resolve_ordered_for_null_ordering_simulation( 3120 self, expression: exp.Ordered 3121 ) -> exp.Expr | None: 3122 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3123 3124 Returns the underlying expression of the uniquely-matching projection 3125 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3126 simulation, since the CASE is evaluated in FROM-clause scope rather 3127 than alias scope (MySQL error 1052). Returns None if no safe 3128 substitution applies, leaving the original behaviour unchanged. 3129 """ 3130 this = expression.this 3131 if not (isinstance(this, exp.Column) and not this.table): 3132 return None 3133 3134 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3135 if not isinstance(ancestor, exp.Select): 3136 return None 3137 3138 column_name = this.name 3139 matched: list[exp.Expr] = [ 3140 p.this if isinstance(p, exp.Alias) else p 3141 for p in ancestor.selects 3142 if p.output_name == column_name 3143 ] 3144 match = matched[0] if len(matched) == 1 else None 3145 3146 # Skip the substitution when it would be identical to the existing 3147 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3148 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3149 return None 3150 3151 return match 3152 3153 def ordered_sql(self, expression: exp.Ordered) -> str: 3154 desc = expression.args.get("desc") 3155 asc = not desc 3156 3157 nulls_first = expression.args.get("nulls_first") 3158 nulls_last = not nulls_first 3159 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3160 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3161 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3162 3163 this = self.sql(expression, "this") 3164 3165 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3166 nulls_sort_change = "" 3167 if nulls_first and ( 3168 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3169 ): 3170 nulls_sort_change = " NULLS FIRST" 3171 elif ( 3172 nulls_last 3173 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3174 and not nulls_are_last 3175 ): 3176 nulls_sort_change = " NULLS LAST" 3177 3178 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3179 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3180 window = expression.find_ancestor(exp.Window, exp.Select) 3181 3182 if isinstance(window, exp.Window): 3183 window_this = window.this 3184 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3185 window_this = window_this.this 3186 spec = window.args.get("spec") 3187 else: 3188 window_this = None 3189 spec = None 3190 3191 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3192 # without a spec or with a ROWS spec, but not with RANGE 3193 if not ( 3194 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3195 and (not spec or spec.text("kind").upper() == "ROWS") 3196 ): 3197 if window_this and spec: 3198 self.unsupported( 3199 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3200 ) 3201 nulls_sort_change = "" 3202 elif self.NULL_ORDERING_SUPPORTED is False and ( 3203 (asc and nulls_sort_change == " NULLS LAST") 3204 or (desc and nulls_sort_change == " NULLS FIRST") 3205 ): 3206 # BigQuery does not allow these ordering/nulls combinations when used under 3207 # an aggregation func or under a window containing one 3208 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3209 3210 if isinstance(ancestor, exp.Window): 3211 ancestor = ancestor.this 3212 if isinstance(ancestor, exp.AggFunc): 3213 self.unsupported( 3214 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3215 ) 3216 nulls_sort_change = "" 3217 elif self.NULL_ORDERING_SUPPORTED is None: 3218 if expression.this.is_int: 3219 self.unsupported( 3220 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3221 ) 3222 elif not isinstance(expression.this, exp.Rand): 3223 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3224 target = self.sql(resolved) if resolved is not None else this 3225 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3226 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3227 nulls_sort_change = "" 3228 3229 with_fill = self.sql(expression, "with_fill") 3230 with_fill = f" {with_fill}" if with_fill else "" 3231 3232 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3233 3234 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3235 window_frame = self.sql(expression, "window_frame") 3236 window_frame = f"{window_frame} " if window_frame else "" 3237 3238 this = self.sql(expression, "this") 3239 3240 return f"{window_frame}{this}" 3241 3242 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3243 partition = self.partition_by_sql(expression) 3244 order = self.sql(expression, "order") 3245 measures = self.expressions(expression, key="measures") 3246 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3247 rows = self.sql(expression, "rows") 3248 rows = self.seg(rows) if rows else "" 3249 after = self.sql(expression, "after") 3250 after = self.seg(after) if after else "" 3251 pattern = self.sql(expression, "pattern") 3252 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3253 definition_sqls = [ 3254 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3255 for definition in expression.args.get("define", []) 3256 ] 3257 definitions = self.expressions(sqls=definition_sqls) 3258 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3259 body = "".join( 3260 ( 3261 partition, 3262 order, 3263 measures, 3264 rows, 3265 after, 3266 pattern, 3267 define, 3268 ) 3269 ) 3270 alias = self.sql(expression, "alias") 3271 alias = f" {alias}" if alias else "" 3272 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3273 3274 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3275 limit = expression.args.get("limit") 3276 3277 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3278 count = limit.args.get("count") 3279 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3280 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3281 limit = exp.Limit( 3282 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3283 ) 3284 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3285 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3286 3287 return csv( 3288 *sqls, 3289 *[self.sql(join) for join in expression.args.get("joins") or []], 3290 self.sql(expression, "match"), 3291 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3292 self.sql(expression, "prewhere"), 3293 self.sql(expression, "where"), 3294 self.sql(expression, "connect"), 3295 self.sql(expression, "group"), 3296 self.sql(expression, "having"), 3297 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3298 self.sql(expression, "order"), 3299 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3300 *self.after_limit_modifiers(expression), 3301 self.options_modifier(expression), 3302 self.sql(expression, "for_"), 3303 sep="", 3304 ) 3305 3306 def options_modifier(self, expression: exp.Expr) -> str: 3307 options = self.expressions(expression, key="options") 3308 return f" {options}" if options else "" 3309 3310 def forclause_sql(self, expression: exp.ForClause) -> str: 3311 kind = expression.args["kind"] 3312 if kind == "BROWSE": 3313 return f"{self.sep()}FOR BROWSE" 3314 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3315 # the target dialect doesn't support QueryOption, so we drop the clause. 3316 options = self.expressions(expression, key="expressions") 3317 if not options: 3318 return "" 3319 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3320 3321 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3322 self.unsupported("Unsupported query option.") 3323 return "" 3324 3325 def offset_limit_modifiers( 3326 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3327 ) -> list[str]: 3328 return [ 3329 self.sql(expression, "offset") if fetch else self.sql(limit), 3330 self.sql(limit) if fetch else self.sql(expression, "offset"), 3331 ] 3332 3333 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3334 locks = self.expressions(expression, key="locks", sep=" ") 3335 locks = f" {locks}" if locks else "" 3336 return [locks, self.sql(expression, "sample")] 3337 3338 def select_sql(self, expression: exp.Select) -> str: 3339 into = expression.args.get("into") 3340 if not self.SUPPORTS_SELECT_INTO and into: 3341 into.pop() 3342 3343 hint = self.sql(expression, "hint") 3344 distinct = self.sql(expression, "distinct") 3345 distinct = f" {distinct}" if distinct else "" 3346 kind = self.sql(expression, "kind") 3347 3348 limit = expression.args.get("limit") 3349 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3350 top = self.limit_sql(limit, top=True) 3351 limit.pop() 3352 else: 3353 top = "" 3354 3355 expressions = self.expressions(expression) 3356 3357 if kind: 3358 if kind in self.SELECT_KINDS: 3359 kind = f" AS {kind}" 3360 else: 3361 if kind == "STRUCT": 3362 expressions = self.expressions( 3363 sqls=[ 3364 self.sql( 3365 exp.Struct( 3366 expressions=[ 3367 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3368 if isinstance(e, exp.Alias) 3369 else e 3370 for e in expression.expressions 3371 ] 3372 ) 3373 ) 3374 ] 3375 ) 3376 kind = "" 3377 3378 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3379 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3380 3381 exclude = expression.args.get("exclude") 3382 3383 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3384 exclude_sql = self.expressions(sqls=exclude, flat=True) 3385 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3386 3387 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3388 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3389 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3390 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3391 sql = self.query_modifiers( 3392 expression, 3393 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3394 self.sql(expression, "into", comment=False), 3395 self.sql(expression, "from_", comment=False), 3396 ) 3397 3398 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3399 if expression.args.get("with_"): 3400 sql = self.maybe_comment(sql, expression) 3401 expression.pop_comments() 3402 3403 sql = self.prepend_ctes(expression, sql) 3404 3405 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3406 expression.set("exclude", None) 3407 subquery = expression.subquery(copy=False) 3408 star = exp.Star(except_=exclude) 3409 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3410 3411 if not self.SUPPORTS_SELECT_INTO and into: 3412 if into.args.get("temporary"): 3413 table_kind = " TEMPORARY" 3414 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3415 table_kind = " UNLOGGED" 3416 else: 3417 table_kind = "" 3418 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3419 3420 return sql 3421 3422 def schema_sql(self, expression: exp.Schema) -> str: 3423 this = self.sql(expression, "this") 3424 sql = self.schema_columns_sql(expression) 3425 return f"{this} {sql}" if this and sql else this or sql 3426 3427 def schema_columns_sql(self, expression: exp.Expr) -> str: 3428 if expression.expressions: 3429 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3430 return "" 3431 3432 def star_sql(self, expression: exp.Star) -> str: 3433 except_ = self.expressions(expression, key="except_", flat=True) 3434 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3435 replace = self.expressions(expression, key="replace", flat=True) 3436 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3437 rename = self.expressions(expression, key="rename", flat=True) 3438 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3439 ilike = self.sql(expression, "ilike") 3440 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3441 return f"*{ilike}{except_}{replace}{rename}" 3442 3443 def parameter_sql(self, expression: exp.Parameter) -> str: 3444 this = self.sql(expression, "this") 3445 return f"{self.PARAMETER_TOKEN}{this}" 3446 3447 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3448 this = self.sql(expression, "this") 3449 kind = expression.text("kind") 3450 if kind: 3451 kind = f"{kind}." 3452 return f"@@{kind}{this}" 3453 3454 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3455 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3456 3457 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3458 alias = self.sql(expression, "alias") 3459 alias = f"{sep}{alias}" if alias else "" 3460 sample = self.sql(expression, "sample") 3461 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3462 alias = f"{sample}{alias}" 3463 3464 # Set to None so it's not generated again by self.query_modifiers() 3465 expression.set("sample", None) 3466 3467 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3468 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3469 return self.prepend_ctes(expression, sql) 3470 3471 def qualify_sql(self, expression: exp.Qualify) -> str: 3472 this = self.indent(self.sql(expression, "this")) 3473 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3474 3475 def unnest_sql(self, expression: exp.Unnest) -> str: 3476 args = self.expressions(expression, flat=True) 3477 3478 alias = expression.args.get("alias") 3479 offset = expression.args.get("offset") 3480 3481 if self.UNNEST_WITH_ORDINALITY: 3482 if alias and isinstance(offset, exp.Expr): 3483 alias.append("columns", offset) 3484 expression.set("offset", None) 3485 3486 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3487 columns = alias.columns 3488 alias = self.sql(columns[0]) if columns else "" 3489 else: 3490 alias = self.sql(alias) 3491 3492 alias = f" AS {alias}" if alias else alias 3493 if self.UNNEST_WITH_ORDINALITY: 3494 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3495 else: 3496 if isinstance(offset, exp.Expr): 3497 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3498 elif offset: 3499 suffix = f"{alias} WITH OFFSET" 3500 else: 3501 suffix = alias 3502 3503 return f"UNNEST({args}){suffix}" 3504 3505 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3506 return "" 3507 3508 def where_sql(self, expression: exp.Where) -> str: 3509 this = self.indent(self.sql(expression, "this")) 3510 return f"{self.seg('WHERE')}{self.sep()}{this}" 3511 3512 def window_sql(self, expression: exp.Window) -> str: 3513 this = self.sql(expression, "this") 3514 partition = self.partition_by_sql(expression) 3515 order = expression.args.get("order") 3516 order = self.order_sql(order, flat=True) if order else "" 3517 spec = self.sql(expression, "spec") 3518 alias = self.sql(expression, "alias") 3519 over = self.sql(expression, "over") or "OVER" 3520 3521 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3522 3523 first = expression.args.get("first") 3524 if first is None: 3525 first = "" 3526 else: 3527 first = "FIRST" if first else "LAST" 3528 3529 if not partition and not order and not spec and alias: 3530 return f"{this} {alias}" 3531 3532 args = self.format_args( 3533 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3534 ) 3535 return f"{this} ({args})" 3536 3537 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3538 partition = self.expressions(expression, key="partition_by", flat=True) 3539 return f"PARTITION BY {partition}" if partition else "" 3540 3541 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3542 kind = self.sql(expression, "kind") 3543 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3544 end = ( 3545 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3546 or "CURRENT ROW" 3547 ) 3548 3549 window_spec = f"{kind} BETWEEN {start} AND {end}" 3550 3551 exclude = self.sql(expression, "exclude") 3552 if exclude: 3553 if self.SUPPORTS_WINDOW_EXCLUDE: 3554 window_spec += f" EXCLUDE {exclude}" 3555 else: 3556 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3557 3558 return window_spec 3559 3560 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3561 this = self.sql(expression, "this") 3562 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3563 return f"{this} WITHIN GROUP ({expression_sql})" 3564 3565 def between_sql(self, expression: exp.Between) -> str: 3566 this = self.sql(expression, "this") 3567 low = self.sql(expression, "low") 3568 high = self.sql(expression, "high") 3569 symmetric = expression.args.get("symmetric") 3570 3571 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3572 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3573 3574 flag = ( 3575 " SYMMETRIC" 3576 if symmetric 3577 else " ASYMMETRIC" 3578 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3579 else "" # silently drop ASYMMETRIC – semantics identical 3580 ) 3581 return f"{this} BETWEEN{flag} {low} AND {high}" 3582 3583 def bracket_offset_expressions( 3584 self, expression: exp.Bracket, index_offset: int | None = None 3585 ) -> list[exp.Expr]: 3586 if expression.args.get("json_access"): 3587 return expression.expressions 3588 3589 return apply_index_offset( 3590 expression.this, 3591 expression.expressions, 3592 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3593 dialect=self.dialect, 3594 ) 3595 3596 def bracket_sql(self, expression: exp.Bracket) -> str: 3597 expressions = self.bracket_offset_expressions(expression) 3598 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3599 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3600 3601 def all_sql(self, expression: exp.All) -> str: 3602 this = self.sql(expression, "this") 3603 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3604 this = self.wrap(this) 3605 return f"ALL {this}" 3606 3607 def any_sql(self, expression: exp.Any) -> str: 3608 this = self.sql(expression, "this") 3609 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3610 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3611 this = self.wrap(this) 3612 return f"ANY{this}" 3613 return f"ANY {this}" 3614 3615 def exists_sql(self, expression: exp.Exists) -> str: 3616 return f"EXISTS{self.wrap(expression)}" 3617 3618 def case_sql(self, expression: exp.Case) -> str: 3619 this = self.sql(expression, "this") 3620 statements = [f"CASE {this}" if this else "CASE"] 3621 3622 for e in expression.args["ifs"]: 3623 statements.append(f"WHEN {self.sql(e, 'this')}") 3624 statements.append(f"THEN {self.sql(e, 'true')}") 3625 3626 default = self.sql(expression, "default") 3627 3628 if default: 3629 statements.append(f"ELSE {default}") 3630 3631 statements.append("END") 3632 3633 if self.pretty and self.too_wide(statements): 3634 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3635 3636 return " ".join(statements) 3637 3638 def constraint_sql(self, expression: exp.Constraint) -> str: 3639 this = self.sql(expression, "this") 3640 expressions = self.expressions(expression, flat=True) 3641 return f"CONSTRAINT {this} {expressions}" 3642 3643 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3644 order = expression.args.get("order") 3645 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3646 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3647 3648 def extract_sql(self, expression: exp.Extract) -> str: 3649 import sqlglot.dialects.dialect 3650 3651 this = ( 3652 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3653 if self.NORMALIZE_EXTRACT_DATE_PARTS 3654 else expression.this 3655 ) 3656 if self.EXTRACT_ALLOWS_QUOTES: 3657 this_sql = self.sql(this) 3658 elif isinstance(this, exp.WeekStart): 3659 this_sql = self.weekstart_name(this) 3660 else: 3661 this_sql = this.name 3662 expression_sql = self.sql(expression, "expression") 3663 3664 return f"EXTRACT({this_sql} FROM {expression_sql})" 3665 3666 def trim_sql(self, expression: exp.Trim) -> str: 3667 trim_type = self.sql(expression, "position") 3668 3669 if trim_type == "LEADING": 3670 func_name = "LTRIM" 3671 elif trim_type == "TRAILING": 3672 func_name = "RTRIM" 3673 else: 3674 func_name = "TRIM" 3675 3676 return self.func(func_name, expression.this, expression.expression) 3677 3678 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3679 args = expression.expressions 3680 if isinstance(expression, exp.ConcatWs): 3681 args = args[1:] # Skip the delimiter 3682 3683 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3684 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3685 3686 concat_coalesce = ( 3687 self.dialect.CONCAT_WS_COALESCE 3688 if isinstance(expression, exp.ConcatWs) 3689 else self.dialect.CONCAT_COALESCE 3690 ) 3691 3692 if not concat_coalesce and expression.args.get("coalesce"): 3693 3694 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3695 if not e.type: 3696 import sqlglot.optimizer.annotate_types 3697 3698 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3699 3700 if e.is_string or e.is_type(exp.DType.ARRAY): 3701 return e 3702 3703 return exp.func("coalesce", e, exp.Literal.string("")) 3704 3705 args = [_wrap_with_coalesce(e) for e in args] 3706 3707 return args 3708 3709 def concat_sql(self, expression: exp.Concat) -> str: 3710 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3711 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3712 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3713 # instead of coalescing them to empty string. 3714 import sqlglot.dialects.dialect 3715 3716 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3717 3718 expressions = self.convert_concat_args(expression) 3719 3720 # Some dialects don't allow a single-argument CONCAT call 3721 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3722 return self.sql(expressions[0]) 3723 3724 return self.func("CONCAT", *expressions) 3725 3726 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3727 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3728 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3729 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3730 all_args = expression.expressions 3731 expression.set("coalesce", True) 3732 return self.sql( 3733 exp.case() 3734 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3735 .else_(expression) 3736 ) 3737 3738 return self.func( 3739 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3740 ) 3741 3742 def check_sql(self, expression: exp.Check) -> str: 3743 this = self.sql(expression, key="this") 3744 return f"CHECK ({this})" 3745 3746 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3747 expressions = self.expressions(expression, flat=True) 3748 expressions = f" ({expressions})" if expressions else "" 3749 reference = self.sql(expression, "reference") 3750 reference = f" {reference}" if reference else "" 3751 delete = self.sql(expression, "delete") 3752 delete = f" ON DELETE {delete}" if delete else "" 3753 update = self.sql(expression, "update") 3754 update = f" ON UPDATE {update}" if update else "" 3755 options = self.expressions(expression, key="options", flat=True, sep=" ") 3756 options = f" {options}" if options else "" 3757 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3758 3759 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3760 this = self.sql(expression, "this") 3761 this = f" {this}" if this else "" 3762 expressions = self.expressions(expression, flat=True) 3763 include = self.sql(expression, "include") 3764 options = self.expressions(expression, key="options", flat=True, sep=" ") 3765 options = f" {options}" if options else "" 3766 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3767 3768 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3769 self.unsupported("TIMESERIES primary key columns are not supported") 3770 return self.sql(expression, "this") 3771 3772 def if_sql(self, expression: exp.If) -> str: 3773 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3774 3775 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3776 if self.MATCH_AGAINST_TABLE_PREFIX: 3777 expressions = [] 3778 for expr in expression.expressions: 3779 if isinstance(expr, exp.Table): 3780 expressions.append(f"TABLE {self.sql(expr)}") 3781 else: 3782 expressions.append(expr) 3783 else: 3784 expressions = expression.expressions 3785 3786 modifier = expression.args.get("modifier") 3787 modifier = f" {modifier}" if modifier else "" 3788 return ( 3789 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3790 ) 3791 3792 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3793 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3794 3795 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3796 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3797 3798 if self.QUOTE_JSON_PATH: 3799 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3800 3801 return path 3802 3803 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3804 if isinstance(expression, exp.JSONPathPart): 3805 transform = self.TRANSFORMS.get(expression.__class__) 3806 if not callable(transform): 3807 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3808 return "" 3809 3810 return transform(self, expression) 3811 3812 if isinstance(expression, int): 3813 return str(expression) 3814 3815 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3816 escaped = expression.replace("'", "\\'") 3817 escaped = f"\\'{expression}\\'" 3818 else: 3819 escaped = expression.replace('"', '\\"') 3820 escaped = f'"{escaped}"' 3821 3822 return escaped 3823 3824 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3825 return f"{self.sql(expression, 'this')} FORMAT JSON" 3826 3827 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3828 # Output the Teradata column FORMAT override. 3829 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3830 this = self.sql(expression, "this") 3831 fmt = self.sql(expression, "format") 3832 return f"{this} (FORMAT {fmt})" 3833 3834 def _jsonobject_sql( 3835 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3836 ) -> str: 3837 null_handling = expression.args.get("null_handling") 3838 null_handling = f" {null_handling}" if null_handling else "" 3839 3840 unique_keys = expression.args.get("unique_keys") 3841 if unique_keys is not None: 3842 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3843 else: 3844 unique_keys = "" 3845 3846 return_type = self.sql(expression, "return_type") 3847 return_type = f" RETURNING {return_type}" if return_type else "" 3848 encoding = self.sql(expression, "encoding") 3849 encoding = f" ENCODING {encoding}" if encoding else "" 3850 3851 if not name: 3852 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3853 3854 return self.func( 3855 name, 3856 *expression.expressions, 3857 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3858 ) 3859 3860 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3861 null_handling = expression.args.get("null_handling") 3862 null_handling = f" {null_handling}" if null_handling else "" 3863 return_type = self.sql(expression, "return_type") 3864 return_type = f" RETURNING {return_type}" if return_type else "" 3865 strict = " STRICT" if expression.args.get("strict") else "" 3866 return self.func( 3867 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3868 ) 3869 3870 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3871 this = self.sql(expression, "this") 3872 order = self.sql(expression, "order") 3873 null_handling = expression.args.get("null_handling") 3874 null_handling = f" {null_handling}" if null_handling else "" 3875 return_type = self.sql(expression, "return_type") 3876 return_type = f" RETURNING {return_type}" if return_type else "" 3877 strict = " STRICT" if expression.args.get("strict") else "" 3878 return self.func( 3879 "JSON_ARRAYAGG", 3880 this, 3881 suffix=f"{order}{null_handling}{return_type}{strict})", 3882 ) 3883 3884 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3885 path = self.sql(expression, "path") 3886 path = f" PATH {path}" if path else "" 3887 nested_schema = self.sql(expression, "nested_schema") 3888 3889 if nested_schema: 3890 return f"NESTED{path} {nested_schema}" 3891 3892 this = self.sql(expression, "this") 3893 kind = self.sql(expression, "kind") 3894 kind = f" {kind}" if kind else "" 3895 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3896 3897 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3898 return f"{this}{kind}{format_json}{path}{ordinality}" 3899 3900 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3901 return self.func("COLUMNS", *expression.expressions) 3902 3903 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3904 this = self.sql(expression, "this") 3905 path = self.sql(expression, "path") 3906 path = f", {path}" if path else "" 3907 error_handling = expression.args.get("error_handling") 3908 error_handling = f" {error_handling}" if error_handling else "" 3909 empty_handling = expression.args.get("empty_handling") 3910 empty_handling = f" {empty_handling}" if empty_handling else "" 3911 schema = self.sql(expression, "schema") 3912 return self.func( 3913 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3914 ) 3915 3916 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3917 this = self.sql(expression, "this") 3918 kind = self.sql(expression, "kind") 3919 path = self.sql(expression, "path") 3920 path = f" {path}" if path else "" 3921 as_json = " AS JSON" if expression.args.get("as_json") else "" 3922 return f"{this} {kind}{path}{as_json}" 3923 3924 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3925 this = self.sql(expression, "this") 3926 path = self.sql(expression, "path") 3927 path = f", {path}" if path else "" 3928 expressions = self.expressions(expression) 3929 with_ = ( 3930 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3931 if expressions 3932 else "" 3933 ) 3934 return f"OPENJSON({this}{path}){with_}" 3935 3936 def in_sql(self, expression: exp.In) -> str: 3937 query = expression.args.get("query") 3938 unnest = expression.args.get("unnest") 3939 field = expression.args.get("field") 3940 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3941 3942 if query: 3943 in_sql = self.sql(query) 3944 elif unnest: 3945 in_sql = self.in_unnest_op(unnest) 3946 elif field: 3947 in_sql = self.sql(field) 3948 else: 3949 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3950 3951 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3952 3953 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3954 return f"(SELECT {self.sql(unnest)})" 3955 3956 def interval_sql(self, expression: exp.Interval) -> str: 3957 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3958 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 3959 exp.AutoRefreshProperty, 3960 ) 3961 interval_keyword = "INTERVAL" if include_keyword else "" 3962 unit_expression = expression.args.get("unit") 3963 unit = self.sql(unit_expression) if unit_expression else "" 3964 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3965 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3966 unit = f" {unit}" if unit else "" 3967 3968 if self.SINGLE_STRING_INTERVAL: 3969 this = expression.this.name if expression.this else "" 3970 if this: 3971 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 3972 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3973 return f"{interval_keyword}'{this}'{unit}" 3974 return f"{interval_keyword}'{this}{unit}'" 3975 return f"{interval_keyword}{unit}" 3976 3977 this = self.sql(expression, "this") 3978 if this: 3979 if not include_keyword and expression.this.is_string: 3980 this = expression.this.name 3981 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 3982 this = f"({this})" 3983 if include_keyword: 3984 this = f" {this}" 3985 3986 return f"{interval_keyword}{this}{unit}" 3987 3988 def return_sql(self, expression: exp.Return) -> str: 3989 return f"RETURN {self.sql(expression, 'this')}" 3990 3991 def reference_sql(self, expression: exp.Reference) -> str: 3992 this = self.sql(expression, "this") 3993 expressions = self.expressions(expression, flat=True) 3994 expressions = f"({expressions})" if expressions else "" 3995 options = self.expressions(expression, key="options", flat=True, sep=" ") 3996 options = f" {options}" if options else "" 3997 return f"REFERENCES {this}{expressions}{options}" 3998 3999 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4000 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4001 parent = expression.parent 4002 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4003 4004 return self.func( 4005 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4006 ) 4007 4008 def paren_sql(self, expression: exp.Paren) -> str: 4009 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4010 return f"({sql}{self.seg(')', sep='')}" 4011 4012 def neg_sql(self, expression: exp.Neg) -> str: 4013 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4014 this_sql = self.sql(expression, "this") 4015 sep = " " if this_sql[0] == "-" else "" 4016 return f"-{sep}{this_sql}" 4017 4018 def not_sql(self, expression: exp.Not) -> str: 4019 return f"NOT {self.sql(expression, 'this')}" 4020 4021 def alias_sql(self, expression: exp.Alias) -> str: 4022 alias = self.sql(expression, "alias") 4023 alias = f" AS {alias}" if alias else "" 4024 return f"{self.sql(expression, 'this')}{alias}" 4025 4026 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4027 alias = expression.args["alias"] 4028 4029 parent = expression.parent 4030 pivot = parent and parent.parent 4031 4032 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4033 identifier_alias = isinstance(alias, exp.Identifier) 4034 literal_alias = isinstance(alias, exp.Literal) 4035 4036 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4037 alias.replace(exp.Literal.string(alias.output_name)) 4038 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4039 alias.replace(exp.to_identifier(alias.output_name)) 4040 4041 return self.alias_sql(expression) 4042 4043 def aliases_sql(self, expression: exp.Aliases) -> str: 4044 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4045 4046 def atindex_sql(self, expression: exp.AtIndex) -> str: 4047 this = self.sql(expression, "this") 4048 index = self.sql(expression, "expression") 4049 return f"{this} AT {index}" 4050 4051 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4052 this = self.sql(expression, "this") 4053 zone = self.sql(expression, "zone") 4054 return f"{this} AT TIME ZONE {zone}" 4055 4056 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4057 this = self.sql(expression, "this") 4058 zone = self.sql(expression, "zone") 4059 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4060 4061 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4062 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4063 4064 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4065 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4066 4067 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4068 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4069 4070 def add_sql(self, expression: exp.Add) -> str: 4071 return self.binary(expression, "+") 4072 4073 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4074 return self.connector_sql(expression, "AND", stack) 4075 4076 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4077 return self.connector_sql(expression, "OR", stack) 4078 4079 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4080 return self.connector_sql(expression, "XOR", stack) 4081 4082 def connector_sql( 4083 self, 4084 expression: exp.Connector, 4085 op: str, 4086 stack: list[str | exp.Expr] | None = None, 4087 ) -> str: 4088 if stack is not None: 4089 stack.append(expression.right) 4090 if expression.comments and self.comments: 4091 op = self.maybe_comment(op, comments=expression.comments) 4092 4093 stack.extend((op, expression.left)) 4094 return op 4095 4096 stack = [expression] 4097 sqls: list[str] = [] 4098 ops = set() 4099 4100 while stack: 4101 node = stack.pop() 4102 if isinstance(node, exp.Connector): 4103 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4104 else: 4105 sql = self.sql(node) 4106 if sqls and sqls[-1] in ops: 4107 sqls[-1] += f" {sql}" 4108 else: 4109 sqls.append(sql) 4110 4111 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4112 return sep.join(sqls) 4113 4114 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4115 return self.binary(expression, "&") 4116 4117 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4118 return self.binary(expression, "<<") 4119 4120 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4121 return f"~{self.sql(expression, 'this')}" 4122 4123 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4124 return self.binary(expression, "|") 4125 4126 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4127 return self.binary(expression, ">>") 4128 4129 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4130 return self.binary(expression, "^") 4131 4132 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4133 format_sql = self.sql(expression, "format") 4134 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4135 to_sql = self.sql(expression, "to") 4136 to_sql = f" {to_sql}" if to_sql else "" 4137 action = self.sql(expression, "action") 4138 action = f" {action}" if action else "" 4139 default = self.sql(expression, "default") 4140 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4141 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4142 4143 # Base implementation that excludes safe, zone, and target_type metadata args 4144 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4145 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4146 4147 # Base implementation that excludes the safe and default_year metadata args 4148 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4149 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4150 4151 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4152 return self.func( 4153 "PARSE_DATETIME", 4154 expression.this, 4155 expression.args.get("format"), 4156 expression.args.get("zone"), 4157 ) 4158 4159 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4160 zone = self.sql(expression, "this") 4161 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4162 4163 def collate_sql(self, expression: exp.Collate) -> str: 4164 if self.COLLATE_IS_FUNC: 4165 return self.function_fallback_sql(expression) 4166 return self.binary(expression, "COLLATE") 4167 4168 def command_sql(self, expression: exp.Command) -> str: 4169 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4170 4171 def comment_sql(self, expression: exp.Comment) -> str: 4172 this = self.sql(expression, "this") 4173 kind = expression.args["kind"] 4174 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4175 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4176 expression_sql = self.sql(expression, "expression") 4177 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4178 4179 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4180 this = self.sql(expression, "this") 4181 delete = " DELETE" if expression.args.get("delete") else "" 4182 recompress = self.sql(expression, "recompress") 4183 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4184 to_disk = self.sql(expression, "to_disk") 4185 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4186 to_volume = self.sql(expression, "to_volume") 4187 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4188 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4189 4190 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4191 where = self.sql(expression, "where") 4192 group = self.sql(expression, "group") 4193 aggregates = self.expressions(expression, key="aggregates") 4194 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4195 4196 if not (where or group or aggregates) and len(expression.expressions) == 1: 4197 return f"TTL {self.expressions(expression, flat=True)}" 4198 4199 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4200 4201 def transaction_sql(self, expression: exp.Transaction) -> str: 4202 modes = self.expressions(expression, key="modes") 4203 modes = f" {modes}" if modes else "" 4204 return f"BEGIN{modes}" 4205 4206 def commit_sql(self, expression: exp.Commit) -> str: 4207 chain = expression.args.get("chain") 4208 if chain is not None: 4209 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4210 4211 return f"COMMIT{chain or ''}" 4212 4213 def rollback_sql(self, expression: exp.Rollback) -> str: 4214 savepoint = expression.args.get("savepoint") 4215 savepoint = f" TO {savepoint}" if savepoint else "" 4216 return f"ROLLBACK{savepoint}" 4217 4218 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4219 this = self.sql(expression, "this") 4220 4221 exists = "" 4222 if expression.args.get("exists"): 4223 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4224 exists = " IF EXISTS" 4225 else: 4226 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4227 4228 dtype = self.sql(expression, "dtype") 4229 if dtype: 4230 collate = self.sql(expression, "collate") 4231 collate = f" COLLATE {collate}" if collate else "" 4232 using = self.sql(expression, "using") 4233 using = f" USING {using}" if using else "" 4234 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4235 null_constraint = self._alter_column_null_constraint_sql(expression) 4236 4237 return ( 4238 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4239 f"{collate}{using}{null_constraint}" 4240 ) 4241 4242 default = self.sql(expression, "default") 4243 if default: 4244 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4245 4246 comment = self.sql(expression, "comment") 4247 if comment: 4248 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4249 4250 visible = expression.args.get("visible") 4251 if visible: 4252 return f"ALTER COLUMN{exists} {this} SET {visible}" 4253 4254 allow_null = expression.args.get("allow_null") 4255 drop = expression.args.get("drop") 4256 4257 if not drop and not allow_null: 4258 self.unsupported("Unsupported ALTER COLUMN syntax") 4259 4260 if allow_null is not None: 4261 keyword = "DROP" if drop else "SET" 4262 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4263 4264 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4265 4266 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4267 allow_null = expression.args.get("allow_null") 4268 if allow_null is None: 4269 return "" 4270 4271 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4272 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4273 return "" 4274 4275 return " NULL" if allow_null else " NOT NULL" 4276 4277 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4278 this = self.sql(expression, "this") 4279 rename_from = self.sql(expression, "rename_from") 4280 if rename_from: 4281 if not self.SUPPORTS_CHANGE_COLUMN: 4282 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4283 return f"CHANGE COLUMN {rename_from} {this}" 4284 if not self.SUPPORTS_MODIFY_COLUMN: 4285 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4286 return f"MODIFY COLUMN {this}" 4287 4288 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4289 this = self.sql(expression, "this") 4290 4291 visible = expression.args.get("visible") 4292 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4293 4294 return f"ALTER INDEX {this} {visible_sql}" 4295 4296 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4297 this = self.sql(expression, "this") 4298 if not isinstance(expression.this, exp.Var): 4299 this = f"KEY DISTKEY {this}" 4300 return f"ALTER DISTSTYLE {this}" 4301 4302 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4303 compound = " COMPOUND" if expression.args.get("compound") else "" 4304 this = self.sql(expression, "this") 4305 expressions = self.expressions(expression, flat=True) 4306 expressions = f"({expressions})" if expressions else "" 4307 return f"ALTER{compound} SORTKEY {this or expressions}" 4308 4309 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4310 if not self.RENAME_TABLE_WITH_DB: 4311 # Remove db from tables 4312 expression = expression.transform( 4313 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4314 ).assert_is(exp.AlterRename) 4315 this = self.sql(expression, "this") 4316 to_kw = " TO" if include_to else "" 4317 return f"RENAME{to_kw} {this}" 4318 4319 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4320 exists = " IF EXISTS" if expression.args.get("exists") else "" 4321 old_column = self.sql(expression, "this") 4322 new_column = self.sql(expression, "to") 4323 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4324 4325 def alterset_sql(self, expression: exp.AlterSet) -> str: 4326 exprs = self.expressions(expression, flat=True) 4327 if self.ALTER_SET_WRAPPED: 4328 exprs = f"({exprs})" 4329 4330 return f"SET {exprs}" 4331 4332 def alter_sql(self, expression: exp.Alter) -> str: 4333 actions = expression.args["actions"] 4334 4335 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4336 actions[0], exp.ColumnDef 4337 ): 4338 actions_sql = self.expressions(expression, key="actions", flat=True) 4339 actions_sql = f"ADD {actions_sql}" 4340 else: 4341 actions_list = [] 4342 for action in actions: 4343 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4344 action_sql = self.add_column_sql(action) 4345 else: 4346 action_sql = self.sql(action) 4347 if isinstance(action, exp.Query): 4348 action_sql = f"AS {action_sql}" 4349 4350 actions_list.append(action_sql) 4351 4352 actions_sql = self.format_args(*actions_list).lstrip("\n") 4353 4354 iceberg = ( 4355 "ICEBERG " 4356 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4357 else "" 4358 ) 4359 exists = " IF EXISTS" if expression.args.get("exists") else "" 4360 on_cluster = self.sql(expression, "cluster") 4361 on_cluster = f" {on_cluster}" if on_cluster else "" 4362 only = " ONLY" if expression.args.get("only") else "" 4363 options = self.expressions(expression, key="options") 4364 options = f", {options}" if options else "" 4365 kind = self.sql(expression, "kind") 4366 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4367 check = " WITH CHECK" if expression.args.get("check") else "" 4368 cascade = ( 4369 " CASCADE" 4370 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4371 else "" 4372 ) 4373 this = self.sql(expression, "this") 4374 this = f" {this}" if this else "" 4375 4376 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4377 4378 def altersession_sql(self, expression: exp.AlterSession) -> str: 4379 items_sql = self.expressions(expression, flat=True) 4380 keyword = "UNSET" if expression.args.get("unset") else "SET" 4381 return f"{keyword} {items_sql}" 4382 4383 def add_column_sql(self, expression: exp.Expr) -> str: 4384 sql = self.sql(expression) 4385 if isinstance(expression, exp.Schema): 4386 column_text = " COLUMNS" 4387 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4388 column_text = " COLUMN" 4389 else: 4390 column_text = "" 4391 4392 return f"ADD{column_text} {sql}" 4393 4394 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4395 expressions = self.expressions(expression) 4396 exists = " IF EXISTS " if expression.args.get("exists") else " " 4397 return f"DROP{exists}{expressions}" 4398 4399 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4400 return "DROP PRIMARY KEY" 4401 4402 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4403 return f"ADD {self.expressions(expression, indent=False)}" 4404 4405 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4406 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4407 location = self.sql(expression, "location") 4408 location = f" {location}" if location else "" 4409 return f"ADD {exists}{self.sql(expression.this)}{location}" 4410 4411 def distinct_sql(self, expression: exp.Distinct) -> str: 4412 this = self.expressions(expression, flat=True) 4413 4414 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4415 case = exp.case() 4416 for arg in expression.expressions: 4417 case = case.when(arg.is_(exp.null()), exp.null()) 4418 this = self.sql(case.else_(f"({this})")) 4419 4420 this = f" {this}" if this else "" 4421 4422 on = self.sql(expression, "on") 4423 on = f" ON {on}" if on else "" 4424 return f"DISTINCT{this}{on}" 4425 4426 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4427 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4428 4429 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4430 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4431 4432 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4433 this_sql = self.sql(expression, "this") 4434 expression_sql = self.sql(expression, "expression") 4435 kind = "MAX" if expression.args.get("max") else "MIN" 4436 return f"{this_sql} HAVING {kind} {expression_sql}" 4437 4438 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4439 return self.sql( 4440 exp.Cast( 4441 this=exp.Div(this=expression.this, expression=expression.expression), 4442 to=exp.DataType(this=exp.DType.INT), 4443 ) 4444 ) 4445 4446 def dpipe_sql(self, expression: exp.DPipe) -> str: 4447 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4448 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4449 return self.binary(expression, "||") 4450 4451 def div_sql(self, expression: exp.Div) -> str: 4452 l, r = expression.left, expression.right 4453 4454 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4455 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4456 4457 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4458 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4459 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4460 4461 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4462 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4463 return self.sql( 4464 exp.cast( 4465 l / r, 4466 to=exp.DType.BIGINT, 4467 ) 4468 ) 4469 4470 return self.binary(expression, "/") 4471 4472 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4473 n = exp._wrap(expression.this, exp.Binary) 4474 d = exp._wrap(expression.expression, exp.Binary) 4475 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4476 4477 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4478 return self.binary(expression, "OVERLAPS") 4479 4480 def distance_sql(self, expression: exp.Distance) -> str: 4481 return self.binary(expression, "<->") 4482 4483 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4484 return self.binary(expression, "<<->>") 4485 4486 def dot_sql(self, expression: exp.Dot) -> str: 4487 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4488 4489 def eq_sql(self, expression: exp.EQ) -> str: 4490 return self.binary(expression, "=") 4491 4492 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4493 return self.binary(expression, ":=") 4494 4495 def escape_sql(self, expression: exp.Escape) -> str: 4496 this = expression.this 4497 if ( 4498 isinstance(this, (exp.Like, exp.ILike)) 4499 and isinstance(this.expression, (exp.All, exp.Any)) 4500 and not self.SUPPORTS_LIKE_QUANTIFIERS 4501 ): 4502 return self._like_sql(this, escape=expression) 4503 return self.binary(expression, "ESCAPE") 4504 4505 def glob_sql(self, expression: exp.Glob) -> str: 4506 return self.binary(expression, "GLOB") 4507 4508 def gt_sql(self, expression: exp.GT) -> str: 4509 return self.binary(expression, ">") 4510 4511 def gte_sql(self, expression: exp.GTE) -> str: 4512 return self.binary(expression, ">=") 4513 4514 def is_sql(self, expression: exp.Is) -> str: 4515 negate = expression.args.get("negate") 4516 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4517 positive = bool(expression.expression.this) != bool(negate) 4518 return self.sql(expression.this if positive else exp.not_(expression.this)) 4519 return self.binary(expression, "IS NOT" if negate else "IS") 4520 4521 def _like_sql( 4522 self, 4523 expression: exp.Like | exp.ILike, 4524 escape: exp.Escape | None = None, 4525 ) -> str: 4526 this = expression.this 4527 rhs = expression.expression 4528 4529 if isinstance(expression, exp.Like): 4530 exp_class: type[exp.Like | exp.ILike] = exp.Like 4531 op = "LIKE" 4532 else: 4533 exp_class = exp.ILike 4534 op = "ILIKE" 4535 4536 if expression.args.get("negate"): 4537 op = f"NOT {op}" 4538 4539 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4540 exprs = rhs.this.unnest() 4541 4542 if isinstance(exprs, exp.Tuple): 4543 exprs = exprs.expressions 4544 else: 4545 exprs = [exprs] 4546 4547 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4548 4549 def _make_like(expr: exp.Expression) -> exp.Expression: 4550 like: exp.Expression = exp_class( 4551 this=this, expression=expr, negate=expression.args.get("negate") 4552 ) 4553 if escape: 4554 like = exp.Escape(this=like, expression=escape.expression.copy()) 4555 return like 4556 4557 like_expr: exp.Expr = _make_like(exprs[0]) 4558 for expr in exprs[1:]: 4559 like_expr = connective(like_expr, _make_like(expr), copy=False) 4560 4561 parent = escape.parent if escape else expression.parent 4562 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4563 parent, exp.Condition 4564 ): 4565 like_expr = exp.paren(like_expr, copy=False) 4566 4567 return self.sql(like_expr) 4568 4569 return self.binary(expression, op) 4570 4571 def like_sql(self, expression: exp.Like) -> str: 4572 return self._like_sql(expression) 4573 4574 def ilike_sql(self, expression: exp.ILike) -> str: 4575 return self._like_sql(expression) 4576 4577 def match_sql(self, expression: exp.Match) -> str: 4578 return self.binary(expression, "MATCH") 4579 4580 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4581 return self.binary(expression, "SIMILAR TO") 4582 4583 def lt_sql(self, expression: exp.LT) -> str: 4584 return self.binary(expression, "<") 4585 4586 def lte_sql(self, expression: exp.LTE) -> str: 4587 return self.binary(expression, "<=") 4588 4589 def mod_sql(self, expression: exp.Mod) -> str: 4590 return self.binary(expression, "%") 4591 4592 def mul_sql(self, expression: exp.Mul) -> str: 4593 return self.binary(expression, "*") 4594 4595 def neq_sql(self, expression: exp.NEQ) -> str: 4596 return self.binary(expression, "<>") 4597 4598 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4599 return self.binary(expression, "IS NOT DISTINCT FROM") 4600 4601 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4602 return self.binary(expression, "IS DISTINCT FROM") 4603 4604 def sub_sql(self, expression: exp.Sub) -> str: 4605 return self.binary(expression, "-") 4606 4607 def trycast_sql(self, expression: exp.TryCast) -> str: 4608 return self.cast_sql(expression, safe_prefix="TRY_") 4609 4610 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4611 return self.cast_sql(expression) 4612 4613 def try_sql(self, expression: exp.Try) -> str: 4614 if not self.TRY_SUPPORTED: 4615 self.unsupported("Unsupported TRY function") 4616 return self.sql(expression, "this") 4617 4618 return self.func("TRY", expression.this) 4619 4620 def log_sql(self, expression: exp.Log) -> str: 4621 this = expression.this 4622 expr = expression.expression 4623 4624 if self.dialect.LOG_BASE_FIRST is False: 4625 this, expr = expr, this 4626 elif self.dialect.LOG_BASE_FIRST is None and expr: 4627 if this.name in ("2", "10"): 4628 return self.func(f"LOG{this.name}", expr) 4629 4630 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4631 4632 return self.func("LOG", this, expr) 4633 4634 def use_sql(self, expression: exp.Use) -> str: 4635 kind = self.sql(expression, "kind") 4636 kind = f" {kind}" if kind else "" 4637 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4638 this = f" {this}" if this else "" 4639 return f"USE{kind}{this}" 4640 4641 def binary(self, expression: exp.Binary, op: str) -> str: 4642 sqls: list[str] = [] 4643 stack: list[None | str | exp.Expr] = [expression] 4644 binary_type = type(expression) 4645 4646 while stack: 4647 node = stack.pop() 4648 4649 if type(node) is binary_type: 4650 op_func = node.args.get("operator") 4651 if op_func: 4652 op = f"OPERATOR({self.sql(op_func)})" 4653 4654 stack.append(node.args.get("expression")) 4655 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4656 stack.append(node.args.get("this")) 4657 else: 4658 sqls.append(self.sql(node)) 4659 4660 return "".join(sqls) 4661 4662 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4663 to_clause = self.sql(expression, "to") 4664 if to_clause: 4665 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4666 4667 return self.function_fallback_sql(expression) 4668 4669 def function_fallback_sql(self, expression: exp.Func) -> str: 4670 args = [] 4671 4672 for key in expression.arg_types: 4673 arg_value = expression.args.get(key) 4674 4675 if isinstance(arg_value, list): 4676 for value in arg_value: 4677 args.append(value) 4678 elif arg_value is not None: 4679 args.append(arg_value) 4680 4681 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4682 name = expression.meta_get("name") or expression.sql_name() 4683 else: 4684 name = expression.sql_name() 4685 4686 return self.func(name, *args) 4687 4688 def func( 4689 self, 4690 name: str, 4691 *args: t.Any, 4692 prefix: str = "(", 4693 suffix: str = ")", 4694 normalize: bool = True, 4695 ) -> str: 4696 name = self.normalize_func(name) if normalize else name 4697 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4698 4699 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4700 arg_sqls = tuple( 4701 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4702 ) 4703 if self.pretty and self.too_wide(arg_sqls): 4704 return self.indent( 4705 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4706 ) 4707 return sep.join(arg_sqls) 4708 4709 def too_wide(self, args: t.Iterable) -> bool: 4710 return sum(len(arg) for arg in args) > self.max_text_width 4711 4712 def format_time( 4713 self, 4714 expression: exp.Expr, 4715 inverse_time_mapping: dict[str, str] | None = None, 4716 inverse_time_trie: dict | None = None, 4717 ) -> str | None: 4718 return format_time( 4719 self.sql(expression, "format"), 4720 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4721 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4722 ) 4723 4724 def expressions( 4725 self, 4726 expression: exp.Expr | None = None, 4727 key: str | None = None, 4728 sqls: t.Collection[str | exp.Expr] | None = None, 4729 flat: bool = False, 4730 indent: bool = True, 4731 skip_first: bool = False, 4732 skip_last: bool = False, 4733 sep: str = ", ", 4734 prefix: str = "", 4735 dynamic: bool = False, 4736 new_line: bool = False, 4737 ) -> str: 4738 expressions = expression.args.get(key or "expressions") if expression else sqls 4739 4740 if not expressions: 4741 return "" 4742 4743 if flat: 4744 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4745 4746 num_sqls = len(expressions) 4747 result_sqls = [] 4748 4749 for i, e in enumerate(expressions): 4750 sql = self.sql(e, comment=False) 4751 if not sql: 4752 continue 4753 4754 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4755 4756 if self.pretty: 4757 if self.leading_comma: 4758 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4759 else: 4760 result_sqls.append( 4761 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4762 ) 4763 else: 4764 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4765 4766 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4767 if new_line: 4768 result_sqls.insert(0, "") 4769 result_sqls.append("") 4770 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4771 else: 4772 result_sql = "".join(result_sqls) 4773 4774 return ( 4775 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4776 if indent 4777 else result_sql 4778 ) 4779 4780 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4781 flat = flat or isinstance(expression.parent, exp.Properties) 4782 expressions_sql = self.expressions(expression, flat=flat) 4783 if flat: 4784 return f"{op} {expressions_sql}" 4785 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4786 4787 def naked_property(self, expression: exp.Property) -> str: 4788 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4789 if not property_name: 4790 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4791 return f"{property_name} {self.sql(expression, 'this')}" 4792 4793 def tag_sql(self, expression: exp.Tag) -> str: 4794 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4795 4796 def token_sql(self, token_type: TokenType) -> str: 4797 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4798 4799 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4800 this = self.sql(expression, "this") 4801 expressions = self.no_identify(self.expressions, expression) 4802 expressions = ( 4803 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4804 ) 4805 return f"{this}{expressions}" if expressions.strip() != "" else this 4806 4807 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4808 return self.expressions(expression, flat=True) 4809 4810 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4811 params = self.no_identify(self.expressions, expression, flat=True) 4812 body = self.sql(expression, "this") 4813 prefix = "TABLE " if expression.args.get("is_table") else "" 4814 return f"({params}) AS {prefix}{body}" 4815 4816 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4817 this = self.sql(expression, "this") 4818 expressions = self.expressions(expression, flat=True) 4819 return f"{this}({expressions})" 4820 4821 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4822 return self.binary(expression, "=>") 4823 4824 def when_sql(self, expression: exp.When) -> str: 4825 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4826 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4827 condition = self.sql(expression, "condition") 4828 condition = f" AND {condition}" if condition else "" 4829 4830 then_expression = expression.args.get("then") 4831 if isinstance(then_expression, exp.Insert): 4832 this = self.sql(then_expression, "this") 4833 this = f"INSERT {this}" if this else "INSERT" 4834 then = self.sql(then_expression, "expression") 4835 then = f"{this} VALUES {then}" if then else this 4836 elif isinstance(then_expression, exp.Update): 4837 if isinstance(then_expression.args.get("expressions"), exp.Star): 4838 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4839 else: 4840 expressions_sql = self.expressions(then_expression) 4841 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4842 else: 4843 then = self.sql(then_expression) 4844 4845 if isinstance(then_expression, (exp.Insert, exp.Update)): 4846 where = self.sql(then_expression, "where") 4847 if where and not self.SUPPORTS_MERGE_WHERE: 4848 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4849 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4850 where = "" 4851 then = f"{then}{where}" 4852 return f"WHEN {matched}{source}{condition} THEN {then}" 4853 4854 def whens_sql(self, expression: exp.Whens) -> str: 4855 return self.expressions(expression, sep=" ", indent=False) 4856 4857 def merge_sql(self, expression: exp.Merge) -> str: 4858 table = expression.this 4859 table_alias = "" 4860 4861 hints = table.args.get("hints") 4862 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4863 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4864 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4865 4866 this = self.sql(table) 4867 using = f"USING {self.sql(expression, 'using')}" 4868 whens = self.sql(expression, "whens") 4869 4870 on = self.sql(expression, "on") 4871 on = f"ON {on}" if on else "" 4872 4873 if not on: 4874 on = self.expressions(expression, key="using_cond") 4875 on = f"USING ({on})" if on else "" 4876 4877 returning = self.sql(expression, "returning") 4878 if returning: 4879 whens = f"{whens}{returning}" 4880 4881 sep = self.sep() 4882 4883 return self.prepend_ctes( 4884 expression, 4885 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4886 ) 4887 4888 @unsupported_args("format") 4889 def tochar_sql(self, expression: exp.ToChar) -> str: 4890 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4891 4892 @unsupported_args("default") 4893 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4894 if not self.SUPPORTS_TO_NUMBER: 4895 self.unsupported("Unsupported TO_NUMBER function") 4896 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4897 4898 fmt = expression.args.get("format") 4899 if not fmt: 4900 self.unsupported("Conversion format is required for TO_NUMBER") 4901 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4902 4903 return self.func("TO_NUMBER", expression.this, fmt) 4904 4905 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4906 this = self.sql(expression, "this") 4907 kind = self.sql(expression, "kind") 4908 settings_sql = self.expressions(expression, key="settings", sep=" ") 4909 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4910 return f"{this}({kind}{args})" 4911 4912 def dictrange_sql(self, expression: exp.DictRange) -> str: 4913 this = self.sql(expression, "this") 4914 max = self.sql(expression, "max") 4915 min = self.sql(expression, "min") 4916 return f"{this}(MIN {min} MAX {max})" 4917 4918 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4919 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4920 4921 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4922 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4923 4924 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4925 def uniquekeyproperty_sql( 4926 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4927 ) -> str: 4928 return f"{prefix} ({self.expressions(expression, flat=True)})" 4929 4930 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4931 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4932 expressions = self.expressions(expression, flat=True) 4933 expressions = f" {self.wrap(expressions)}" if expressions else "" 4934 buckets = self.sql(expression, "buckets") 4935 kind = self.sql(expression, "kind") 4936 buckets = f" BUCKETS {buckets}" if buckets else "" 4937 order = self.sql(expression, "order") 4938 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4939 4940 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4941 return "" 4942 4943 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4944 expressions = self.expressions(expression, key="expressions", flat=True) 4945 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4946 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4947 buckets = self.sql(expression, "buckets") 4948 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4949 4950 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4951 this = self.sql(expression, "this") 4952 having = self.sql(expression, "having") 4953 4954 if having: 4955 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4956 4957 return self.func("ANY_VALUE", this) 4958 4959 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4960 transform = self.func("TRANSFORM", *expression.expressions) 4961 row_format_before = self.sql(expression, "row_format_before") 4962 row_format_before = f" {row_format_before}" if row_format_before else "" 4963 record_writer = self.sql(expression, "record_writer") 4964 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4965 using = f" USING {self.sql(expression, 'command_script')}" 4966 schema = self.sql(expression, "schema") 4967 schema = f" AS {schema}" if schema else "" 4968 row_format_after = self.sql(expression, "row_format_after") 4969 row_format_after = f" {row_format_after}" if row_format_after else "" 4970 record_reader = self.sql(expression, "record_reader") 4971 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4972 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4973 4974 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4975 key_block_size = self.sql(expression, "key_block_size") 4976 if key_block_size: 4977 return f"KEY_BLOCK_SIZE = {key_block_size}" 4978 4979 using = self.sql(expression, "using") 4980 if using: 4981 return f"USING {using}" 4982 4983 parser = self.sql(expression, "parser") 4984 if parser: 4985 return f"WITH PARSER {parser}" 4986 4987 comment = self.sql(expression, "comment") 4988 if comment: 4989 return f"COMMENT {comment}" 4990 4991 visible = expression.args.get("visible") 4992 if visible is not None: 4993 return "VISIBLE" if visible else "INVISIBLE" 4994 4995 engine_attr = self.sql(expression, "engine_attr") 4996 if engine_attr: 4997 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4998 4999 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5000 if secondary_engine_attr: 5001 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5002 5003 self.unsupported("Unsupported index constraint option.") 5004 return "" 5005 5006 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5007 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5008 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5009 5010 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5011 kind = self.sql(expression, "kind") 5012 kind = f"{kind} INDEX" if kind else "INDEX" 5013 this = self.sql(expression, "this") 5014 this = f" {this}" if this else "" 5015 index_type = self.sql(expression, "index_type") 5016 index_type = f" USING {index_type}" if index_type else "" 5017 expressions = self.expressions(expression, flat=True) 5018 expressions = f" ({expressions})" if expressions else "" 5019 options = self.expressions(expression, key="options", sep=" ") 5020 options = f" {options}" if options else "" 5021 return f"{kind}{this}{index_type}{expressions}{options}" 5022 5023 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5024 if self.NVL2_SUPPORTED: 5025 return self.function_fallback_sql(expression) 5026 5027 case = exp.Case().when( 5028 expression.this.is_(exp.null()).not_(copy=False), 5029 expression.args["true"], 5030 copy=False, 5031 ) 5032 else_cond = expression.args.get("false") 5033 if else_cond: 5034 case.else_(else_cond, copy=False) 5035 5036 return self.sql(case) 5037 5038 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5039 this = self.sql(expression, "this") 5040 expr = self.sql(expression, "expression") 5041 position = self.sql(expression, "position") 5042 position = f", {position}" if position else "" 5043 iterator = self.sql(expression, "iterator") 5044 condition = self.sql(expression, "condition") 5045 condition = f" IF {condition}" if condition else "" 5046 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5047 5048 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5049 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5050 5051 def opclass_sql(self, expression: exp.Opclass) -> str: 5052 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5053 5054 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5055 model = self.sql(expression, "this") 5056 model = f"MODEL {model}" 5057 expr = expression.expression 5058 if expr: 5059 expr_sql = self.sql(expression, "expression") 5060 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5061 else: 5062 expr_sql = None 5063 5064 parameters = self.sql(expression, "params_struct") or None 5065 5066 return self.func(name, model, expr_sql, parameters) 5067 5068 def predict_sql(self, expression: exp.Predict) -> str: 5069 return self._ml_sql(expression, "PREDICT") 5070 5071 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5072 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5073 return self._ml_sql(expression, name) 5074 5075 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5076 return self._ml_sql(expression, "GENERATE_TEXT") 5077 5078 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5079 return self._ml_sql(expression, "GENERATE_TABLE") 5080 5081 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5082 return self._ml_sql(expression, "GENERATE_BOOL") 5083 5084 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5085 return self._ml_sql(expression, "GENERATE_INT") 5086 5087 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5088 return self._ml_sql(expression, "GENERATE_DOUBLE") 5089 5090 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5091 return self._ml_sql(expression, "TRANSLATE") 5092 5093 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5094 return self._ml_sql(expression, "FORECAST") 5095 5096 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5097 this_sql = self.sql(expression, "this") 5098 if isinstance(expression.this, exp.Table): 5099 this_sql = f"TABLE {this_sql}" 5100 5101 return self.func( 5102 "FORECAST", 5103 this_sql, 5104 expression.args.get("data_col"), 5105 expression.args.get("timestamp_col"), 5106 expression.args.get("model"), 5107 expression.args.get("id_cols"), 5108 expression.args.get("horizon"), 5109 expression.args.get("forecast_end_timestamp"), 5110 expression.args.get("confidence_level"), 5111 expression.args.get("output_historical_time_series"), 5112 expression.args.get("context_window"), 5113 ) 5114 5115 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5116 this_sql = self.sql(expression, "this") 5117 if isinstance(expression.this, exp.Table): 5118 this_sql = f"TABLE {this_sql}" 5119 5120 return self.func( 5121 "FEATURES_AT_TIME", 5122 this_sql, 5123 expression.args.get("time"), 5124 expression.args.get("num_rows"), 5125 expression.args.get("ignore_feature_nulls"), 5126 ) 5127 5128 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5129 this_sql = self.sql(expression, "this") 5130 if isinstance(expression.this, exp.Table): 5131 this_sql = f"TABLE {this_sql}" 5132 5133 query_table = self.sql(expression, "query_table") 5134 if isinstance(expression.args["query_table"], exp.Table): 5135 query_table = f"TABLE {query_table}" 5136 5137 return self.func( 5138 "VECTOR_SEARCH", 5139 this_sql, 5140 expression.args.get("column_to_search"), 5141 query_table, 5142 expression.args.get("query_column_to_search"), 5143 expression.args.get("top_k"), 5144 expression.args.get("distance_type"), 5145 expression.args.get("options"), 5146 ) 5147 5148 def forin_sql(self, expression: exp.ForIn) -> str: 5149 this = self.sql(expression, "this") 5150 expression_sql = self.sql(expression, "expression") 5151 return f"FOR {this} DO {expression_sql}" 5152 5153 def refresh_sql(self, expression: exp.Refresh) -> str: 5154 this = self.sql(expression, "this") 5155 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5156 return f"REFRESH {kind}{this}" 5157 5158 def toarray_sql(self, expression: exp.ToArray) -> str: 5159 arg = expression.this 5160 if not arg.type: 5161 import sqlglot.optimizer.annotate_types 5162 5163 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5164 5165 if arg.is_type(exp.DType.ARRAY): 5166 return self.sql(arg) 5167 5168 cond_for_null = arg.is_(exp.null()) 5169 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5170 5171 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5172 this = expression.this 5173 time_format = self.format_time(expression) 5174 5175 if time_format: 5176 return self.sql( 5177 exp.cast( 5178 exp.StrToTime(this=this, format=expression.args["format"]), 5179 exp.DType.TIME, 5180 ) 5181 ) 5182 5183 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5184 return self.sql(this) 5185 5186 return self.sql(exp.cast(this, exp.DType.TIME)) 5187 5188 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5189 this = expression.this 5190 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5191 return self.sql(this) 5192 5193 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5194 5195 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5196 this = expression.this 5197 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5198 return self.sql(this) 5199 5200 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5201 5202 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5203 this = expression.this 5204 time_format = self.format_time(expression) 5205 safe = expression.args.get("safe") 5206 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5207 return self.sql( 5208 exp.cast( 5209 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5210 exp.DType.DATE, 5211 ) 5212 ) 5213 5214 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5215 return self.sql(this) 5216 5217 if safe: 5218 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5219 5220 return self.sql(exp.cast(this, exp.DType.DATE)) 5221 5222 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5223 return self.sql( 5224 exp.func( 5225 "DATEDIFF", 5226 expression.this, 5227 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5228 "day", 5229 ) 5230 ) 5231 5232 def lastday_sql(self, expression: exp.LastDay) -> str: 5233 if self.LAST_DAY_SUPPORTS_DATE_PART: 5234 return self.function_fallback_sql(expression) 5235 5236 unit = expression.args.get("unit") 5237 if unit and unit.name.upper() != "MONTH": 5238 self.unsupported("Date parts are not supported in LAST_DAY.") 5239 5240 return self.func("LAST_DAY", expression.this) 5241 5242 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5243 import sqlglot.dialects.dialect 5244 5245 return self.func( 5246 "DATE_ADD", 5247 expression.this, 5248 expression.expression, 5249 sqlglot.dialects.dialect.unit_to_str(expression), 5250 ) 5251 5252 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5253 if self.CAN_IMPLEMENT_ARRAY_ANY: 5254 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5255 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5256 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5257 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5258 5259 import sqlglot.dialects.dialect 5260 5261 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5262 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5263 self.unsupported("ARRAY_ANY is unsupported") 5264 5265 return self.function_fallback_sql(expression) 5266 5267 def struct_sql(self, expression: exp.Struct) -> str: 5268 expression.set( 5269 "expressions", 5270 [ 5271 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5272 if isinstance(e, exp.PropertyEQ) 5273 else e 5274 for e in expression.expressions 5275 ], 5276 ) 5277 5278 return self.function_fallback_sql(expression) 5279 5280 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5281 low = self.sql(expression, "this") 5282 high = self.sql(expression, "expression") 5283 5284 return f"{low} TO {high}" 5285 5286 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5287 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5288 tables = f" {self.expressions(expression)}" 5289 5290 exists = " IF EXISTS" if expression.args.get("exists") else "" 5291 5292 on_cluster = self.sql(expression, "cluster") 5293 on_cluster = f" {on_cluster}" if on_cluster else "" 5294 5295 identity = self.sql(expression, "identity") 5296 identity = f" {identity} IDENTITY" if identity else "" 5297 5298 option = self.sql(expression, "option") 5299 option = f" {option}" if option else "" 5300 5301 partition = self.sql(expression, "partition") 5302 partition = f" {partition}" if partition else "" 5303 5304 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5305 5306 # This transpiles T-SQL's CONVERT function 5307 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5308 def convert_sql(self, expression: exp.Convert) -> str: 5309 to = expression.this 5310 value = expression.expression 5311 style = expression.args.get("style") 5312 safe = expression.args.get("safe") 5313 strict = expression.args.get("strict") 5314 5315 if not to or not value: 5316 return "" 5317 5318 # Retrieve length of datatype and override to default if not specified 5319 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5320 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5321 5322 transformed: exp.Expr | None = None 5323 cast = exp.Cast if strict else exp.TryCast 5324 5325 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5326 if isinstance(style, exp.Literal) and style.is_int: 5327 import sqlglot.dialects.tsql 5328 5329 style_value = style.name 5330 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5331 if not converted_style: 5332 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5333 5334 fmt = exp.Literal.string(converted_style) 5335 5336 if to.this == exp.DType.DATE: 5337 transformed = exp.StrToDate(this=value, format=fmt) 5338 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5339 transformed = exp.StrToTime(this=value, format=fmt) 5340 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5341 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5342 elif to.this == exp.DType.TEXT: 5343 transformed = exp.TimeToStr(this=value, format=fmt) 5344 5345 if not transformed: 5346 transformed = cast(this=value, to=to, safe=safe) 5347 5348 return self.sql(transformed) 5349 5350 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5351 this = expression.this 5352 if isinstance(this, exp.JSONPathWildcard): 5353 this = self.json_path_part(this) 5354 return f".{this}" if this else "" 5355 5356 quoted = expression.args.get("quoted") 5357 if not ( 5358 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5359 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5360 return f".{this}" 5361 5362 this = self.json_path_part(this) 5363 5364 if quoted and self.QUOTE_JSON_PATH: 5365 # The whole path is rendered as a single quoted string literal, so the bracketed key 5366 # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be 5367 # escaped again for the outer string literal (-> ["x \\"y\\"z"]). 5368 this = self.escape_str(this) 5369 5370 return ( 5371 f"[{this}]" 5372 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5373 else f".{this}" 5374 ) 5375 5376 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5377 this = self.json_path_part(expression.this) 5378 return f"[{this}]" if this else "" 5379 5380 def _simplify_unless_literal(self, expression: E) -> E: 5381 if not isinstance(expression, exp.Literal): 5382 import sqlglot.optimizer.simplify 5383 5384 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5385 5386 return expression 5387 5388 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5389 this = expression.this 5390 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5391 self.unsupported( 5392 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5393 ) 5394 return self.sql(this) 5395 5396 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5397 if self.IGNORE_NULLS_BEFORE_ORDER: 5398 # The first modifier here will be the one closest to the AggFunc's arg 5399 mods = sorted( 5400 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5401 key=lambda x: ( 5402 0 5403 if isinstance(x, exp.HavingMax) 5404 else (1 if isinstance(x, exp.Order) else 2) 5405 ), 5406 ) 5407 5408 if mods: 5409 mod = mods[0] 5410 this = expression.__class__(this=mod.this.copy()) 5411 this.meta["inline"] = True 5412 mod.this.replace(this) 5413 return self.sql(expression.this) 5414 5415 agg_func = expression.find(exp.AggFunc) 5416 5417 if agg_func: 5418 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5419 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5420 5421 return f"{self.sql(expression, 'this')} {text}" 5422 5423 def _replace_line_breaks(self, string: str) -> str: 5424 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5425 if self.pretty: 5426 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5427 return string 5428 5429 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5430 option = self.sql(expression, "this") 5431 5432 if expression.expressions: 5433 upper = option.upper() 5434 5435 # Snowflake FILE_FORMAT options are separated by whitespace 5436 sep = " " if upper == "FILE_FORMAT" else ", " 5437 5438 # Databricks copy/format options do not set their list of values with EQ 5439 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5440 values = self.expressions(expression, flat=True, sep=sep) 5441 return f"{option}{op}({values})" 5442 5443 value = self.sql(expression, "expression") 5444 5445 if not value: 5446 return option 5447 5448 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5449 5450 return f"{option}{op}{value}" 5451 5452 def credentials_sql(self, expression: exp.Credentials) -> str: 5453 cred_expr = expression.args.get("credentials") 5454 if isinstance(cred_expr, exp.Literal): 5455 # Redshift case: CREDENTIALS <string> 5456 credentials = self.sql(expression, "credentials") 5457 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5458 else: 5459 # Snowflake case: CREDENTIALS = (...) 5460 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5461 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5462 5463 storage = self.sql(expression, "storage") 5464 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5465 5466 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5467 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5468 5469 iam_role = self.sql(expression, "iam_role") 5470 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5471 5472 region = self.sql(expression, "region") 5473 region = f" REGION {region}" if region else "" 5474 5475 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5476 5477 def copy_sql(self, expression: exp.Copy) -> str: 5478 this = self.sql(expression, "this") 5479 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5480 5481 credentials = self.sql(expression, "credentials") 5482 credentials = self.seg(credentials) if credentials else "" 5483 files = self.expressions(expression, key="files", flat=True) 5484 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5485 5486 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5487 params = self.expressions( 5488 expression, 5489 key="params", 5490 sep=sep, 5491 new_line=True, 5492 skip_last=True, 5493 skip_first=True, 5494 indent=self.COPY_PARAMS_ARE_WRAPPED, 5495 ) 5496 5497 if params: 5498 if self.COPY_PARAMS_ARE_WRAPPED: 5499 params = f" WITH ({params})" 5500 elif not self.pretty and (files or credentials): 5501 params = f" {params}" 5502 5503 return f"COPY{this}{kind} {files}{credentials}{params}" 5504 5505 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5506 return "" 5507 5508 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5509 on_sql = "ON" if expression.args.get("on") else "OFF" 5510 filter_col: str | None = self.sql(expression, "filter_column") 5511 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5512 retention_period: str | None = self.sql(expression, "retention_period") 5513 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5514 5515 if filter_col or retention_period: 5516 on_sql = self.func("ON", filter_col, retention_period) 5517 5518 return f"DATA_DELETION={on_sql}" 5519 5520 def maskingpolicycolumnconstraint_sql( 5521 self, expression: exp.MaskingPolicyColumnConstraint 5522 ) -> str: 5523 this = self.sql(expression, "this") 5524 expressions = self.expressions(expression, flat=True) 5525 expressions = f" USING ({expressions})" if expressions else "" 5526 return f"MASKING POLICY {this}{expressions}" 5527 5528 def gapfill_sql(self, expression: exp.GapFill) -> str: 5529 this = self.sql(expression, "this") 5530 this = f"TABLE {this}" 5531 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5532 5533 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5534 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5535 5536 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5537 this = self.sql(expression, "this") 5538 expr = expression.expression 5539 5540 if isinstance(expr, exp.Func): 5541 # T-SQL's CLR functions are case sensitive 5542 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5543 else: 5544 expr = self.sql(expression, "expression") 5545 5546 return self.scope_resolution(expr, this) 5547 5548 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5549 if self.PARSE_JSON_NAME is None: 5550 return self.sql(expression.this) 5551 5552 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5553 5554 def rand_sql(self, expression: exp.Rand) -> str: 5555 lower = self.sql(expression, "lower") 5556 upper = self.sql(expression, "upper") 5557 5558 if lower and upper: 5559 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5560 return self.func("RAND", expression.this) 5561 5562 def changes_sql(self, expression: exp.Changes) -> str: 5563 information = self.sql(expression, "information") 5564 information = f"INFORMATION => {information}" 5565 at_before = self.sql(expression, "at_before") 5566 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5567 end = self.sql(expression, "end") 5568 end = f"{self.seg('')}{end}" if end else "" 5569 5570 return f"CHANGES ({information}){at_before}{end}" 5571 5572 def pad_sql(self, expression: exp.Pad) -> str: 5573 prefix = "L" if expression.args.get("is_left") else "R" 5574 5575 fill_pattern = self.sql(expression, "fill_pattern") or None 5576 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5577 fill_pattern = "' '" 5578 5579 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5580 5581 def summarize_sql(self, expression: exp.Summarize) -> str: 5582 table = " TABLE" if expression.args.get("table") else "" 5583 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5584 5585 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5586 generate_series = exp.GenerateSeries(**expression.args) 5587 5588 parent = expression.parent 5589 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5590 parent = parent.parent 5591 5592 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5593 return self.sql(exp.Unnest(expressions=[generate_series])) 5594 5595 if isinstance(parent, exp.Select): 5596 self.unsupported("GenerateSeries projection unnesting is not supported.") 5597 5598 return self.sql(generate_series) 5599 5600 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5601 if self.SUPPORTS_CONVERT_TIMEZONE: 5602 return self.function_fallback_sql(expression) 5603 5604 source_tz = expression.args.get("source_tz") 5605 target_tz = expression.args.get("target_tz") 5606 timestamp = expression.args.get("timestamp") 5607 5608 if source_tz and timestamp: 5609 timestamp = exp.AtTimeZone( 5610 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5611 ) 5612 5613 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5614 5615 return self.sql(expr) 5616 5617 def json_sql(self, expression: exp.JSON) -> str: 5618 this = self.sql(expression, "this") 5619 this = f" {this}" if this else "" 5620 5621 _with = expression.args.get("with_") 5622 5623 if _with is None: 5624 with_sql = "" 5625 elif not _with: 5626 with_sql = " WITHOUT" 5627 else: 5628 with_sql = " WITH" 5629 5630 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5631 5632 return f"JSON{this}{with_sql}{unique_sql}" 5633 5634 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5635 path = self.sql(expression, "path") 5636 returning = self.sql(expression, "returning") 5637 returning = f" RETURNING {returning}" if returning else "" 5638 5639 on_condition = self.sql(expression, "on_condition") 5640 on_condition = f" {on_condition}" if on_condition else "" 5641 5642 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5643 5644 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5645 regexp = " REGEXP" if expression.args.get("regexp") else "" 5646 return f"SKIP{regexp} {self.sql(expression.expression)}" 5647 5648 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5649 else_ = "ELSE " if expression.args.get("else_") else "" 5650 condition = self.sql(expression, "expression") 5651 condition = f"WHEN {condition} THEN " if condition else else_ 5652 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5653 return f"{condition}{insert}" 5654 5655 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5656 kind = self.sql(expression, "kind") 5657 expressions = self.seg(self.expressions(expression, sep=" ")) 5658 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5659 return res 5660 5661 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5662 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5663 empty = expression.args.get("empty") 5664 empty = ( 5665 f"DEFAULT {empty} ON EMPTY" 5666 if isinstance(empty, exp.Expr) 5667 else self.sql(expression, "empty") 5668 ) 5669 5670 error = expression.args.get("error") 5671 error = ( 5672 f"DEFAULT {error} ON ERROR" 5673 if isinstance(error, exp.Expr) 5674 else self.sql(expression, "error") 5675 ) 5676 5677 if error and empty: 5678 error = ( 5679 f"{empty} {error}" 5680 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5681 else f"{error} {empty}" 5682 ) 5683 empty = "" 5684 5685 null = self.sql(expression, "null") 5686 5687 return f"{empty}{error}{null}" 5688 5689 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5690 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5691 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5692 5693 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5694 this = self.sql(expression, "this") 5695 path = self.sql(expression, "path") 5696 5697 passing = self.expressions(expression, "passing") 5698 passing = f" PASSING {passing}" if passing else "" 5699 5700 on_condition = self.sql(expression, "on_condition") 5701 on_condition = f" {on_condition}" if on_condition else "" 5702 5703 path = f"{path}{passing}{on_condition}" 5704 5705 return self.func("JSON_EXISTS", this, path) 5706 5707 def _add_arrayagg_null_filter( 5708 self, 5709 array_agg_sql: str, 5710 array_agg_expr: exp.ArrayAgg, 5711 column_expr: exp.Expr, 5712 ) -> str: 5713 """ 5714 Add NULL filter to ARRAY_AGG if dialect requires it. 5715 5716 Args: 5717 array_agg_sql: The generated ARRAY_AGG SQL string 5718 array_agg_expr: The ArrayAgg expression node 5719 column_expr: The column/expression to filter (before ORDER BY wrapping) 5720 5721 Returns: 5722 SQL string with FILTER clause added if needed 5723 """ 5724 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5725 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5726 if not ( 5727 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5728 ): 5729 return array_agg_sql 5730 5731 parent = array_agg_expr.parent 5732 if isinstance(parent, exp.Filter): 5733 parent_cond = parent.expression.this 5734 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5735 elif column_expr.find(exp.Column): 5736 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5737 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5738 this_sql = ( 5739 self.expressions(column_expr) 5740 if isinstance(column_expr, exp.Distinct) 5741 else self.sql(column_expr) 5742 ) 5743 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5744 5745 return array_agg_sql 5746 5747 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5748 array_agg = self.function_fallback_sql(expression) 5749 column_expr = expression.this 5750 if isinstance(column_expr, exp.Order): 5751 column_expr = column_expr.this 5752 5753 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5754 5755 def slice_sql(self, expression: exp.Slice) -> str: 5756 step = self.sql(expression, "step") 5757 end = self.sql(expression.expression) 5758 begin = self.sql(expression.this) 5759 5760 sql = f"{end}:{step}" if step else end 5761 return f"{begin}:{sql}" if sql else f"{begin}:" 5762 5763 def apply_sql(self, expression: exp.Apply) -> str: 5764 this = self.sql(expression, "this") 5765 expr = self.sql(expression, "expression") 5766 5767 return f"{this} APPLY({expr})" 5768 5769 def _grant_or_revoke_sql( 5770 self, 5771 expression: exp.Grant | exp.Revoke, 5772 keyword: str, 5773 preposition: str, 5774 grant_option_prefix: str = "", 5775 grant_option_suffix: str = "", 5776 ) -> str: 5777 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5778 5779 kind = self.sql(expression, "kind") 5780 kind = f" {kind}" if kind else "" 5781 5782 securable = self.sql(expression, "securable") 5783 securable = f" {securable}" if securable else "" 5784 5785 principals = self.expressions(expression, key="principals", flat=True) 5786 5787 if not expression.args.get("grant_option"): 5788 grant_option_prefix = grant_option_suffix = "" 5789 5790 # cascade for revoke only 5791 cascade = self.sql(expression, "cascade") 5792 cascade = f" {cascade}" if cascade else "" 5793 5794 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5795 5796 def grant_sql(self, expression: exp.Grant) -> str: 5797 return self._grant_or_revoke_sql( 5798 expression, 5799 keyword="GRANT", 5800 preposition="TO", 5801 grant_option_suffix=" WITH GRANT OPTION", 5802 ) 5803 5804 def revoke_sql(self, expression: exp.Revoke) -> str: 5805 return self._grant_or_revoke_sql( 5806 expression, 5807 keyword="REVOKE", 5808 preposition="FROM", 5809 grant_option_prefix="GRANT OPTION FOR ", 5810 ) 5811 5812 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5813 this = self.sql(expression, "this") 5814 columns = self.expressions(expression, flat=True) 5815 columns = f"({columns})" if columns else "" 5816 5817 return f"{this}{columns}" 5818 5819 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5820 this = self.sql(expression, "this") 5821 5822 kind = self.sql(expression, "kind") 5823 kind = f"{kind} " if kind else "" 5824 5825 return f"{kind}{this}" 5826 5827 def columns_sql(self, expression: exp.Columns) -> str: 5828 func = self.function_fallback_sql(expression) 5829 if expression.args.get("unpack"): 5830 func = f"*{func}" 5831 5832 return func 5833 5834 def overlay_sql(self, expression: exp.Overlay) -> str: 5835 this = self.sql(expression, "this") 5836 expr = self.sql(expression, "expression") 5837 from_sql = self.sql(expression, "from_") 5838 for_sql = self.sql(expression, "for_") 5839 for_sql = f" FOR {for_sql}" if for_sql else "" 5840 5841 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5842 5843 @unsupported_args("format") 5844 def todouble_sql(self, expression: exp.ToDouble) -> str: 5845 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5846 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5847 5848 def string_sql(self, expression: exp.String) -> str: 5849 this = expression.this 5850 zone = expression.args.get("zone") 5851 5852 if zone: 5853 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5854 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5855 # set for source_tz to transpile the time conversion before the STRING cast 5856 this = exp.ConvertTimezone( 5857 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5858 ) 5859 5860 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5861 5862 def median_sql(self, expression: exp.Median) -> str: 5863 if not self.SUPPORTS_MEDIAN: 5864 return self.sql( 5865 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5866 ) 5867 5868 return self.function_fallback_sql(expression) 5869 5870 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5871 filler = self.sql(expression, "this") 5872 filler = f" {filler}" if filler else "" 5873 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5874 return f"TRUNCATE{filler} {with_count}" 5875 5876 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5877 if self.SUPPORTS_UNIX_SECONDS: 5878 return self.function_fallback_sql(expression) 5879 5880 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5881 5882 return self.sql( 5883 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5884 ) 5885 5886 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5887 dim = expression.expression 5888 5889 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5890 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5891 if not (dim.is_int and dim.name == "1"): 5892 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5893 dim = None 5894 5895 # If dimension is required but not specified, default initialize it 5896 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5897 dim = exp.Literal.number(1) 5898 5899 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5900 5901 def attach_sql(self, expression: exp.Attach) -> str: 5902 this = self.sql(expression, "this") 5903 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5904 expressions = self.expressions(expression) 5905 expressions = f" ({expressions})" if expressions else "" 5906 5907 return f"ATTACH{exists_sql} {this}{expressions}" 5908 5909 def detach_sql(self, expression: exp.Detach) -> str: 5910 kind = self.sql(expression, "kind") 5911 kind = f" {kind}" if kind else "" 5912 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5913 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5914 exists = " IF EXISTS" if expression.args.get("exists") else "" 5915 if exists: 5916 kind = kind or " DATABASE" 5917 5918 this = self.sql(expression, "this") 5919 this = f" {this}" if this else "" 5920 cluster = self.sql(expression, "cluster") 5921 cluster = f" {cluster}" if cluster else "" 5922 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5923 sync = " SYNC" if expression.args.get("sync") else "" 5924 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5925 5926 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5927 this = self.sql(expression, "this") 5928 value = self.sql(expression, "expression") 5929 value = f" {value}" if value else "" 5930 return f"{this}{value}" 5931 5932 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5933 return ( 5934 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5935 ) 5936 5937 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5938 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5939 encode = f"{encode} {self.sql(expression, 'this')}" 5940 5941 properties = expression.args.get("properties") 5942 if properties: 5943 encode = f"{encode} {self.properties(properties)}" 5944 5945 return encode 5946 5947 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5948 this = self.sql(expression, "this") 5949 include = f"INCLUDE {this}" 5950 5951 column_def = self.sql(expression, "column_def") 5952 if column_def: 5953 include = f"{include} {column_def}" 5954 5955 alias = self.sql(expression, "alias") 5956 if alias: 5957 include = f"{include} AS {alias}" 5958 5959 return include 5960 5961 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5962 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5963 name = f"{prefix} {self.sql(expression, 'this')}" 5964 return self.func("XMLELEMENT", name, *expression.expressions) 5965 5966 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5967 this = self.sql(expression, "this") 5968 expr = self.sql(expression, "expression") 5969 expr = f"({expr})" if expr else "" 5970 return f"{this}{expr}" 5971 5972 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5973 partitions = self.expressions(expression, "partition_expressions") 5974 create = self.expressions(expression, "create_expressions") 5975 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5976 5977 def partitionbyrangepropertydynamic_sql( 5978 self, expression: exp.PartitionByRangePropertyDynamic 5979 ) -> str: 5980 start = self.sql(expression, "start") 5981 end = self.sql(expression, "end") 5982 5983 every = expression.args["every"] 5984 if isinstance(every, exp.Interval) and every.this.is_string: 5985 every.this.replace(exp.Literal.number(every.name)) 5986 5987 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5988 5989 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5990 name = self.sql(expression, "this") 5991 values = self.expressions(expression, flat=True) 5992 5993 return f"NAME {name} VALUE {values}" 5994 5995 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5996 kind = self.sql(expression, "kind") 5997 sample = self.sql(expression, "sample") 5998 return f"SAMPLE {sample} {kind}" 5999 6000 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6001 kind = self.sql(expression, "kind") 6002 option = self.sql(expression, "option") 6003 option = f" {option}" if option else "" 6004 this = self.sql(expression, "this") 6005 this = f" {this}" if this else "" 6006 columns = self.expressions(expression) 6007 columns = f" {columns}" if columns else "" 6008 return f"{kind}{option} STATISTICS{this}{columns}" 6009 6010 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6011 this = self.sql(expression, "this") 6012 columns = self.expressions(expression) 6013 inner_expression = self.sql(expression, "expression") 6014 inner_expression = f" {inner_expression}" if inner_expression else "" 6015 update_options = self.sql(expression, "update_options") 6016 update_options = f" {update_options} UPDATE" if update_options else "" 6017 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6018 6019 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6020 kind = self.sql(expression, "kind") 6021 kind = f" {kind}" if kind else "" 6022 return f"DELETE{kind} STATISTICS" 6023 6024 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6025 inner_expression = self.sql(expression, "expression") 6026 return f"LIST CHAINED ROWS{inner_expression}" 6027 6028 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6029 kind = self.sql(expression, "kind") 6030 this = self.sql(expression, "this") 6031 this = f" {this}" if this else "" 6032 inner_expression = self.sql(expression, "expression") 6033 return f"VALIDATE {kind}{this}{inner_expression}" 6034 6035 def analyze_sql(self, expression: exp.Analyze) -> str: 6036 options = self.expressions(expression, key="options", sep=" ") 6037 options = f" {options}" if options else "" 6038 kind = self.sql(expression, "kind") 6039 kind = f" {kind}" if kind else "" 6040 this = self.sql(expression, "this") 6041 this = f" {this}" if this else "" 6042 mode = self.sql(expression, "mode") 6043 mode = f" {mode}" if mode else "" 6044 properties = self.sql(expression, "properties") 6045 properties = f" {properties}" if properties else "" 6046 partition = self.sql(expression, "partition") 6047 partition = f" {partition}" if partition else "" 6048 inner_expression = self.sql(expression, "expression") 6049 inner_expression = f" {inner_expression}" if inner_expression else "" 6050 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 6051 6052 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6053 this = self.sql(expression, "this") 6054 namespaces = self.expressions(expression, key="namespaces") 6055 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6056 passing = self.expressions(expression, key="passing") 6057 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6058 columns = self.expressions(expression, key="columns") 6059 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6060 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6061 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6062 6063 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6064 this = self.sql(expression, "this") 6065 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6066 6067 def export_sql(self, expression: exp.Export) -> str: 6068 this = self.sql(expression, "this") 6069 connection = self.sql(expression, "connection") 6070 connection = f"WITH CONNECTION {connection} " if connection else "" 6071 options = self.sql(expression, "options") 6072 return f"EXPORT DATA {connection}{options} AS {this}" 6073 6074 def declare_sql(self, expression: exp.Declare) -> str: 6075 replace = "OR REPLACE " if expression.args.get("replace") else "" 6076 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6077 6078 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6079 variables = self.expressions(expression, "this") 6080 default = self.sql(expression, "default") 6081 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6082 6083 kind = self.sql(expression, "kind") 6084 if isinstance(expression.args.get("kind"), exp.Schema): 6085 kind = f"TABLE {kind}" 6086 6087 kind = f" {kind}" if kind else "" 6088 6089 return f"{variables}{kind}{default}" 6090 6091 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6092 kind = self.sql(expression, "kind") 6093 this = self.sql(expression, "this") 6094 set = self.sql(expression, "expression") 6095 using = self.sql(expression, "using") 6096 using = f" USING {using}" if using else "" 6097 6098 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6099 6100 return f"{kind_sql} {this} SET {set}{using}" 6101 6102 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6103 params = self.expressions(expression, key="params", flat=True) 6104 return self.func(expression.name, *expression.expressions) + f"({params})" 6105 6106 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6107 return self.func(expression.name, *expression.expressions) 6108 6109 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6110 return self.anonymousaggfunc_sql(expression) 6111 6112 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6113 return self.parameterizedagg_sql(expression) 6114 6115 def show_sql(self, expression: exp.Show) -> str: 6116 self.unsupported("Unsupported SHOW statement") 6117 return "" 6118 6119 def install_sql(self, expression: exp.Install) -> str: 6120 self.unsupported("Unsupported INSTALL statement") 6121 return "" 6122 6123 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6124 # Snowflake GET/PUT statements: 6125 # PUT <file> <internalStage> <properties> 6126 # GET <internalStage> <file> <properties> 6127 props = expression.args.get("properties") 6128 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6129 this = self.sql(expression, "this") 6130 target = self.sql(expression, "target") 6131 6132 if isinstance(expression, exp.Put): 6133 return f"PUT {this} {target}{props_sql}" 6134 else: 6135 return f"GET {target} {this}{props_sql}" 6136 6137 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6138 this = self.sql(expression, "this") 6139 expr = self.sql(expression, "expression") 6140 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6141 return f"TRANSLATE({this} USING {expr}{with_error})" 6142 6143 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6144 if self.SUPPORTS_DECODE_CASE: 6145 return self.func("DECODE", *expression.expressions) 6146 6147 decode_expr, *expressions = expression.expressions 6148 6149 ifs = [] 6150 for search, result in zip(expressions[::2], expressions[1::2]): 6151 if isinstance(search, exp.Literal): 6152 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6153 elif isinstance(search, exp.Null): 6154 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6155 else: 6156 if isinstance(search, exp.Binary): 6157 search = exp.paren(search) 6158 6159 cond = exp.or_( 6160 decode_expr.eq(search), 6161 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6162 copy=False, 6163 ) 6164 ifs.append(exp.If(this=cond, true=result)) 6165 6166 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6167 return self.sql(case) 6168 6169 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6170 this = self.sql(expression, "this") 6171 this = self.seg(this, sep="") 6172 dimensions = self.expressions( 6173 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6174 ) 6175 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6176 metrics = self.expressions( 6177 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6178 ) 6179 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6180 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6181 facts = self.seg(f"FACTS {facts}") if facts else "" 6182 where = self.sql(expression, "where") 6183 where = self.seg(f"WHERE {where}") if where else "" 6184 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6185 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6186 6187 def getextract_sql(self, expression: exp.GetExtract) -> str: 6188 this = expression.this 6189 expr = expression.expression 6190 6191 if not this.type or not expression.type: 6192 import sqlglot.optimizer.annotate_types 6193 6194 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6195 6196 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6197 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6198 6199 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6200 6201 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6202 return self.sql( 6203 exp.DateAdd( 6204 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6205 expression=expression.this, 6206 unit=exp.var("DAY"), 6207 ) 6208 ) 6209 6210 def space_sql(self: Generator, expression: exp.Space) -> str: 6211 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6212 6213 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6214 return f"BUILD {self.sql(expression, 'this')}" 6215 6216 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6217 method = self.sql(expression, "method") 6218 kind = expression.args.get("kind") 6219 if not kind: 6220 return f"REFRESH {method}" 6221 6222 every = self.sql(expression, "every") 6223 unit = self.sql(expression, "unit") 6224 every = f" EVERY {every} {unit}" if every else "" 6225 starts = self.sql(expression, "starts") 6226 starts = f" STARTS {starts}" if starts else "" 6227 6228 return f"REFRESH {method} ON {kind}{every}{starts}" 6229 6230 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6231 self.unsupported("The model!attribute syntax is not supported") 6232 return "" 6233 6234 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6235 return self.func("DIRECTORY", expression.this) 6236 6237 def uuid_sql(self, expression: exp.Uuid) -> str: 6238 is_string = expression.args.get("is_string", False) 6239 uuid_func_sql = self.func("UUID") 6240 6241 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6242 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6243 6244 return uuid_func_sql 6245 6246 def initcap_sql(self, expression: exp.Initcap) -> str: 6247 delimiters = expression.expression 6248 6249 if delimiters: 6250 # do not generate delimiters arg if we are round-tripping from default delimiters 6251 if ( 6252 delimiters.is_string 6253 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6254 ): 6255 delimiters = None 6256 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6257 self.unsupported("INITCAP does not support custom delimiters") 6258 delimiters = None 6259 6260 return self.func("INITCAP", expression.this, delimiters) 6261 6262 def localtime_sql(self, expression: exp.Localtime) -> str: 6263 this = expression.this 6264 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6265 6266 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6267 this = expression.this 6268 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6269 6270 def weekstart_name(self, expression: exp.WeekStart) -> str: 6271 import sqlglot.dialects.dialect 6272 6273 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6274 this = expression.this.name.upper() 6275 6276 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6277 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6278 6279 if dow_from_week_start_day != dow_from_week_offset: 6280 self.unsupported( 6281 f"WEEK({this}) is not supported; falling back to the default week start day" 6282 ) 6283 6284 return "WEEK" 6285 6286 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6287 name = self.weekstart_name(expression) 6288 6289 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6290 if isinstance(expression.parent, exp.DateTrunc): 6291 return self.sql(exp.Literal.string(name)) 6292 6293 return name 6294 6295 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6296 this = self.expressions(expression) 6297 charset = self.sql(expression, "charset") 6298 using = f" USING {charset}" if charset else "" 6299 return self.func(name, this + using) 6300 6301 def block_sql(self, expression: exp.Block) -> str: 6302 expressions = self.expressions(expression, sep="; ", flat=True) 6303 begin = "BEGIN " if expression.args.get("begin") else "" 6304 return f"{begin}{expressions}" if expressions else "" 6305 6306 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6307 self.unsupported("Unsupported Inline UDFs syntax") 6308 return "" 6309 6310 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6311 self.unsupported("Unsupported Stored Procedure syntax") 6312 return "" 6313 6314 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6315 self.unsupported("Unsupported If block syntax") 6316 return "" 6317 6318 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6319 self.unsupported("Unsupported Case statement syntax") 6320 return "" 6321 6322 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6323 self.unsupported("Unsupported While block syntax") 6324 return "" 6325 6326 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6327 self.unsupported("Unsupported Loop block syntax") 6328 return "" 6329 6330 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6331 self.unsupported("Unsupported Repeat block syntax") 6332 return "" 6333 6334 def leave_sql(self, expression: exp.Leave) -> str: 6335 self.unsupported("Unsupported Leave syntax") 6336 return "" 6337 6338 def iterate_sql(self, expression: exp.Iterate) -> str: 6339 self.unsupported("Unsupported Iterate syntax") 6340 return "" 6341 6342 def execute_sql(self, expression: exp.Execute) -> str: 6343 self.unsupported("Unsupported Execute syntax") 6344 return "" 6345 6346 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6347 self.unsupported("Unsupported Execute syntax") 6348 return "" 6349 6350 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6351 props = self.expressions(expression, sep=" ") 6352 return f"MODIFY {props}" 6353 6354 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6355 kind = expression.args.get("kind") 6356 return f"USING {kind} {self.sql(expression, 'this')}" 6357 6358 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6359 this = self.sql(expression, "this") 6360 to = self.sql(expression, "to") 6361 return f"RENAME INDEX {this} TO {to}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: str | tuple[str, str]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
33def unsupported_args( 34 *args: str | tuple[str, str], 35) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 36 """ 37 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 38 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 39 """ 40 diagnostic_by_arg: dict[str, str | None] = {} 41 for arg in args: 42 if isinstance(arg, str): 43 diagnostic_by_arg[arg] = None 44 else: 45 diagnostic_by_arg[arg[0]] = arg[1] 46 47 def decorator(func: GeneratorMethod) -> GeneratorMethod: 48 @wraps(func) 49 def _func(generator: G, expression: E) -> str: 50 expression_name = expression.__class__.__name__ 51 dialect_name = generator.dialect.__class__.__name__ 52 53 for arg_name, diagnostic in diagnostic_by_arg.items(): 54 if expression.args.get(arg_name): 55 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 56 arg_name, expression_name, dialect_name 57 ) 58 generator.unsupported(diagnostic) 59 60 return func(generator, expression) 61 62 return _func 63 64 return decorator
Decorator that can be used to mark certain args of an Expr subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, typing.Any] =
{'windows': <function <lambda>>, 'qualify': <function <lambda>>}
class
Generator:
98class Generator: 99 """ 100 Generator converts a given syntax tree to the corresponding SQL string. 101 102 Args: 103 pretty: Whether to format the produced SQL string. 104 Default: False. 105 identify: Determines when an identifier should be quoted. Possible values are: 106 False (default): Never quote, except in cases where it's mandatory by the dialect. 107 True: Always quote except for specials cases. 108 'safe': Only quote identifiers that are case insensitive. 109 normalize: Whether to normalize identifiers to lowercase. 110 Default: False. 111 pad: The pad size in a formatted string. For example, this affects the indentation of 112 a projection in a query, relative to its nesting level. 113 Default: 2. 114 indent: The indentation size in a formatted string. For example, this affects the 115 indentation of subqueries and filters under a `WHERE` clause. 116 Default: 2. 117 normalize_functions: How to normalize function names. Possible values are: 118 "upper" or True (default): Convert names to uppercase. 119 "lower": Convert names to lowercase. 120 False: Disables function name normalization. 121 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 122 Default ErrorLevel.WARN. 123 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 124 This is only relevant if unsupported_level is ErrorLevel.RAISE. 125 Default: 3 126 leading_comma: Whether the comma is leading or trailing in select expressions. 127 This is only relevant when generating in pretty mode. 128 Default: False 129 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 130 The default is on the smaller end because the length only represents a segment and not the true 131 line length. 132 Default: 80 133 comments: Whether to preserve comments in the output SQL code. 134 Default: True 135 """ 136 137 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 138 **JSON_PATH_PART_TRANSFORMS, 139 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 140 exp.AllowedValuesProperty: lambda self, e: ( 141 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 142 ), 143 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 144 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 145 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 146 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 147 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 148 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 149 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 150 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 151 exp.CaseSpecificColumnConstraint: lambda _, e: ( 152 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 153 ), 154 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 155 exp.Ceil: lambda self, e: self.ceil_floor(e), 156 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 157 exp.CharacterSetProperty: lambda self, e: ( 158 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 159 ), 160 exp.ClusteredColumnConstraint: lambda self, e: ( 161 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 162 ), 163 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 164 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 165 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 166 exp.ConvertToCharset: lambda self, e: self.func( 167 "CONVERT", e.this, e.args["dest"], e.args.get("source") 168 ), 169 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 170 exp.CredentialsProperty: lambda self, e: ( 171 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 172 ), 173 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 174 exp.SessionUser: lambda *_: "SESSION_USER", 175 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 176 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 177 exp.ApiProperty: lambda *_: "API", 178 exp.ApplicationProperty: lambda *_: "APPLICATION", 179 exp.CatalogProperty: lambda *_: "CATALOG", 180 exp.ComputeProperty: lambda *_: "COMPUTE", 181 exp.DatabaseProperty: lambda *_: "DATABASE", 182 exp.DynamicProperty: lambda *_: "DYNAMIC", 183 exp.EmptyProperty: lambda *_: "EMPTY", 184 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 185 exp.EndStatement: lambda *_: "END", 186 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 187 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 188 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 189 exp.EphemeralColumnConstraint: lambda self, e: ( 190 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 191 ), 192 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 193 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 194 exp.Except: lambda self, e: self.set_operations(e), 195 exp.ExternalProperty: lambda *_: "EXTERNAL", 196 exp.Floor: lambda self, e: self.ceil_floor(e), 197 exp.Get: lambda self, e: self.get_put_sql(e), 198 exp.GlobalProperty: lambda *_: "GLOBAL", 199 exp.HeapProperty: lambda *_: "HEAP", 200 exp.HybridProperty: lambda *_: "HYBRID", 201 exp.IcebergProperty: lambda *_: "ICEBERG", 202 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 203 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 204 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 205 exp.Intersect: lambda self, e: self.set_operations(e), 206 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 207 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 208 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 209 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 210 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 211 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 212 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 213 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 214 exp.LanguageProperty: lambda self, e: self.naked_property(e), 215 exp.LocationProperty: lambda self, e: self.naked_property(e), 216 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 217 exp.MaskingProperty: lambda *_: "MASKING", 218 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 219 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 220 exp.NetworkProperty: lambda *_: "NETWORK", 221 exp.NonClusteredColumnConstraint: lambda self, e: ( 222 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 223 ), 224 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 225 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 226 exp.OnCommitProperty: lambda _, e: ( 227 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 228 ), 229 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 230 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 231 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 232 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 233 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 234 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 235 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 236 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 237 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 238 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 239 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 240 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 241 f"PROJECTION POLICY {self.sql(e, 'this')}" 242 ), 243 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 244 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 245 exp.Put: lambda self, e: self.get_put_sql(e), 246 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 247 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 248 ), 249 exp.ReturnsProperty: lambda self, e: ( 250 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 251 ), 252 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 253 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 254 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 255 exp.SecureProperty: lambda *_: "SECURE", 256 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 257 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 258 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 259 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 260 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 261 exp.SqlReadWriteProperty: lambda _, e: e.name, 262 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 263 exp.StabilityProperty: lambda _, e: e.name, 264 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 265 exp.StreamingTableProperty: lambda *_: "STREAMING", 266 exp.StrictProperty: lambda *_: "STRICT", 267 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 268 exp.TableColumn: lambda self, e: self.sql(e.this), 269 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 270 exp.TemporaryProperty: lambda *_: "TEMPORARY", 271 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 272 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 273 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 274 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 275 exp.TransientProperty: lambda *_: "TRANSIENT", 276 exp.VirtualProperty: lambda *_: "VIRTUAL", 277 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 278 exp.Union: lambda self, e: self.set_operations(e), 279 exp.UnloggedProperty: lambda *_: "UNLOGGED", 280 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 281 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 282 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 283 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 284 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 285 exp.UtcTimestamp: lambda self, e: self.sql( 286 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 287 ), 288 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 289 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 290 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 291 exp.VolatileProperty: lambda *_: "VOLATILE", 292 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 293 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 294 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 295 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 296 exp.ForceProperty: lambda *_: "FORCE", 297 } 298 299 # Whether null ordering is supported in order by 300 # True: Full Support, None: No support, False: No support for certain cases 301 # such as window specifications, aggregate functions etc 302 NULL_ORDERING_SUPPORTED: bool | None = True 303 304 # Window functions that support NULLS FIRST/LAST 305 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 306 307 # Whether ignore nulls is inside the agg or outside. 308 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 309 IGNORE_NULLS_IN_FUNC = False 310 311 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 312 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 313 IGNORE_NULLS_BEFORE_ORDER = True 314 315 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 316 LOCKING_READS_SUPPORTED = False 317 318 # Whether the EXCEPT and INTERSECT operations can return duplicates 319 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 320 321 # Wrap derived values in parens, usually standard but spark doesn't support it 322 WRAP_DERIVED_VALUES = True 323 324 # Whether create function uses an AS before the RETURN 325 CREATE_FUNCTION_RETURN_AS = True 326 327 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 328 MATCHED_BY_SOURCE = True 329 330 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 331 SUPPORTS_MERGE_WHERE = False 332 333 # Whether the INTERVAL expression works only with values like '1 day' 334 SINGLE_STRING_INTERVAL = False 335 336 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 337 INTERVAL_ALLOWS_PLURAL_FORM = True 338 339 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 340 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 341 AUTO_REFRESH_BARE_INTERVALS = False 342 343 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 344 LIMIT_FETCH = "ALL" 345 346 # Whether limit and fetch allows expresions or just limits 347 LIMIT_ONLY_LITERALS = False 348 349 # Whether a table is allowed to be renamed with a db 350 RENAME_TABLE_WITH_DB = True 351 352 # The separator for grouping sets and rollups 353 GROUPINGS_SEP = "," 354 355 # The string used for creating an index on a table 356 INDEX_ON = "ON" 357 358 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 359 INOUT_SEPARATOR = " " 360 361 # Whether join hints should be generated 362 JOIN_HINTS = True 363 364 # Whether directed joins are supported 365 DIRECTED_JOINS = False 366 367 # Whether table hints should be generated 368 TABLE_HINTS = True 369 370 # Whether query hints should be generated 371 QUERY_HINTS = True 372 373 # What kind of separator to use for query hints 374 QUERY_HINT_SEP = ", " 375 376 # Whether comparing against booleans (e.g. x IS TRUE) is supported 377 IS_BOOL_ALLOWED = True 378 379 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 380 DUPLICATE_KEY_UPDATE_WITH_SET = True 381 382 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 383 LIMIT_IS_TOP = False 384 385 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 386 RETURNING_END = True 387 388 # Whether to generate an unquoted value for EXTRACT's date part argument 389 EXTRACT_ALLOWS_QUOTES = True 390 391 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 392 TZ_TO_WITH_TIME_ZONE = False 393 394 # Whether the NVL2 function is supported 395 NVL2_SUPPORTED = True 396 397 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 398 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 399 400 # Whether VALUES statements can be used as derived tables. 401 # MySQL 5 and Redshift do not allow this, so when False, it will convert 402 # SELECT * VALUES into SELECT UNION 403 VALUES_AS_TABLE = True 404 405 # Whether the word COLUMN is included when adding a column with ALTER TABLE 406 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 407 408 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 409 UNNEST_WITH_ORDINALITY = True 410 411 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 412 SEMI_ANTI_JOIN_WITH_SIDE = True 413 414 # Whether to include the type of a computed column in the CREATE DDL 415 COMPUTED_COLUMN_WITH_TYPE = True 416 417 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 418 SUPPORTS_TABLE_COPY = True 419 420 # Whether parentheses are required around the table sample's expression 421 TABLESAMPLE_REQUIRES_PARENS = True 422 423 # Whether a table sample clause's size needs to be followed by the ROWS keyword 424 TABLESAMPLE_SIZE_IS_ROWS = True 425 426 # The keyword(s) to use when generating a sample clause 427 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 428 429 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 430 TABLESAMPLE_WITH_METHOD = True 431 432 # The keyword to use when specifying the seed of a sample clause 433 TABLESAMPLE_SEED_KEYWORD = "SEED" 434 435 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 436 HISTORICAL_DATA_POST_ALIAS = False 437 438 # Whether COLLATE is a function instead of a binary operator 439 COLLATE_IS_FUNC = False 440 441 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 442 DATA_TYPE_SPECIFIERS_ALLOWED = False 443 444 # Whether conditions require booleans WHERE x = 0 vs WHERE x 445 ENSURE_BOOLS = False 446 447 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 448 CTE_RECURSIVE_KEYWORD_REQUIRED = True 449 450 # Whether CONCAT requires >1 arguments 451 SUPPORTS_SINGLE_ARG_CONCAT = True 452 453 # Whether LAST_DAY function supports a date part argument 454 LAST_DAY_SUPPORTS_DATE_PART = True 455 456 # Whether named columns are allowed in table aliases 457 SUPPORTS_TABLE_ALIAS_COLUMNS = True 458 459 # Whether named columns are allowed in CTE definitions 460 SUPPORTS_NAMED_CTE_COLUMNS = True 461 462 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 463 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 464 465 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 466 PIVOT_ALIAS_WITH_AS = True 467 468 # What delimiter to use for separating JSON key/value pairs 469 JSON_KEY_VALUE_PAIR_SEP = ":" 470 471 # INSERT OVERWRITE TABLE x override 472 INSERT_OVERWRITE = " OVERWRITE TABLE" 473 474 # Whether the SELECT .. INTO syntax is used instead of CTAS 475 SUPPORTS_SELECT_INTO = False 476 477 # Whether UNLOGGED tables can be created 478 SUPPORTS_UNLOGGED_TABLES = False 479 480 # Whether the CREATE TABLE LIKE statement is supported 481 SUPPORTS_CREATE_TABLE_LIKE = True 482 483 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 484 SUPPORTS_MODIFY_COLUMN = False 485 486 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 487 SUPPORTS_CHANGE_COLUMN = False 488 489 # Whether ALTER COLUMN can set a column's nullability together with its type 490 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 491 492 # Whether ALTER COLUMN IF EXISTS is supported 493 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 494 495 # Whether the LikeProperty needs to be specified inside of the schema clause 496 LIKE_PROPERTY_INSIDE_SCHEMA = False 497 498 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 499 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 500 MULTI_ARG_DISTINCT = True 501 502 # Whether the JSON extraction operators expect a value of type JSON 503 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 504 505 # Whether bracketed keys like ["foo"] are supported in JSON paths 506 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 507 508 # Whether to escape keys using single quotes in JSON paths 509 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 510 511 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 512 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 513 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 514 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 515 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 516 517 # The JSONPathPart expressions supported by this dialect 518 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 519 520 # Whether any(f(x) for x in array) can be implemented by this dialect 521 CAN_IMPLEMENT_ARRAY_ANY = False 522 523 # Whether the function TO_NUMBER is supported 524 SUPPORTS_TO_NUMBER = True 525 526 # Whether EXCLUDE in window specification is supported 527 SUPPORTS_WINDOW_EXCLUDE = False 528 529 # Whether or not set op modifiers apply to the outer set op or select. 530 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 531 # True means limit 1 happens after the set op, False means it it happens on y. 532 SET_OP_MODIFIERS = True 533 534 # Whether parameters from COPY statement are wrapped in parentheses 535 COPY_PARAMS_ARE_WRAPPED = True 536 537 # Whether values of params are set with "=" token or empty space 538 COPY_PARAMS_EQ_REQUIRED = False 539 540 # Whether COPY statement has INTO keyword 541 COPY_HAS_INTO_KEYWORD = True 542 543 # Whether the conditional TRY(expression) function is supported 544 TRY_SUPPORTED = True 545 546 # Whether the UESCAPE syntax in unicode strings is supported 547 SUPPORTS_UESCAPE = True 548 549 # Function used to replace escaped unicode codes in unicode strings 550 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 551 552 # The keyword to use when generating a star projection with excluded columns 553 STAR_EXCEPT = "EXCEPT" 554 555 # The HEX function name 556 HEX_FUNC = "HEX" 557 558 # The keywords to use when prefixing & separating WITH based properties 559 WITH_PROPERTIES_PREFIX = "WITH" 560 561 # Whether to quote the generated expression of exp.JsonPath 562 QUOTE_JSON_PATH = True 563 564 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 565 PAD_FILL_PATTERN_IS_REQUIRED = False 566 567 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 568 SUPPORTS_EXPLODING_PROJECTIONS = True 569 570 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 571 ARRAY_CONCAT_IS_VAR_LEN = True 572 573 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 574 SUPPORTS_CONVERT_TIMEZONE = False 575 576 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 577 SUPPORTS_MEDIAN = True 578 579 # Whether UNIX_SECONDS(timestamp) is supported 580 SUPPORTS_UNIX_SECONDS = False 581 582 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 583 ALTER_SET_WRAPPED = False 584 585 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 586 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 587 # TODO: The normalization should be done by default once we've tested it across all dialects. 588 NORMALIZE_EXTRACT_DATE_PARTS = False 589 590 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 591 PARSE_JSON_NAME: str | None = "PARSE_JSON" 592 593 # The function name of the exp.ArraySize expression 594 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 595 596 # The syntax to use when altering the type of a column 597 ALTER_SET_TYPE = "SET DATA TYPE" 598 599 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 600 # None -> Doesn't support it at all 601 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 602 # True (Postgres) -> Explicitly requires it 603 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 604 605 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 606 SUPPORTS_DECODE_CASE = True 607 608 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 609 SUPPORTS_BETWEEN_FLAGS = False 610 611 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 612 SUPPORTS_LIKE_QUANTIFIERS = True 613 614 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 615 MATCH_AGAINST_TABLE_PREFIX: str | None = None 616 617 # Whether to include the VARIABLE keyword for SET assignments 618 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 619 620 # The keyword to use for default value assignment in DECLARE statements 621 DECLARE_DEFAULT_ASSIGNMENT = "=" 622 623 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 624 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 625 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 626 UPDATE_STATEMENT_SUPPORTS_FROM = True 627 628 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 629 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 630 631 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 632 # - Snowflake: DROP ICEBERG TABLE a.b; 633 # - DuckDB: DROP TABLE a.b; 634 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 635 636 TYPE_MAPPING: t.ClassVar = { 637 exp.DType.DATETIME2: "TIMESTAMP", 638 exp.DType.NCHAR: "CHAR", 639 exp.DType.NVARCHAR: "VARCHAR", 640 exp.DType.MEDIUMTEXT: "TEXT", 641 exp.DType.LONGTEXT: "TEXT", 642 exp.DType.TINYTEXT: "TEXT", 643 exp.DType.BLOB: "VARBINARY", 644 exp.DType.MEDIUMBLOB: "BLOB", 645 exp.DType.LONGBLOB: "BLOB", 646 exp.DType.TINYBLOB: "BLOB", 647 exp.DType.INET: "INET", 648 exp.DType.ROWVERSION: "VARBINARY", 649 exp.DType.SMALLDATETIME: "TIMESTAMP", 650 } 651 652 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 653 654 # mapping of DType to its default parameters, bounds 655 TYPE_PARAM_SETTINGS: t.ClassVar[ 656 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 657 ] = {} 658 659 TIME_PART_SINGULARS: t.ClassVar = { 660 "MICROSECONDS": "MICROSECOND", 661 "SECONDS": "SECOND", 662 "MINUTES": "MINUTE", 663 "HOURS": "HOUR", 664 "DAYS": "DAY", 665 "WEEKS": "WEEK", 666 "MONTHS": "MONTH", 667 "QUARTERS": "QUARTER", 668 "YEARS": "YEAR", 669 } 670 671 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 672 "cluster": lambda self, e: self.sql(e, "cluster"), 673 "distribute": lambda self, e: self.sql(e, "distribute"), 674 "sort": lambda self, e: self.sql(e, "sort"), 675 **AFTER_HAVING_MODIFIER_TRANSFORMS, 676 } 677 678 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 679 680 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 681 682 PARAMETER_TOKEN = "@" 683 NAMED_PLACEHOLDER_TOKEN = ":" 684 685 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 686 687 PROPERTIES_LOCATION: t.ClassVar = { 688 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 689 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 690 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 691 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 692 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 696 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 698 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 700 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 701 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 702 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 704 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 705 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 709 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 710 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 711 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 712 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 713 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 715 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 719 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 725 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 726 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 727 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 728 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 729 exp.HeapProperty: exp.Properties.Location.POST_WITH, 730 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 731 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 733 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 736 exp.JournalProperty: exp.Properties.Location.POST_NAME, 737 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 742 exp.LogProperty: exp.Properties.Location.POST_NAME, 743 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 744 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 745 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 746 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 748 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 749 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 751 exp.Order: exp.Properties.Location.POST_SCHEMA, 752 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 754 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 756 exp.Property: exp.Properties.Location.POST_WITH, 757 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 761 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 762 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 768 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 769 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 770 exp.Set: exp.Properties.Location.POST_SCHEMA, 771 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.SetProperty: exp.Properties.Location.POST_CREATE, 773 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 775 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 776 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 777 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 782 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 783 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 784 exp.Tags: exp.Properties.Location.POST_WITH, 785 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 786 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 788 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 790 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 791 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 792 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 795 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 796 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 797 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 798 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 799 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 801 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 802 } 803 804 # Keywords that can't be used as unquoted identifier names 805 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 806 807 # Exprs whose comments are separated from them for better formatting 808 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 809 exp.Command, 810 exp.Create, 811 exp.Describe, 812 exp.Delete, 813 exp.Drop, 814 exp.From, 815 exp.Insert, 816 exp.Join, 817 exp.MultitableInserts, 818 exp.Order, 819 exp.Group, 820 exp.Having, 821 exp.Select, 822 exp.SetOperation, 823 exp.Update, 824 exp.Where, 825 exp.With, 826 ) 827 828 # Exprs that should not have their comments generated in maybe_comment 829 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 830 exp.Binary, 831 exp.SetOperation, 832 ) 833 834 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 835 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 836 exp.Column, 837 exp.Literal, 838 exp.Neg, 839 exp.Paren, 840 ) 841 842 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 843 exp.DType.NVARCHAR, 844 exp.DType.VARCHAR, 845 exp.DType.CHAR, 846 exp.DType.NCHAR, 847 } 848 849 # Exprs that need to have all CTEs under them bubbled up to them 850 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 851 852 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 853 854 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 855 856 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 857 858 __slots__ = ( 859 "pretty", 860 "identify", 861 "normalize", 862 "pad", 863 "_indent", 864 "normalize_functions", 865 "unsupported_level", 866 "max_unsupported", 867 "leading_comma", 868 "max_text_width", 869 "comments", 870 "dialect", 871 "unsupported_messages", 872 "_escaped_quote_end", 873 "_escaped_byte_quote_end", 874 "_escaped_identifier_end", 875 "_next_name", 876 "_identifier_start", 877 "_identifier_end", 878 "_quote_json_path_key_using_brackets", 879 "_dispatch", 880 ) 881 882 def __init__( 883 self, 884 pretty: bool | int | None = None, 885 identify: str | bool = False, 886 normalize: bool = False, 887 pad: int = 2, 888 indent: int = 2, 889 normalize_functions: str | bool | None = None, 890 unsupported_level: ErrorLevel = ErrorLevel.WARN, 891 max_unsupported: int = 3, 892 leading_comma: bool = False, 893 max_text_width: int = 80, 894 comments: bool = True, 895 dialect: DialectType = None, 896 ): 897 import sqlglot 898 import sqlglot.dialects.dialect 899 900 self.pretty = pretty if pretty is not None else sqlglot.pretty 901 self.identify = identify 902 self.normalize = normalize 903 self.pad = pad 904 self._indent = indent 905 self.unsupported_level = unsupported_level 906 self.max_unsupported = max_unsupported 907 self.leading_comma = leading_comma 908 self.max_text_width = max_text_width 909 self.comments = comments 910 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 911 912 # This is both a Dialect property and a Generator argument, so we prioritize the latter 913 self.normalize_functions = ( 914 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 915 ) 916 917 self.unsupported_messages: list[str] = [] 918 self._escaped_quote_end: str = ( 919 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 920 ) 921 self._escaped_byte_quote_end: str = ( 922 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 923 if self.dialect.BYTE_END 924 else "" 925 ) 926 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 927 928 self._next_name = name_sequence("_t") 929 930 self._identifier_start = self.dialect.IDENTIFIER_START 931 self._identifier_end = self.dialect.IDENTIFIER_END 932 933 self._quote_json_path_key_using_brackets = True 934 935 cls = type(self) 936 dispatch = _DISPATCH_CACHE.get(cls) 937 if dispatch is None: 938 dispatch = _build_dispatch(cls) 939 _DISPATCH_CACHE[cls] = dispatch 940 self._dispatch = dispatch 941 942 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 943 """ 944 Generates the SQL string corresponding to the given syntax tree. 945 946 Args: 947 expression: The syntax tree. 948 copy: Whether to copy the expression. The generator performs mutations so 949 it is safer to copy. 950 951 Returns: 952 The SQL string corresponding to `expression`. 953 """ 954 if copy: 955 expression = expression.copy() 956 957 expression = self.preprocess(expression) 958 959 self.unsupported_messages = [] 960 sql = self.sql(expression).strip() 961 962 if self.pretty: 963 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 964 965 if self.unsupported_level == ErrorLevel.IGNORE: 966 return sql 967 968 if self.unsupported_level == ErrorLevel.WARN: 969 for msg in self.unsupported_messages: 970 logger.warning(msg) 971 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 972 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 973 974 return sql 975 976 def preprocess(self, expression: exp.Expr) -> exp.Expr: 977 """Apply generic preprocessing transformations to a given expression.""" 978 expression = self._move_ctes_to_top_level(expression) 979 980 if self.ENSURE_BOOLS: 981 import sqlglot.transforms 982 983 expression = sqlglot.transforms.ensure_bools(expression) 984 985 return expression 986 987 def _move_ctes_to_top_level(self, expression: E) -> E: 988 if ( 989 not expression.parent 990 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 991 and any(node.parent is not expression for node in expression.find_all(exp.With)) 992 ): 993 import sqlglot.transforms 994 995 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 996 return expression 997 998 def unsupported(self, message: str) -> None: 999 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1000 raise UnsupportedError(message) 1001 self.unsupported_messages.append(message) 1002 1003 def sep(self, sep: str = " ") -> str: 1004 return f"{sep.strip()}\n" if self.pretty else sep 1005 1006 def seg(self, sql: str, sep: str = " ") -> str: 1007 return f"{self.sep(sep)}{sql}" 1008 1009 def sanitize_comment(self, comment: str) -> str: 1010 comment = " " + comment if comment[0].strip() else comment 1011 comment = comment + " " if comment[-1].strip() else comment 1012 1013 # Escape block comment markers to prevent premature closure or unintended nesting. 1014 # This is necessary because single-line comments (--) are converted to block comments 1015 # (/* */) on output, and any */ in the original text would close the comment early. 1016 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1017 1018 return comment 1019 1020 def maybe_comment( 1021 self, 1022 sql: str, 1023 expression: exp.Expr | None = None, 1024 comments: list[str] | None = None, 1025 separated: bool = False, 1026 ) -> str: 1027 comments = ( 1028 ((expression and expression.comments) if comments is None else comments) # type: ignore 1029 if self.comments 1030 else None 1031 ) 1032 1033 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1034 return sql 1035 1036 comments_list = [ 1037 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1038 for comment in comments 1039 if comment 1040 ] 1041 1042 if not comments_list: 1043 return sql 1044 1045 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1046 comments_sql = self.sep().join(comments_list) 1047 return ( 1048 f"{self.sep()}{comments_sql}{sql}" 1049 if not sql or sql[0].isspace() 1050 else f"{comments_sql}{self.sep()}{sql}" 1051 ) 1052 1053 return f"{sql} {' '.join(comments_list)}" 1054 1055 def wrap(self, expression: exp.Expr | str) -> str: 1056 this_sql = ( 1057 self.sql(expression) 1058 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1059 else self.sql(expression, "this") 1060 ) 1061 if not this_sql: 1062 return "()" 1063 1064 this_sql = self.indent(this_sql, level=1, pad=0) 1065 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1066 1067 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1068 original = self.identify 1069 self.identify = False 1070 result = func(*args, **kwargs) 1071 self.identify = original 1072 return result 1073 1074 def normalize_func(self, name: str) -> str: 1075 if self.normalize_functions == "upper" or self.normalize_functions is True: 1076 return name.upper() 1077 if self.normalize_functions == "lower": 1078 return name.lower() 1079 return name 1080 1081 def indent( 1082 self, 1083 sql: str, 1084 level: int = 0, 1085 pad: int | None = None, 1086 skip_first: bool = False, 1087 skip_last: bool = False, 1088 ) -> str: 1089 if not self.pretty or not sql: 1090 return sql 1091 1092 pad = self.pad if pad is None else pad 1093 lines = sql.split("\n") 1094 1095 return "\n".join( 1096 ( 1097 line 1098 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1099 else f"{' ' * (level * self._indent + pad)}{line}" 1100 ) 1101 for i, line in enumerate(lines) 1102 ) 1103 1104 def sql( 1105 self, 1106 expression: str | exp.Expr | None, 1107 key: str | None = None, 1108 comment: bool = True, 1109 ) -> str: 1110 if not expression: 1111 return "" 1112 1113 if isinstance(expression, str): 1114 return expression 1115 1116 if key: 1117 value = expression.args.get(key) 1118 if value: 1119 return self.sql(value) 1120 return "" 1121 1122 handler = self._dispatch.get(expression.__class__) 1123 1124 if handler: 1125 sql = handler(self, expression) 1126 elif isinstance(expression, exp.Func): 1127 sql = self.function_fallback_sql(expression) 1128 elif isinstance(expression, exp.Property): 1129 sql = self.property_sql(expression) 1130 else: 1131 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1132 1133 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1134 1135 def uncache_sql(self, expression: exp.Uncache) -> str: 1136 table = self.sql(expression, "this") 1137 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1138 return f"UNCACHE TABLE{exists_sql} {table}" 1139 1140 def cache_sql(self, expression: exp.Cache) -> str: 1141 lazy = " LAZY" if expression.args.get("lazy") else "" 1142 table = self.sql(expression, "this") 1143 options = expression.args.get("options") 1144 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1145 sql = self.sql(expression, "expression") 1146 sql = f" AS{self.sep()}{sql}" if sql else "" 1147 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1148 return self.prepend_ctes(expression, sql) 1149 1150 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1151 default = "DEFAULT " if expression.args.get("default") else "" 1152 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1153 1154 def column_parts(self, expression: exp.Column) -> str: 1155 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1156 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1157 return self.sql(expression, "this") 1158 1159 return ".".join( 1160 self.sql(part) 1161 for part in ( 1162 expression.args.get("catalog"), 1163 expression.args.get("db"), 1164 expression.args.get("table"), 1165 expression.args.get("this"), 1166 ) 1167 if part 1168 ) 1169 1170 def column_sql(self, expression: exp.Column) -> str: 1171 join_mark = " (+)" if expression.args.get("join_mark") else "" 1172 1173 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1174 join_mark = "" 1175 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1176 1177 return f"{self.column_parts(expression)}{join_mark}" 1178 1179 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1180 return self.column_sql(expression) 1181 1182 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1183 this = self.sql(expression, "this") 1184 this = f" {this}" if this else "" 1185 position = self.sql(expression, "position") 1186 return f"{position}{this}" 1187 1188 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1189 column = self.sql(expression, "this") 1190 kind = self.sql(expression, "kind") 1191 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1192 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1193 kind = f"{sep}{kind}" if kind else "" 1194 constraints = f" {constraints}" if constraints else "" 1195 position = self.sql(expression, "position") 1196 position = f" {position}" if position else "" 1197 1198 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1199 kind = "" 1200 1201 return f"{exists}{column}{kind}{constraints}{position}" 1202 1203 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1204 this = self.sql(expression, "this") 1205 kind_sql = self.sql(expression, "kind").strip() 1206 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1207 1208 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1209 this = self.sql(expression, "this") 1210 if expression.args.get("not_null"): 1211 persisted = " PERSISTED NOT NULL" 1212 elif expression.args.get("persisted"): 1213 persisted = " PERSISTED" 1214 else: 1215 persisted = "" 1216 1217 return f"AS {this}{persisted}" 1218 1219 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1220 return self.token_sql(TokenType.AUTO_INCREMENT) 1221 1222 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1223 if isinstance(expression.this, list): 1224 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1225 else: 1226 this = self.sql(expression, "this") 1227 1228 return f"COMPRESS {this}" 1229 1230 def generatedasidentitycolumnconstraint_sql( 1231 self, expression: exp.GeneratedAsIdentityColumnConstraint 1232 ) -> str: 1233 this = "" 1234 if expression.this is not None: 1235 on_null = " ON NULL" if expression.args.get("on_null") else "" 1236 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1237 1238 start = expression.args.get("start") 1239 start = f"START WITH {start}" if start else "" 1240 increment = expression.args.get("increment") 1241 increment = f" INCREMENT BY {increment}" if increment else "" 1242 minvalue = expression.args.get("minvalue") 1243 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1244 maxvalue = expression.args.get("maxvalue") 1245 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1246 cycle = expression.args.get("cycle") 1247 cycle_sql = "" 1248 1249 if cycle is not None: 1250 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1251 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1252 1253 sequence_opts = "" 1254 if start or increment or cycle_sql: 1255 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1256 sequence_opts = f" ({sequence_opts.strip()})" 1257 1258 expr = self.sql(expression, "expression") 1259 expr = f"({expr})" if expr else "IDENTITY" 1260 1261 return f"GENERATED{this} AS {expr}{sequence_opts}" 1262 1263 def generatedasrowcolumnconstraint_sql( 1264 self, expression: exp.GeneratedAsRowColumnConstraint 1265 ) -> str: 1266 start = "START" if expression.args.get("start") else "END" 1267 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1268 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1269 1270 def periodforsystemtimeconstraint_sql( 1271 self, expression: exp.PeriodForSystemTimeConstraint 1272 ) -> str: 1273 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1274 1275 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1276 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1277 1278 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1279 desc = expression.args.get("desc") 1280 if desc is not None: 1281 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1282 options = self.expressions(expression, key="options", flat=True, sep=" ") 1283 options = f" {options}" if options else "" 1284 return f"PRIMARY KEY{options}" 1285 1286 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1287 this = self.sql(expression, "this") 1288 this = f" {this}" if this else "" 1289 index_type = expression.args.get("index_type") 1290 index_type = f" USING {index_type}" if index_type else "" 1291 on_conflict = self.sql(expression, "on_conflict") 1292 on_conflict = f" {on_conflict}" if on_conflict else "" 1293 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1294 options = self.expressions(expression, key="options", flat=True, sep=" ") 1295 options = f" {options}" if options else "" 1296 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1297 1298 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1299 input_ = expression.args.get("input_") 1300 output = expression.args.get("output") 1301 variadic = expression.args.get("variadic") 1302 1303 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1304 if variadic: 1305 return "VARIADIC" 1306 1307 if input_ and output: 1308 return f"IN{self.INOUT_SEPARATOR}OUT" 1309 if input_: 1310 return "IN" 1311 if output: 1312 return "OUT" 1313 1314 return "" 1315 1316 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1317 return self.sql(expression, "this") 1318 1319 def create_sql(self, expression: exp.Create) -> str: 1320 kind = self.sql(expression, "kind") 1321 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1322 1323 properties = expression.args.get("properties") 1324 1325 if ( 1326 kind == "TRIGGER" 1327 and properties 1328 and properties.expressions 1329 and isinstance(properties.expressions[0], exp.TriggerProperties) 1330 and properties.expressions[0].args.get("constraint") 1331 ): 1332 kind = f"CONSTRAINT {kind}" 1333 1334 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1335 1336 this = self.createable_sql(expression, properties_locs) 1337 1338 properties_sql = "" 1339 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1340 exp.Properties.Location.POST_WITH 1341 ): 1342 props_ast = exp.Properties( 1343 expressions=[ 1344 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1345 *properties_locs[exp.Properties.Location.POST_WITH], 1346 ] 1347 ) 1348 props_ast.parent = expression 1349 properties_sql = self.sql(props_ast) 1350 1351 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1352 properties_sql = self.sep() + properties_sql 1353 elif not self.pretty: 1354 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1355 properties_sql = f" {properties_sql}" 1356 1357 begin = " BEGIN" if expression.args.get("begin") else "" 1358 1359 expression_sql = self.sql(expression, "expression") 1360 if expression_sql: 1361 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1362 1363 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1364 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1365 ): 1366 postalias_props_sql = "" 1367 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1368 postalias_props_sql = self.properties( 1369 exp.Properties( 1370 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1371 ), 1372 wrapped=False, 1373 ) 1374 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1375 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1376 1377 postindex_props_sql = "" 1378 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1379 postindex_props_sql = self.properties( 1380 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1381 wrapped=False, 1382 prefix=" ", 1383 ) 1384 1385 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1386 indexes = f" {indexes}" if indexes else "" 1387 index_sql = indexes + postindex_props_sql 1388 1389 replace = " OR REPLACE" if expression.args.get("replace") else "" 1390 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1391 unique = " UNIQUE" if expression.args.get("unique") else "" 1392 1393 clustered = expression.args.get("clustered") 1394 if clustered is None: 1395 clustered_sql = "" 1396 elif clustered: 1397 clustered_sql = " CLUSTERED COLUMNSTORE" 1398 else: 1399 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1400 1401 postcreate_props_sql = "" 1402 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1403 postcreate_props_sql = self.properties( 1404 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1405 sep=" ", 1406 prefix=" ", 1407 wrapped=False, 1408 ) 1409 1410 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1411 1412 postexpression_props_sql = "" 1413 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1414 postexpression_props_sql = self.properties( 1415 exp.Properties( 1416 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1417 ), 1418 sep=" ", 1419 prefix=" ", 1420 wrapped=False, 1421 ) 1422 1423 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1424 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1425 no_schema_binding = ( 1426 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1427 ) 1428 1429 clone = self.sql(expression, "clone") 1430 clone = f" {clone}" if clone else "" 1431 1432 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1433 properties_expression = f"{expression_sql}{properties_sql}" 1434 else: 1435 properties_expression = f"{properties_sql}{expression_sql}" 1436 1437 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1438 return self.prepend_ctes(expression, expression_sql) 1439 1440 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1441 start = self.sql(expression, "start") 1442 start = f"START WITH {start}" if start else "" 1443 increment = self.sql(expression, "increment") 1444 increment = f" INCREMENT BY {increment}" if increment else "" 1445 minvalue = self.sql(expression, "minvalue") 1446 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1447 maxvalue = self.sql(expression, "maxvalue") 1448 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1449 owned = self.sql(expression, "owned") 1450 owned = f" OWNED BY {owned}" if owned else "" 1451 1452 cache = expression.args.get("cache") 1453 if cache is None: 1454 cache_str = "" 1455 elif cache is True: 1456 cache_str = " CACHE" 1457 else: 1458 cache_str = f" CACHE {cache}" 1459 1460 options = self.expressions(expression, key="options", flat=True, sep=" ") 1461 options = f" {options}" if options else "" 1462 1463 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1464 1465 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1466 timing = expression.args.get("timing", "") 1467 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1468 timing_events = f"{timing} {events}".strip() if timing or events else "" 1469 1470 parts = [timing_events, "ON", self.sql(expression, "table")] 1471 1472 if referenced_table := expression.args.get("referenced_table"): 1473 parts.extend(["FROM", self.sql(referenced_table)]) 1474 1475 if deferrable := expression.args.get("deferrable"): 1476 parts.append(deferrable) 1477 1478 if initially := expression.args.get("initially"): 1479 parts.append(f"INITIALLY {initially}") 1480 1481 if referencing := expression.args.get("referencing"): 1482 parts.append(self.sql(referencing)) 1483 1484 if for_each := expression.args.get("for_each"): 1485 parts.append(f"FOR EACH {for_each}") 1486 1487 if when := expression.args.get("when"): 1488 parts.append(f"WHEN ({self.sql(when)})") 1489 1490 parts.append(self.sql(expression, "execute")) 1491 1492 return self.sep().join(parts) 1493 1494 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1495 parts = [] 1496 1497 if old_alias := expression.args.get("old"): 1498 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1499 1500 if new_alias := expression.args.get("new"): 1501 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1502 1503 return f"REFERENCING {' '.join(parts)}" 1504 1505 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1506 columns = expression.args.get("columns") 1507 if columns: 1508 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1509 1510 return self.sql(expression, "this") 1511 1512 def clone_sql(self, expression: exp.Clone) -> str: 1513 this = self.sql(expression, "this") 1514 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1515 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1516 return f"{shallow}{keyword} {this}" 1517 1518 def describe_sql(self, expression: exp.Describe) -> str: 1519 style = expression.args.get("style") 1520 style = f" {style}" if style else "" 1521 partition = self.sql(expression, "partition") 1522 partition = f" {partition}" if partition else "" 1523 format = self.sql(expression, "format") 1524 format = f" {format}" if format else "" 1525 as_json = " AS JSON" if expression.args.get("as_json") else "" 1526 1527 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1528 1529 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1530 tag = self.sql(expression, "tag") 1531 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1532 1533 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1534 with_ = self.sql(expression, "with_") 1535 if with_: 1536 sql = f"{with_}{self.sep()}{sql}" 1537 return sql 1538 1539 def with_sql(self, expression: exp.With) -> str: 1540 udfs = self.expressions(expression, key="udfs", flat=True) 1541 udfs = f"WITH {udfs}" if udfs else "" 1542 1543 sql = self.expressions(expression, flat=True) 1544 1545 recursive = ( 1546 "RECURSIVE " 1547 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1548 else "" 1549 ) 1550 search = self.sql(expression, "search") 1551 search = f" {search}" if search else "" 1552 1553 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1554 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1555 1556 def cte_sql(self, expression: exp.CTE) -> str: 1557 alias = expression.args.get("alias") 1558 if alias: 1559 alias.add_comments(expression.pop_comments()) 1560 1561 alias_sql = self.sql(expression, "alias") 1562 1563 materialized = expression.args.get("materialized") 1564 if materialized is False: 1565 materialized = "NOT MATERIALIZED " 1566 elif materialized: 1567 materialized = "MATERIALIZED " 1568 1569 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1570 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1571 1572 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1573 1574 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1575 alias = self.sql(expression, "this") 1576 columns = self.expressions(expression, key="columns", flat=True) 1577 columns = f"({columns})" if columns else "" 1578 1579 if ( 1580 columns 1581 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1582 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1583 ): 1584 columns = "" 1585 self.unsupported("Named columns are not supported in table alias.") 1586 1587 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1588 alias = self._next_name() 1589 1590 return f"{alias}{columns}" 1591 1592 def bitstring_sql(self, expression: exp.BitString) -> str: 1593 this = self.sql(expression, "this") 1594 if self.dialect.BIT_START: 1595 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1596 return f"{int(this, 2)}" 1597 1598 def hexstring_sql( 1599 self, expression: exp.HexString, binary_function_repr: str | None = None 1600 ) -> str: 1601 this = self.sql(expression, "this") 1602 is_integer_type = expression.args.get("is_integer") 1603 1604 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1605 not self.dialect.HEX_START and not binary_function_repr 1606 ): 1607 # Integer representation will be returned if: 1608 # - The read dialect treats the hex value as integer literal but not the write 1609 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1610 return f"{int(this, 16)}" 1611 1612 if not is_integer_type: 1613 # Read dialect treats the hex value as BINARY/BLOB 1614 if binary_function_repr: 1615 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1616 return self.func(binary_function_repr, exp.Literal.string(this)) 1617 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1618 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1619 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1620 1621 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1622 1623 def bytestring_sql(self, expression: exp.ByteString) -> str: 1624 this = self.sql(expression, "this") 1625 if self.dialect.BYTE_START: 1626 escaped_byte_string = self.escape_str( 1627 this, 1628 escape_backslash=False, 1629 delimiter=self.dialect.BYTE_END, 1630 escaped_delimiter=self._escaped_byte_quote_end, 1631 is_byte_string=True, 1632 ) 1633 is_bytes = expression.args.get("is_bytes", False) 1634 delimited_byte_string = ( 1635 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1636 ) 1637 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1638 return self.sql( 1639 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1640 ) 1641 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1642 return self.sql( 1643 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1644 ) 1645 1646 return delimited_byte_string 1647 1648 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1649 return self.sql(exp.Literal.string(this)) 1650 1651 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1652 return "" 1653 1654 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1655 this = self.sql(expression, "this") 1656 escape = expression.args.get("escape") 1657 unicode_start = self.dialect.UNICODE_START 1658 1659 if unicode_start: 1660 escape_substitute = r"\\\1" 1661 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1662 else: 1663 escape_substitute = r"\\u\1" 1664 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1665 1666 if escape: 1667 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1668 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1669 else: 1670 escape_pattern = ESCAPED_UNICODE_RE 1671 escape_sql = "" 1672 1673 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1674 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1675 1676 if unicode_start: 1677 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1678 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1679 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1680 else: 1681 this = self.escape_str(this, escape_backslash=False) 1682 1683 return f"{left_quote}{this}{right_quote}{escape_sql}" 1684 1685 def rawstring_sql(self, expression: exp.RawString) -> str: 1686 string = expression.this 1687 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1688 string = string.replace("\\", "\\\\") 1689 1690 string = self.escape_str(string, escape_backslash=False) 1691 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1692 1693 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1694 this = self.sql(expression, "this") 1695 specifier = self.sql(expression, "expression") 1696 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1697 return f"{this}{specifier}" 1698 1699 def datatype_param_bound_limiter( 1700 self, 1701 expression: exp.DataType, 1702 type_value: exp.DType, 1703 defaults: tuple[int, ...], 1704 bounds: tuple[int | None, ...], 1705 ) -> exp.DataType: 1706 params = expression.expressions 1707 1708 if not params: 1709 if defaults: 1710 expression.set( 1711 "expressions", 1712 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1713 ) 1714 return expression 1715 1716 if not bounds: 1717 return expression 1718 1719 for i, param in enumerate(params): 1720 bound = bounds[i] if i < len(bounds) else None 1721 if bound is None: 1722 continue 1723 1724 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1725 value = ( 1726 param_value.to_py() 1727 if isinstance(param_value, exp.Literal) and param_value.is_number 1728 else None 1729 ) 1730 if isinstance(value, (int, Decimal)) and value > bound: 1731 self.unsupported( 1732 f"{type_value.value} parameter {param_value.name} exceeds " 1733 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1734 ) 1735 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1736 1737 return expression 1738 1739 def datatype_sql(self, expression: exp.DataType) -> str: 1740 nested = "" 1741 values = "" 1742 1743 expr_nested = expression.args.get("nested") 1744 type_value = expression.this 1745 1746 if ( 1747 not expr_nested 1748 and isinstance(type_value, exp.DType) 1749 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1750 ): 1751 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1752 1753 interior = ( 1754 self.expressions( 1755 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1756 ) 1757 if expr_nested and self.pretty 1758 else self.expressions(expression, flat=True) 1759 ) 1760 1761 if type_value in self.UNSUPPORTED_TYPES: 1762 self.unsupported( 1763 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1764 ) 1765 1766 type_sql: t.Any = "" 1767 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1768 type_sql = self.sql(expression, "kind") 1769 elif type_value == exp.DType.CHARACTER_SET: 1770 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1771 else: 1772 type_sql = ( 1773 self.TYPE_MAPPING.get(type_value, type_value.value) 1774 if isinstance(type_value, exp.DType) 1775 else type_value 1776 ) 1777 1778 if interior: 1779 if expr_nested: 1780 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1781 if expression.args.get("values") is not None: 1782 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1783 values = self.expressions(expression, key="values", flat=True) 1784 values = f"{delimiters[0]}{values}{delimiters[1]}" 1785 elif type_value == exp.DType.INTERVAL: 1786 nested = f" {interior}" 1787 else: 1788 nested = f"({interior})" 1789 1790 type_sql = f"{type_sql}{nested}{values}" 1791 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1792 exp.DType.TIMETZ, 1793 exp.DType.TIMESTAMPTZ, 1794 ): 1795 type_sql = f"{type_sql} WITH TIME ZONE" 1796 1797 collate = self.sql(expression, "collate") 1798 if collate: 1799 type_sql = f"{type_sql} COLLATE {collate}" 1800 1801 return type_sql 1802 1803 def directory_sql(self, expression: exp.Directory) -> str: 1804 local = "LOCAL " if expression.args.get("local") else "" 1805 row_format = self.sql(expression, "row_format") 1806 row_format = f" {row_format}" if row_format else "" 1807 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1808 1809 def delete_sql(self, expression: exp.Delete) -> str: 1810 hint = self.sql(expression, "hint") 1811 this = self.sql(expression, "this") 1812 this = f" FROM {this}" if this else "" 1813 using = self.expressions(expression, key="using") 1814 using = f" USING {using}" if using else "" 1815 cluster = self.sql(expression, "cluster") 1816 cluster = f" {cluster}" if cluster else "" 1817 where = self.sql(expression, "where") 1818 returning = self.sql(expression, "returning") 1819 order = self.sql(expression, "order") 1820 limit = self.sql(expression, "limit") 1821 tables = self.expressions(expression, key="tables") 1822 tables = f" {tables}" if tables else "" 1823 if self.RETURNING_END: 1824 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1825 else: 1826 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1827 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1828 1829 def drop_sql(self, expression: exp.Drop) -> str: 1830 this = self.sql(expression, "this") 1831 expressions = self.expressions(expression, flat=True) 1832 expressions = f" ({expressions})" if expressions else "" 1833 kind = expression.args["kind"] 1834 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1835 iceberg = ( 1836 " ICEBERG" 1837 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1838 else "" 1839 ) 1840 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1841 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1842 on_cluster = self.sql(expression, "cluster") 1843 on_cluster = f" {on_cluster}" if on_cluster else "" 1844 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1845 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1846 cascade = " CASCADE" if expression.args.get("cascade") else "" 1847 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1848 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1849 purge = " PURGE" if expression.args.get("purge") else "" 1850 sync = " SYNC" if expression.args.get("sync") else "" 1851 force = " FORCE" if expression.args.get("force") else "" 1852 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1853 1854 def set_operation(self, expression: exp.SetOperation) -> str: 1855 op_type = type(expression) 1856 op_name = op_type.key.upper() 1857 1858 distinct = expression.args.get("distinct") 1859 if ( 1860 distinct is False 1861 and op_type in (exp.Except, exp.Intersect) 1862 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1863 ): 1864 self.unsupported(f"{op_name} ALL is not supported") 1865 1866 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1867 1868 if distinct is None: 1869 distinct = default_distinct 1870 if distinct is None: 1871 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1872 1873 if distinct is default_distinct: 1874 distinct_or_all = "" 1875 else: 1876 distinct_or_all = " DISTINCT" if distinct else " ALL" 1877 1878 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1879 side_kind = f"{side_kind} " if side_kind else "" 1880 1881 by_name = " BY NAME" if expression.args.get("by_name") else "" 1882 on = self.expressions(expression, key="on", flat=True) 1883 on = f" ON ({on})" if on else "" 1884 1885 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1886 1887 def set_operations(self, expression: exp.SetOperation) -> str: 1888 if not self.SET_OP_MODIFIERS: 1889 limit = expression.args.get("limit") 1890 order = expression.args.get("order") 1891 1892 if limit or order: 1893 select = self._move_ctes_to_top_level( 1894 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1895 ) 1896 1897 if limit: 1898 select = select.limit(limit.pop(), copy=False) 1899 if order: 1900 select = select.order_by(order.pop(), copy=False) 1901 return self.sql(select) 1902 1903 sqls: list[str] = [] 1904 stack: list[str | exp.Expr] = [expression] 1905 1906 while stack: 1907 node = stack.pop() 1908 1909 if isinstance(node, exp.SetOperation): 1910 stack.append(node.expression) 1911 stack.append( 1912 self.maybe_comment( 1913 self.set_operation(node), comments=node.comments, separated=True 1914 ) 1915 ) 1916 stack.append(node.this) 1917 else: 1918 sqls.append(self.sql(node)) 1919 1920 this = self.sep().join(sqls) 1921 this = self.query_modifiers(expression, this) 1922 return self.prepend_ctes(expression, this) 1923 1924 def fetch_sql(self, expression: exp.Fetch) -> str: 1925 direction = expression.args.get("direction") 1926 direction = f" {direction}" if direction else "" 1927 count = self.sql(expression, "count") 1928 count = f" {count}" if count else "" 1929 limit_options = self.sql(expression, "limit_options") 1930 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1931 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1932 1933 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1934 percent = " PERCENT" if expression.args.get("percent") else "" 1935 rows = " ROWS" if expression.args.get("rows") else "" 1936 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1937 if not with_ties and rows: 1938 with_ties = " ONLY" 1939 return f"{percent}{rows}{with_ties}" 1940 1941 def filter_sql(self, expression: exp.Filter) -> str: 1942 this = self.sql(expression, "this") 1943 where = self.sql(expression, "expression").strip() 1944 return f"{this} FILTER({where})" 1945 1946 def hint_sql(self, expression: exp.Hint) -> str: 1947 if not self.QUERY_HINTS: 1948 self.unsupported("Hints are not supported") 1949 return "" 1950 1951 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1952 1953 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1954 using = self.sql(expression, "using") 1955 using = f" USING {using}" if using else "" 1956 columns = self.expressions(expression, key="columns", flat=True) 1957 columns = f"({columns})" if columns else "" 1958 partition_by = self.expressions(expression, key="partition_by", flat=True) 1959 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1960 where = self.sql(expression, "where") 1961 include = self.expressions(expression, key="include", flat=True) 1962 if include: 1963 include = f" INCLUDE ({include})" 1964 with_storage = self.expressions(expression, key="with_storage", flat=True) 1965 with_storage = f" WITH ({with_storage})" if with_storage else "" 1966 tablespace = self.sql(expression, "tablespace") 1967 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1968 on = self.sql(expression, "on") 1969 on = f" ON {on}" if on else "" 1970 1971 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1972 1973 def index_sql(self, expression: exp.Index) -> str: 1974 unique = "UNIQUE " if expression.args.get("unique") else "" 1975 primary = "PRIMARY " if expression.args.get("primary") else "" 1976 amp = "AMP " if expression.args.get("amp") else "" 1977 name = self.sql(expression, "this") 1978 name = f"{name} " if name else "" 1979 table = self.sql(expression, "table") 1980 table = f"{self.INDEX_ON} {table}" if table else "" 1981 1982 index = "INDEX " if not table else "" 1983 1984 params = self.sql(expression, "params") 1985 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1986 1987 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1988 this = expression.this 1989 if this and this.is_string: 1990 resolved = maybe_parse(this.name).sql(self.dialect) 1991 if "expressions" in expression.args: 1992 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1993 # We can't safely emit the call to other dialects since name/arg semantics may differ 1994 self.unsupported( 1995 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1996 ) 1997 return resolved 1998 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1999 return self.func("IDENTIFIER", this) 2000 2001 def identifier_sql(self, expression: exp.Identifier) -> str: 2002 text = expression.name 2003 lower = text.lower() 2004 quoted = expression.quoted 2005 text = lower if self.normalize and not quoted else text 2006 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2007 if ( 2008 quoted 2009 or self.dialect.can_quote(expression, self.identify) 2010 or lower in self.RESERVED_KEYWORDS 2011 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2012 ): 2013 text = ( 2014 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2015 ) 2016 return text 2017 2018 def hex_sql(self, expression: exp.Hex) -> str: 2019 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2020 if self.dialect.HEX_LOWERCASE: 2021 text = self.func("LOWER", text) 2022 2023 return text 2024 2025 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2026 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2027 if not self.dialect.HEX_LOWERCASE: 2028 text = self.func("LOWER", text) 2029 return text 2030 2031 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2032 input_format = self.sql(expression, "input_format") 2033 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2034 output_format = self.sql(expression, "output_format") 2035 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2036 return self.sep().join((input_format, output_format)) 2037 2038 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2039 string = self.sql(exp.Literal.string(expression.name)) 2040 return f"{prefix}{string}" 2041 2042 def partition_sql(self, expression: exp.Partition) -> str: 2043 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2044 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2045 2046 def properties_sql(self, expression: exp.Properties) -> str: 2047 root_properties = [] 2048 with_properties = [] 2049 2050 for p in expression.expressions: 2051 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2052 if p_loc == exp.Properties.Location.POST_WITH: 2053 with_properties.append(p) 2054 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2055 root_properties.append(p) 2056 2057 root_props_ast = exp.Properties(expressions=root_properties) 2058 root_props_ast.parent = expression.parent 2059 2060 with_props_ast = exp.Properties(expressions=with_properties) 2061 with_props_ast.parent = expression.parent 2062 2063 root_props = self.root_properties(root_props_ast) 2064 with_props = self.with_properties(with_props_ast) 2065 2066 if root_props and with_props and not self.pretty: 2067 with_props = " " + with_props 2068 2069 return root_props + with_props 2070 2071 def root_properties(self, properties: exp.Properties) -> str: 2072 if properties.expressions: 2073 return self.expressions(properties, indent=False, sep=" ") 2074 return "" 2075 2076 def properties( 2077 self, 2078 properties: exp.Properties, 2079 prefix: str = "", 2080 sep: str = ", ", 2081 suffix: str = "", 2082 wrapped: bool = True, 2083 ) -> str: 2084 if properties.expressions: 2085 expressions = self.expressions(properties, sep=sep, indent=False) 2086 if expressions: 2087 expressions = self.wrap(expressions) if wrapped else expressions 2088 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2089 return "" 2090 2091 def with_properties(self, properties: exp.Properties) -> str: 2092 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2093 2094 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2095 properties_locs = defaultdict(list) 2096 for p in properties.expressions: 2097 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2098 if p_loc != exp.Properties.Location.UNSUPPORTED: 2099 properties_locs[p_loc].append(p) 2100 else: 2101 self.unsupported(f"Unsupported property {p.key}") 2102 2103 return properties_locs 2104 2105 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2106 if isinstance(expression.this, exp.Dot): 2107 return self.sql(expression, "this") 2108 return f"'{expression.name}'" if string_key else expression.name 2109 2110 def property_sql(self, expression: exp.Property) -> str: 2111 property_cls = expression.__class__ 2112 if property_cls == exp.Property: 2113 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2114 2115 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2116 if not property_name: 2117 self.unsupported(f"Unsupported property {expression.key}") 2118 2119 return f"{property_name}={self.sql(expression, 'this')}" 2120 2121 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2122 return f"UUID {self.sql(expression, 'this')}" 2123 2124 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2125 if self.SUPPORTS_CREATE_TABLE_LIKE: 2126 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2127 options = f" {options}" if options else "" 2128 2129 like = f"LIKE {self.sql(expression, 'this')}{options}" 2130 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2131 like = f"({like})" 2132 2133 return like 2134 2135 if expression.expressions: 2136 self.unsupported("Transpilation of LIKE property options is unsupported") 2137 2138 select = exp.select("*").from_(expression.this).limit(0) 2139 return f"AS {self.sql(select)}" 2140 2141 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2142 no = "NO " if expression.args.get("no") else "" 2143 protection = " PROTECTION" if expression.args.get("protection") else "" 2144 return f"{no}FALLBACK{protection}" 2145 2146 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2147 no = "NO " if expression.args.get("no") else "" 2148 local = expression.args.get("local") 2149 local = f"{local} " if local else "" 2150 dual = "DUAL " if expression.args.get("dual") else "" 2151 before = "BEFORE " if expression.args.get("before") else "" 2152 after = "AFTER " if expression.args.get("after") else "" 2153 return f"{no}{local}{dual}{before}{after}JOURNAL" 2154 2155 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2156 freespace = self.sql(expression, "this") 2157 percent = " PERCENT" if expression.args.get("percent") else "" 2158 return f"FREESPACE={freespace}{percent}" 2159 2160 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2161 if expression.args.get("default"): 2162 property = "DEFAULT" 2163 elif expression.args.get("on"): 2164 property = "ON" 2165 else: 2166 property = "OFF" 2167 return f"CHECKSUM={property}" 2168 2169 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2170 if expression.args.get("no"): 2171 return "NO MERGEBLOCKRATIO" 2172 if expression.args.get("default"): 2173 return "DEFAULT MERGEBLOCKRATIO" 2174 2175 percent = " PERCENT" if expression.args.get("percent") else "" 2176 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2177 2178 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2179 expressions = self.expressions(expression, flat=True) 2180 expressions = f"({expressions})" if expressions else "" 2181 return f"USING {self.sql(expression, 'this')}{expressions}" 2182 2183 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2184 default = expression.args.get("default") 2185 minimum = expression.args.get("minimum") 2186 maximum = expression.args.get("maximum") 2187 if default or minimum or maximum: 2188 if default: 2189 prop = "DEFAULT" 2190 elif minimum: 2191 prop = "MINIMUM" 2192 else: 2193 prop = "MAXIMUM" 2194 return f"{prop} DATABLOCKSIZE" 2195 units = expression.args.get("units") 2196 units = f" {units}" if units else "" 2197 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2198 2199 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2200 autotemp = expression.args.get("autotemp") 2201 always = expression.args.get("always") 2202 default = expression.args.get("default") 2203 manual = expression.args.get("manual") 2204 never = expression.args.get("never") 2205 2206 if autotemp is not None: 2207 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2208 elif always: 2209 prop = "ALWAYS" 2210 elif default: 2211 prop = "DEFAULT" 2212 elif manual: 2213 prop = "MANUAL" 2214 elif never: 2215 prop = "NEVER" 2216 return f"BLOCKCOMPRESSION={prop}" 2217 2218 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2219 no = expression.args.get("no") 2220 no = " NO" if no else "" 2221 concurrent = expression.args.get("concurrent") 2222 concurrent = " CONCURRENT" if concurrent else "" 2223 target = self.sql(expression, "target") 2224 target = f" {target}" if target else "" 2225 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2226 2227 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2228 if isinstance(expression.this, list): 2229 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2230 if expression.this: 2231 modulus = self.sql(expression, "this") 2232 remainder = self.sql(expression, "expression") 2233 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2234 2235 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2236 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2237 return f"FROM ({from_expressions}) TO ({to_expressions})" 2238 2239 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2240 this = self.sql(expression, "this") 2241 2242 for_values_or_default = expression.expression 2243 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2244 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2245 else: 2246 for_values_or_default = " DEFAULT" 2247 2248 return f"PARTITION OF {this}{for_values_or_default}" 2249 2250 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2251 kind = expression.args.get("kind") 2252 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2253 for_or_in = expression.args.get("for_or_in") 2254 for_or_in = f" {for_or_in}" if for_or_in else "" 2255 lock_type = expression.args.get("lock_type") 2256 override = " OVERRIDE" if expression.args.get("override") else "" 2257 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2258 2259 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2260 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2261 statistics = expression.args.get("statistics") 2262 statistics_sql = "" 2263 if statistics is not None: 2264 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2265 return f"{data_sql}{statistics_sql}" 2266 2267 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2268 this = self.sql(expression, "this") 2269 this = f"HISTORY_TABLE={this}" if this else "" 2270 data_consistency: str | None = self.sql(expression, "data_consistency") 2271 data_consistency = ( 2272 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2273 ) 2274 retention_period: str | None = self.sql(expression, "retention_period") 2275 retention_period = ( 2276 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2277 ) 2278 2279 if this: 2280 on_sql = self.func("ON", this, data_consistency, retention_period) 2281 else: 2282 on_sql = "ON" if expression.args.get("on") else "OFF" 2283 2284 sql = f"SYSTEM_VERSIONING={on_sql}" 2285 2286 return f"WITH({sql})" if expression.args.get("with_") else sql 2287 2288 def insert_sql(self, expression: exp.Insert) -> str: 2289 hint = self.sql(expression, "hint") 2290 overwrite = expression.args.get("overwrite") 2291 2292 if isinstance(expression.this, exp.Directory): 2293 this = " OVERWRITE" if overwrite else " INTO" 2294 else: 2295 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2296 2297 stored = self.sql(expression, "stored") 2298 stored = f" {stored}" if stored else "" 2299 alternative = expression.args.get("alternative") 2300 alternative = f" OR {alternative}" if alternative else "" 2301 ignore = " IGNORE" if expression.args.get("ignore") else "" 2302 is_function = expression.args.get("is_function") 2303 if is_function: 2304 this = f"{this} FUNCTION" 2305 this = f"{this} {self.sql(expression, 'this')}" 2306 2307 exists = " IF EXISTS" if expression.args.get("exists") else "" 2308 where = self.sql(expression, "where") 2309 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2310 using = self.expressions(expression, key="using", flat=True) 2311 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2312 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2313 on_conflict = self.sql(expression, "conflict") 2314 on_conflict = f" {on_conflict}" if on_conflict else "" 2315 by_name = " BY NAME" if expression.args.get("by_name") else "" 2316 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2317 returning = self.sql(expression, "returning") 2318 2319 if self.RETURNING_END: 2320 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2321 else: 2322 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2323 2324 partition_by = self.sql(expression, "partition") 2325 partition_by = f" {partition_by}" if partition_by else "" 2326 settings = self.sql(expression, "settings") 2327 settings = f" {settings}" if settings else "" 2328 2329 source = self.sql(expression, "source") 2330 source = f"TABLE {source}" if source else "" 2331 2332 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2333 return self.prepend_ctes(expression, sql) 2334 2335 def introducer_sql(self, expression: exp.Introducer) -> str: 2336 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2337 2338 def kill_sql(self, expression: exp.Kill) -> str: 2339 kind = self.sql(expression, "kind") 2340 kind = f" {kind}" if kind else "" 2341 this = self.sql(expression, "this") 2342 this = f" {this}" if this else "" 2343 return f"KILL{kind}{this}" 2344 2345 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2346 return expression.name 2347 2348 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2349 return expression.name 2350 2351 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2352 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2353 2354 constraint = self.sql(expression, "constraint") 2355 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2356 2357 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2358 if conflict_keys: 2359 conflict_keys = f"({conflict_keys})" 2360 2361 index_predicate = self.sql(expression, "index_predicate") 2362 conflict_keys = f"{conflict_keys}{index_predicate} " 2363 2364 action = self.sql(expression, "action") 2365 2366 expressions = self.expressions(expression, flat=True) 2367 if expressions: 2368 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2369 expressions = f" {set_keyword}{expressions}" 2370 2371 where = self.sql(expression, "where") 2372 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2373 2374 def returning_sql(self, expression: exp.Returning) -> str: 2375 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2376 2377 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2378 fields = self.sql(expression, "fields") 2379 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2380 escaped = self.sql(expression, "escaped") 2381 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2382 items = self.sql(expression, "collection_items") 2383 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2384 keys = self.sql(expression, "map_keys") 2385 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2386 lines = self.sql(expression, "lines") 2387 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2388 null = self.sql(expression, "null") 2389 null = f" NULL DEFINED AS {null}" if null else "" 2390 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2391 2392 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2393 return f"WITH ({self.expressions(expression, flat=True)})" 2394 2395 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2396 this = f"{self.sql(expression, 'this')} INDEX" 2397 target = self.sql(expression, "target") 2398 target = f" FOR {target}" if target else "" 2399 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2400 2401 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2402 this = self.sql(expression, "this") 2403 kind = self.sql(expression, "kind") 2404 expr = self.sql(expression, "expression") 2405 return f"{this} ({kind} => {expr})" 2406 2407 def table_parts(self, expression: exp.Table) -> str: 2408 return ".".join( 2409 self.sql(part) 2410 for part in ( 2411 expression.args.get("catalog"), 2412 expression.args.get("db"), 2413 expression.args.get("this"), 2414 ) 2415 if part is not None 2416 ) 2417 2418 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2419 table = self.table_parts(expression) 2420 only = "ONLY " if expression.args.get("only") else "" 2421 partition = self.sql(expression, "partition") 2422 partition = f" {partition}" if partition else "" 2423 version = self.sql(expression, "version") 2424 version = f" {version}" if version else "" 2425 alias = self.sql(expression, "alias") 2426 alias = f"{sep}{alias}" if alias else "" 2427 2428 sample = self.sql(expression, "sample") 2429 post_alias = "" 2430 pre_alias = "" 2431 2432 if self.dialect.ALIAS_POST_TABLESAMPLE: 2433 pre_alias = sample 2434 else: 2435 post_alias = sample 2436 2437 if self.dialect.ALIAS_POST_VERSION: 2438 pre_alias = f"{pre_alias}{version}" 2439 else: 2440 post_alias = f"{post_alias}{version}" 2441 2442 hints = self.expressions(expression, key="hints", sep=" ") 2443 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2444 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2445 joins = self.indent( 2446 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2447 ) 2448 laterals = self.expressions(expression, key="laterals", sep="") 2449 2450 file_format = self.sql(expression, "format") 2451 pattern = self.sql(expression, "pattern") 2452 if file_format: 2453 pattern = f", PATTERN => {pattern}" if pattern else "" 2454 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2455 elif pattern: 2456 file_format = f" (PATTERN => {pattern})" 2457 2458 ordinality = expression.args.get("ordinality") or "" 2459 if ordinality: 2460 ordinality = f" WITH ORDINALITY{alias}" 2461 alias = "" 2462 2463 when = self.sql(expression, "when") 2464 if when: 2465 if self.HISTORICAL_DATA_POST_ALIAS: 2466 alias = f"{alias} {when}" 2467 else: 2468 table = f"{table} {when}" 2469 2470 changes = self.sql(expression, "changes") 2471 changes = f" {changes}" if changes else "" 2472 2473 rows_from = self.expressions(expression, key="rows_from") 2474 if rows_from: 2475 table = f"ROWS FROM {self.wrap(rows_from)}" 2476 2477 indexed = expression.args.get("indexed") 2478 if indexed is not None: 2479 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2480 else: 2481 indexed = "" 2482 2483 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2484 2485 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2486 table = self.func("TABLE", expression.this) 2487 alias = self.sql(expression, "alias") 2488 alias = f" AS {alias}" if alias else "" 2489 sample = self.sql(expression, "sample") 2490 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2491 joins = self.indent( 2492 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2493 ) 2494 return f"{table}{alias}{pivots}{sample}{joins}" 2495 2496 def tablesample_sql( 2497 self, 2498 expression: exp.TableSample, 2499 tablesample_keyword: str | None = None, 2500 ) -> str: 2501 method = self.sql(expression, "method") 2502 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2503 numerator = self.sql(expression, "bucket_numerator") 2504 denominator = self.sql(expression, "bucket_denominator") 2505 field = self.sql(expression, "bucket_field") 2506 field = f" ON {field}" if field else "" 2507 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2508 seed = self.sql(expression, "seed") 2509 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2510 2511 size = self.sql(expression, "size") 2512 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2513 size = f"{size} ROWS" 2514 2515 percent = self.sql(expression, "percent") 2516 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2517 percent = f"{percent} PERCENT" 2518 2519 expr = f"{bucket}{percent}{size}" 2520 if self.TABLESAMPLE_REQUIRES_PARENS: 2521 expr = f"({expr})" 2522 2523 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2524 2525 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2526 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2527 # the stored column name differs from the target dialect's natural output. 2528 columns = expression.args.get("columns") 2529 if not columns or len(expression.fields) != 1: 2530 return None 2531 2532 args = expression.args 2533 parser_cls = self.dialect.parser_class 2534 2535 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2536 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2537 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2538 2539 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2540 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2541 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2542 2543 if ( 2544 src_identify_pivot_strings == tgt_identify_pivot_strings 2545 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2546 and src_pivot_column_naming == tgt_pivot_column_naming 2547 ): 2548 return None 2549 2550 in_exprs = expression.fields[0].expressions 2551 step = len(columns) // len(in_exprs) 2552 2553 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2554 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2555 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2556 first_stored = columns[0].name 2557 2558 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2559 if not first_stored.startswith(first_base): 2560 return None 2561 2562 suffix = first_stored[len(first_base) :] 2563 2564 # Whether the target dialect would append an agg-name suffix for this pivot. 2565 # Spark single-agg uniquely drops the agg alias entirely. 2566 target_has_suffix = ( 2567 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2568 ) and any(a.alias for a in expression.expressions) 2569 source_has_suffix = suffix != "" 2570 2571 new_exprs: list[exp.Expression] = [] 2572 modified = False 2573 for val_idx, e in enumerate(in_exprs): 2574 if isinstance(e, exp.PivotAlias): 2575 new_exprs.append(e) 2576 continue 2577 2578 i = val_idx * step 2579 stored_full = columns[i].name 2580 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2581 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2582 2583 # Source had a suffix, but target won't apply one 2584 if source_has_suffix and not target_has_suffix: 2585 new_exprs.append( 2586 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2587 ) 2588 modified = True 2589 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2590 elif stored_value != target_value: 2591 new_exprs.append( 2592 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2593 ) 2594 modified = True 2595 else: 2596 new_exprs.append(e) 2597 2598 return new_exprs if modified else None 2599 2600 def pivot_sql(self, expression: exp.Pivot) -> str: 2601 expressions = self.expressions(expression, flat=True) 2602 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2603 2604 group = self.sql(expression, "group") 2605 2606 if expression.this: 2607 this = self.sql(expression, "this") 2608 if not expressions: 2609 sql = f"UNPIVOT {this}" 2610 else: 2611 on = f"{self.seg('ON')} {expressions}" 2612 into = self.sql(expression, "into") 2613 into = f"{self.seg('INTO')} {into}" if into else "" 2614 using = self.expressions(expression, key="using", flat=True) 2615 using = f"{self.seg('USING')} {using}" if using else "" 2616 sql = f"{direction} {this}{on}{into}{using}{group}" 2617 return self.prepend_ctes(expression, sql) 2618 2619 if not expression.unpivot: 2620 # Wrap IN-list values with explicit aliases where the target dialect would differ 2621 new_field_exprs = self._pivot_in_value_aliases(expression) 2622 if new_field_exprs is not None: 2623 expression.fields[0].set("expressions", new_field_exprs) 2624 2625 alias = self.sql(expression, "alias") 2626 if alias: 2627 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2628 2629 fields = self.expressions( 2630 expression, 2631 "fields", 2632 sep=" ", 2633 dynamic=True, 2634 new_line=True, 2635 skip_first=True, 2636 skip_last=True, 2637 ) 2638 2639 include_nulls = expression.args.get("include_nulls") 2640 if include_nulls is not None: 2641 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2642 else: 2643 nulls = "" 2644 2645 default_on_null = self.sql(expression, "default_on_null") 2646 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2647 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2648 return self.prepend_ctes(expression, sql) 2649 2650 def version_sql(self, expression: exp.Version) -> str: 2651 this = f"FOR {expression.name}" 2652 kind = expression.text("kind") 2653 expr = self.sql(expression, "expression") 2654 return f"{this} {kind} {expr}" 2655 2656 def tuple_sql(self, expression: exp.Tuple) -> str: 2657 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2658 2659 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2660 """ 2661 Returns (join_sql, from_sql) for UPDATE statements. 2662 - join_sql: placed after UPDATE table, before SET 2663 - from_sql: placed after SET clause (standard position) 2664 Dialects like MySQL need to convert FROM to JOIN syntax. 2665 """ 2666 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2667 return ("", self.sql(expression, "from_")) 2668 2669 # Qualify unqualified columns in SET clause with the target table 2670 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2671 target_table = expression.this 2672 if isinstance(target_table, exp.Table): 2673 target_name = exp.to_identifier(target_table.alias_or_name) 2674 for eq in expression.expressions: 2675 col = eq.this 2676 if isinstance(col, exp.Column) and not col.table: 2677 col.set("table", target_name) 2678 2679 table = from_expr.this 2680 if nested_joins := table.args.get("joins", []): 2681 table.set("joins", None) 2682 2683 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2684 for nested in nested_joins: 2685 if not nested.args.get("on") and not nested.args.get("using"): 2686 nested.set("on", exp.true()) 2687 join_sql += self.sql(nested) 2688 2689 return (join_sql, "") 2690 2691 def update_sql(self, expression: exp.Update) -> str: 2692 hint = self.sql(expression, "hint") 2693 this = self.sql(expression, "this") 2694 join_sql, from_sql = self._update_from_joins_sql(expression) 2695 set_sql = self.expressions(expression, flat=True) 2696 where_sql = self.sql(expression, "where") 2697 returning = self.sql(expression, "returning") 2698 order = self.sql(expression, "order") 2699 limit = self.sql(expression, "limit") 2700 if self.RETURNING_END: 2701 expression_sql = f"{from_sql}{where_sql}{returning}" 2702 else: 2703 expression_sql = f"{returning}{from_sql}{where_sql}" 2704 options = self.expressions(expression, key="options") 2705 options = f" OPTION({options})" if options else "" 2706 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2707 return self.prepend_ctes(expression, sql) 2708 2709 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2710 values_as_table = values_as_table and self.VALUES_AS_TABLE 2711 2712 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2713 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2714 args = self.expressions(expression) 2715 alias = self.sql(expression, "alias") 2716 values = f"VALUES{self.seg('')}{args}" 2717 values = ( 2718 f"({values})" 2719 if self.WRAP_DERIVED_VALUES 2720 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2721 else values 2722 ) 2723 values = self.query_modifiers(expression, values) 2724 return f"{values} AS {alias}" if alias else values 2725 2726 # Converts `VALUES...` expression into a series of select unions. 2727 alias_node = expression.args.get("alias") 2728 column_names = alias_node and alias_node.columns 2729 2730 selects: list[exp.Query] = [] 2731 2732 for i, tup in enumerate(expression.expressions): 2733 row = tup.expressions 2734 2735 if i == 0 and column_names: 2736 row = [ 2737 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2738 ] 2739 2740 selects.append(exp.Select(expressions=row)) 2741 2742 if self.pretty: 2743 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2744 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2745 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2746 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2747 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2748 2749 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2750 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2751 return f"({unions}){alias}" 2752 2753 def var_sql(self, expression: exp.Var) -> str: 2754 return self.sql(expression, "this") 2755 2756 @unsupported_args("expressions") 2757 def into_sql(self, expression: exp.Into) -> str: 2758 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2759 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2760 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2761 2762 def from_sql(self, expression: exp.From) -> str: 2763 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2764 2765 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2766 grouping_sets = self.expressions(expression, indent=False) 2767 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2768 2769 def rollup_sql(self, expression: exp.Rollup) -> str: 2770 expressions = self.expressions(expression, indent=False) 2771 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2772 2773 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2774 this = self.sql(expression, "this") 2775 2776 columns = self.expressions(expression, flat=True) 2777 2778 from_sql = self.sql(expression, "from_index") 2779 from_sql = f" FROM {from_sql}" if from_sql else "" 2780 2781 properties = expression.args.get("properties") 2782 properties_sql = ( 2783 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2784 ) 2785 2786 return f"{this}({columns}){from_sql}{properties_sql}" 2787 2788 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2789 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2790 2791 def cube_sql(self, expression: exp.Cube) -> str: 2792 expressions = self.expressions(expression, indent=False) 2793 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2794 2795 def group_sql(self, expression: exp.Group) -> str: 2796 group_by_all = expression.args.get("all") 2797 if group_by_all is True: 2798 modifier = " ALL" 2799 elif group_by_all is False: 2800 modifier = " DISTINCT" 2801 else: 2802 modifier = "" 2803 2804 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2805 2806 grouping_sets = self.expressions(expression, key="grouping_sets") 2807 cube = self.expressions(expression, key="cube") 2808 rollup = self.expressions(expression, key="rollup") 2809 2810 groupings = csv( 2811 self.seg(grouping_sets) if grouping_sets else "", 2812 self.seg(cube) if cube else "", 2813 self.seg(rollup) if rollup else "", 2814 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2815 sep=self.GROUPINGS_SEP, 2816 ) 2817 2818 if ( 2819 expression.expressions 2820 and groupings 2821 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2822 ): 2823 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2824 2825 return f"{group_by}{groupings}" 2826 2827 def having_sql(self, expression: exp.Having) -> str: 2828 this = self.indent(self.sql(expression, "this")) 2829 return f"{self.seg('HAVING')}{self.sep()}{this}" 2830 2831 def connect_sql(self, expression: exp.Connect) -> str: 2832 start = self.sql(expression, "start") 2833 start = self.seg(f"START WITH {start}") if start else "" 2834 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2835 connect = self.sql(expression, "connect") 2836 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2837 return start + connect 2838 2839 def prior_sql(self, expression: exp.Prior) -> str: 2840 return f"PRIOR {self.sql(expression, 'this')}" 2841 2842 def join_sql(self, expression: exp.Join) -> str: 2843 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2844 side = None 2845 else: 2846 side = expression.side 2847 2848 op_sql = " ".join( 2849 op 2850 for op in ( 2851 expression.method, 2852 "GLOBAL" if expression.args.get("global_") else None, 2853 side, 2854 expression.kind, 2855 expression.hint if self.JOIN_HINTS else None, 2856 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2857 ) 2858 if op 2859 ) 2860 match_cond = self.sql(expression, "match_condition") 2861 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2862 on_sql = self.sql(expression, "on") 2863 using = expression.args.get("using") 2864 2865 if not on_sql and using: 2866 on_sql = csv(*(self.sql(column) for column in using)) 2867 2868 this = expression.this 2869 this_sql = self.sql(this) 2870 2871 exprs = self.expressions(expression) 2872 if exprs: 2873 this_sql = f"{this_sql},{self.seg(exprs)}" 2874 2875 if on_sql: 2876 on_sql = self.indent(on_sql, skip_first=True) 2877 space = self.seg(" " * self.pad) if self.pretty else " " 2878 if using: 2879 on_sql = f"{space}USING ({on_sql})" 2880 else: 2881 on_sql = f"{space}ON {on_sql}" 2882 elif not op_sql: 2883 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2884 return f" {this_sql}" 2885 2886 return f", {this_sql}" 2887 2888 if op_sql != "STRAIGHT_JOIN": 2889 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2890 2891 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2892 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2893 2894 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2895 args = self.expressions(expression, flat=True) 2896 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2897 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2898 2899 def lateral_op(self, expression: exp.Lateral) -> str: 2900 cross_apply = expression.args.get("cross_apply") 2901 2902 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2903 if cross_apply is True: 2904 op = "INNER JOIN " 2905 elif cross_apply is False: 2906 op = "LEFT JOIN " 2907 else: 2908 op = "" 2909 2910 return f"{op}LATERAL" 2911 2912 def lateral_sql(self, expression: exp.Lateral) -> str: 2913 this = self.sql(expression, "this") 2914 2915 if expression.args.get("view"): 2916 alias = expression.args["alias"] 2917 columns = self.expressions(alias, key="columns", flat=True) 2918 table = f" {alias.name}" if alias.name else "" 2919 columns = f" AS {columns}" if columns else "" 2920 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2921 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2922 2923 alias = self.sql(expression, "alias") 2924 alias = f" AS {alias}" if alias else "" 2925 2926 ordinality = expression.args.get("ordinality") or "" 2927 if ordinality: 2928 ordinality = f" WITH ORDINALITY{alias}" 2929 alias = "" 2930 2931 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2932 2933 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2934 this = self.sql(expression, "this") 2935 2936 args = [ 2937 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2938 for e in (expression.args.get(k) for k in ("offset", "expression")) 2939 if e 2940 ] 2941 2942 args_sql = ", ".join(self.sql(e) for e in args) 2943 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2944 expressions = self.expressions(expression, flat=True) 2945 limit_options = self.sql(expression, "limit_options") 2946 expressions = f" BY {expressions}" if expressions else "" 2947 2948 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2949 2950 def offset_sql(self, expression: exp.Offset) -> str: 2951 this = self.sql(expression, "this") 2952 value = expression.expression 2953 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2954 expressions = self.expressions(expression, flat=True) 2955 expressions = f" BY {expressions}" if expressions else "" 2956 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2957 2958 def setitem_sql(self, expression: exp.SetItem) -> str: 2959 kind = self.sql(expression, "kind") 2960 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2961 kind = "" 2962 else: 2963 kind = f"{kind} " if kind else "" 2964 this = self.sql(expression, "this") 2965 expressions = self.expressions(expression) 2966 collate = self.sql(expression, "collate") 2967 collate = f" COLLATE {collate}" if collate else "" 2968 global_ = "GLOBAL " if expression.args.get("global_") else "" 2969 return f"{global_}{kind}{this}{expressions}{collate}" 2970 2971 def set_sql(self, expression: exp.Set) -> str: 2972 expressions = f" {self.expressions(expression, flat=True)}" 2973 tag = " TAG" if expression.args.get("tag") else "" 2974 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2975 2976 def queryband_sql(self, expression: exp.QueryBand) -> str: 2977 this = self.sql(expression, "this") 2978 update = " UPDATE" if expression.args.get("update") else "" 2979 scope = self.sql(expression, "scope") 2980 scope = f" FOR {scope}" if scope else "" 2981 2982 return f"QUERY_BAND = {this}{update}{scope}" 2983 2984 def pragma_sql(self, expression: exp.Pragma) -> str: 2985 return f"PRAGMA {self.sql(expression, 'this')}" 2986 2987 def lock_sql(self, expression: exp.Lock) -> str: 2988 if not self.LOCKING_READS_SUPPORTED: 2989 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2990 return "" 2991 2992 update = expression.args["update"] 2993 key = expression.args.get("key") 2994 if update: 2995 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2996 else: 2997 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2998 expressions = self.expressions(expression, flat=True) 2999 expressions = f" OF {expressions}" if expressions else "" 3000 wait = expression.args.get("wait") 3001 3002 if wait is not None: 3003 if isinstance(wait, exp.Literal): 3004 wait = f" WAIT {self.sql(wait)}" 3005 else: 3006 wait = " NOWAIT" if wait else " SKIP LOCKED" 3007 3008 return f"{lock_type}{expressions}{wait or ''}" 3009 3010 def literal_sql(self, expression: exp.Literal) -> str: 3011 text = expression.this or "" 3012 if expression.is_string: 3013 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3014 return text 3015 3016 def escape_str( 3017 self, 3018 text: str, 3019 escape_backslash: bool = True, 3020 delimiter: str | None = None, 3021 escaped_delimiter: str | None = None, 3022 is_byte_string: bool = False, 3023 ) -> str: 3024 if is_byte_string: 3025 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3026 else: 3027 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3028 3029 if supports_escape_sequences: 3030 text = "".join( 3031 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3032 for ch in text 3033 ) 3034 3035 delimiter = delimiter or self.dialect.QUOTE_END 3036 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3037 3038 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3039 3040 def loaddata_sql(self, expression: exp.LoadData) -> str: 3041 is_overwrite = expression.args.get("overwrite") 3042 overwrite = " OVERWRITE" if is_overwrite else "" 3043 this = self.sql(expression, "this") 3044 3045 files = expression.args.get("files") 3046 if files: 3047 files_sql = self.expressions(files, flat=True) 3048 files_sql = f"FILES{self.wrap(files_sql)}" 3049 if is_overwrite: 3050 this = f" {this}" 3051 elif expression.args.get("temp"): 3052 this = f" INTO TEMP TABLE {this}" 3053 else: 3054 this = f" INTO TABLE {this}" 3055 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3056 3057 local = " LOCAL" if expression.args.get("local") else "" 3058 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3059 this = f" INTO TABLE {this}" 3060 partition = self.sql(expression, "partition") 3061 partition = f" {partition}" if partition else "" 3062 input_format = self.sql(expression, "input_format") 3063 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3064 serde = self.sql(expression, "serde") 3065 serde = f" SERDE {serde}" if serde else "" 3066 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3067 3068 def null_sql(self, *_) -> str: 3069 return "NULL" 3070 3071 def boolean_sql(self, expression: exp.Boolean) -> str: 3072 return "TRUE" if expression.this else "FALSE" 3073 3074 def booland_sql(self, expression: exp.Booland) -> str: 3075 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3076 3077 def boolor_sql(self, expression: exp.Boolor) -> str: 3078 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3079 3080 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3081 this = self.sql(expression, "this") 3082 this = f"{this} " if this else this 3083 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3084 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3085 3086 def withfill_sql(self, expression: exp.WithFill) -> str: 3087 from_sql = self.sql(expression, "from_") 3088 from_sql = f" FROM {from_sql}" if from_sql else "" 3089 to_sql = self.sql(expression, "to") 3090 to_sql = f" TO {to_sql}" if to_sql else "" 3091 step_sql = self.sql(expression, "step") 3092 step_sql = f" STEP {step_sql}" if step_sql else "" 3093 interpolated_values = [ 3094 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3095 if isinstance(e, exp.Alias) 3096 else self.sql(e, "this") 3097 for e in expression.args.get("interpolate") or [] 3098 ] 3099 interpolate = ( 3100 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3101 ) 3102 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3103 3104 def cluster_sql(self, expression: exp.Cluster) -> str: 3105 return self.op_expressions("CLUSTER BY", expression) 3106 3107 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3108 if expression.this: 3109 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3110 return "" 3111 expressions = self.expressions(expression, flat=True) 3112 return f"CLUSTER BY ({expressions})" 3113 3114 def distribute_sql(self, expression: exp.Distribute) -> str: 3115 return self.op_expressions("DISTRIBUTE BY", expression) 3116 3117 def sort_sql(self, expression: exp.Sort) -> str: 3118 return self.op_expressions("SORT BY", expression) 3119 3120 def _resolve_ordered_for_null_ordering_simulation( 3121 self, expression: exp.Ordered 3122 ) -> exp.Expr | None: 3123 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3124 3125 Returns the underlying expression of the uniquely-matching projection 3126 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3127 simulation, since the CASE is evaluated in FROM-clause scope rather 3128 than alias scope (MySQL error 1052). Returns None if no safe 3129 substitution applies, leaving the original behaviour unchanged. 3130 """ 3131 this = expression.this 3132 if not (isinstance(this, exp.Column) and not this.table): 3133 return None 3134 3135 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3136 if not isinstance(ancestor, exp.Select): 3137 return None 3138 3139 column_name = this.name 3140 matched: list[exp.Expr] = [ 3141 p.this if isinstance(p, exp.Alias) else p 3142 for p in ancestor.selects 3143 if p.output_name == column_name 3144 ] 3145 match = matched[0] if len(matched) == 1 else None 3146 3147 # Skip the substitution when it would be identical to the existing 3148 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3149 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3150 return None 3151 3152 return match 3153 3154 def ordered_sql(self, expression: exp.Ordered) -> str: 3155 desc = expression.args.get("desc") 3156 asc = not desc 3157 3158 nulls_first = expression.args.get("nulls_first") 3159 nulls_last = not nulls_first 3160 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3161 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3162 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3163 3164 this = self.sql(expression, "this") 3165 3166 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3167 nulls_sort_change = "" 3168 if nulls_first and ( 3169 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3170 ): 3171 nulls_sort_change = " NULLS FIRST" 3172 elif ( 3173 nulls_last 3174 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3175 and not nulls_are_last 3176 ): 3177 nulls_sort_change = " NULLS LAST" 3178 3179 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3180 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3181 window = expression.find_ancestor(exp.Window, exp.Select) 3182 3183 if isinstance(window, exp.Window): 3184 window_this = window.this 3185 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3186 window_this = window_this.this 3187 spec = window.args.get("spec") 3188 else: 3189 window_this = None 3190 spec = None 3191 3192 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3193 # without a spec or with a ROWS spec, but not with RANGE 3194 if not ( 3195 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3196 and (not spec or spec.text("kind").upper() == "ROWS") 3197 ): 3198 if window_this and spec: 3199 self.unsupported( 3200 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3201 ) 3202 nulls_sort_change = "" 3203 elif self.NULL_ORDERING_SUPPORTED is False and ( 3204 (asc and nulls_sort_change == " NULLS LAST") 3205 or (desc and nulls_sort_change == " NULLS FIRST") 3206 ): 3207 # BigQuery does not allow these ordering/nulls combinations when used under 3208 # an aggregation func or under a window containing one 3209 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3210 3211 if isinstance(ancestor, exp.Window): 3212 ancestor = ancestor.this 3213 if isinstance(ancestor, exp.AggFunc): 3214 self.unsupported( 3215 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3216 ) 3217 nulls_sort_change = "" 3218 elif self.NULL_ORDERING_SUPPORTED is None: 3219 if expression.this.is_int: 3220 self.unsupported( 3221 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3222 ) 3223 elif not isinstance(expression.this, exp.Rand): 3224 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3225 target = self.sql(resolved) if resolved is not None else this 3226 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3227 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3228 nulls_sort_change = "" 3229 3230 with_fill = self.sql(expression, "with_fill") 3231 with_fill = f" {with_fill}" if with_fill else "" 3232 3233 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3234 3235 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3236 window_frame = self.sql(expression, "window_frame") 3237 window_frame = f"{window_frame} " if window_frame else "" 3238 3239 this = self.sql(expression, "this") 3240 3241 return f"{window_frame}{this}" 3242 3243 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3244 partition = self.partition_by_sql(expression) 3245 order = self.sql(expression, "order") 3246 measures = self.expressions(expression, key="measures") 3247 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3248 rows = self.sql(expression, "rows") 3249 rows = self.seg(rows) if rows else "" 3250 after = self.sql(expression, "after") 3251 after = self.seg(after) if after else "" 3252 pattern = self.sql(expression, "pattern") 3253 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3254 definition_sqls = [ 3255 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3256 for definition in expression.args.get("define", []) 3257 ] 3258 definitions = self.expressions(sqls=definition_sqls) 3259 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3260 body = "".join( 3261 ( 3262 partition, 3263 order, 3264 measures, 3265 rows, 3266 after, 3267 pattern, 3268 define, 3269 ) 3270 ) 3271 alias = self.sql(expression, "alias") 3272 alias = f" {alias}" if alias else "" 3273 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3274 3275 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3276 limit = expression.args.get("limit") 3277 3278 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3279 count = limit.args.get("count") 3280 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3281 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3282 limit = exp.Limit( 3283 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3284 ) 3285 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3286 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3287 3288 return csv( 3289 *sqls, 3290 *[self.sql(join) for join in expression.args.get("joins") or []], 3291 self.sql(expression, "match"), 3292 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3293 self.sql(expression, "prewhere"), 3294 self.sql(expression, "where"), 3295 self.sql(expression, "connect"), 3296 self.sql(expression, "group"), 3297 self.sql(expression, "having"), 3298 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3299 self.sql(expression, "order"), 3300 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3301 *self.after_limit_modifiers(expression), 3302 self.options_modifier(expression), 3303 self.sql(expression, "for_"), 3304 sep="", 3305 ) 3306 3307 def options_modifier(self, expression: exp.Expr) -> str: 3308 options = self.expressions(expression, key="options") 3309 return f" {options}" if options else "" 3310 3311 def forclause_sql(self, expression: exp.ForClause) -> str: 3312 kind = expression.args["kind"] 3313 if kind == "BROWSE": 3314 return f"{self.sep()}FOR BROWSE" 3315 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3316 # the target dialect doesn't support QueryOption, so we drop the clause. 3317 options = self.expressions(expression, key="expressions") 3318 if not options: 3319 return "" 3320 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3321 3322 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3323 self.unsupported("Unsupported query option.") 3324 return "" 3325 3326 def offset_limit_modifiers( 3327 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3328 ) -> list[str]: 3329 return [ 3330 self.sql(expression, "offset") if fetch else self.sql(limit), 3331 self.sql(limit) if fetch else self.sql(expression, "offset"), 3332 ] 3333 3334 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3335 locks = self.expressions(expression, key="locks", sep=" ") 3336 locks = f" {locks}" if locks else "" 3337 return [locks, self.sql(expression, "sample")] 3338 3339 def select_sql(self, expression: exp.Select) -> str: 3340 into = expression.args.get("into") 3341 if not self.SUPPORTS_SELECT_INTO and into: 3342 into.pop() 3343 3344 hint = self.sql(expression, "hint") 3345 distinct = self.sql(expression, "distinct") 3346 distinct = f" {distinct}" if distinct else "" 3347 kind = self.sql(expression, "kind") 3348 3349 limit = expression.args.get("limit") 3350 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3351 top = self.limit_sql(limit, top=True) 3352 limit.pop() 3353 else: 3354 top = "" 3355 3356 expressions = self.expressions(expression) 3357 3358 if kind: 3359 if kind in self.SELECT_KINDS: 3360 kind = f" AS {kind}" 3361 else: 3362 if kind == "STRUCT": 3363 expressions = self.expressions( 3364 sqls=[ 3365 self.sql( 3366 exp.Struct( 3367 expressions=[ 3368 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3369 if isinstance(e, exp.Alias) 3370 else e 3371 for e in expression.expressions 3372 ] 3373 ) 3374 ) 3375 ] 3376 ) 3377 kind = "" 3378 3379 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3380 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3381 3382 exclude = expression.args.get("exclude") 3383 3384 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3385 exclude_sql = self.expressions(sqls=exclude, flat=True) 3386 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3387 3388 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3389 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3390 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3391 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3392 sql = self.query_modifiers( 3393 expression, 3394 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3395 self.sql(expression, "into", comment=False), 3396 self.sql(expression, "from_", comment=False), 3397 ) 3398 3399 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3400 if expression.args.get("with_"): 3401 sql = self.maybe_comment(sql, expression) 3402 expression.pop_comments() 3403 3404 sql = self.prepend_ctes(expression, sql) 3405 3406 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3407 expression.set("exclude", None) 3408 subquery = expression.subquery(copy=False) 3409 star = exp.Star(except_=exclude) 3410 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3411 3412 if not self.SUPPORTS_SELECT_INTO and into: 3413 if into.args.get("temporary"): 3414 table_kind = " TEMPORARY" 3415 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3416 table_kind = " UNLOGGED" 3417 else: 3418 table_kind = "" 3419 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3420 3421 return sql 3422 3423 def schema_sql(self, expression: exp.Schema) -> str: 3424 this = self.sql(expression, "this") 3425 sql = self.schema_columns_sql(expression) 3426 return f"{this} {sql}" if this and sql else this or sql 3427 3428 def schema_columns_sql(self, expression: exp.Expr) -> str: 3429 if expression.expressions: 3430 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3431 return "" 3432 3433 def star_sql(self, expression: exp.Star) -> str: 3434 except_ = self.expressions(expression, key="except_", flat=True) 3435 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3436 replace = self.expressions(expression, key="replace", flat=True) 3437 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3438 rename = self.expressions(expression, key="rename", flat=True) 3439 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3440 ilike = self.sql(expression, "ilike") 3441 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3442 return f"*{ilike}{except_}{replace}{rename}" 3443 3444 def parameter_sql(self, expression: exp.Parameter) -> str: 3445 this = self.sql(expression, "this") 3446 return f"{self.PARAMETER_TOKEN}{this}" 3447 3448 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3449 this = self.sql(expression, "this") 3450 kind = expression.text("kind") 3451 if kind: 3452 kind = f"{kind}." 3453 return f"@@{kind}{this}" 3454 3455 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3456 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3457 3458 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3459 alias = self.sql(expression, "alias") 3460 alias = f"{sep}{alias}" if alias else "" 3461 sample = self.sql(expression, "sample") 3462 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3463 alias = f"{sample}{alias}" 3464 3465 # Set to None so it's not generated again by self.query_modifiers() 3466 expression.set("sample", None) 3467 3468 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3469 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3470 return self.prepend_ctes(expression, sql) 3471 3472 def qualify_sql(self, expression: exp.Qualify) -> str: 3473 this = self.indent(self.sql(expression, "this")) 3474 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3475 3476 def unnest_sql(self, expression: exp.Unnest) -> str: 3477 args = self.expressions(expression, flat=True) 3478 3479 alias = expression.args.get("alias") 3480 offset = expression.args.get("offset") 3481 3482 if self.UNNEST_WITH_ORDINALITY: 3483 if alias and isinstance(offset, exp.Expr): 3484 alias.append("columns", offset) 3485 expression.set("offset", None) 3486 3487 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3488 columns = alias.columns 3489 alias = self.sql(columns[0]) if columns else "" 3490 else: 3491 alias = self.sql(alias) 3492 3493 alias = f" AS {alias}" if alias else alias 3494 if self.UNNEST_WITH_ORDINALITY: 3495 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3496 else: 3497 if isinstance(offset, exp.Expr): 3498 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3499 elif offset: 3500 suffix = f"{alias} WITH OFFSET" 3501 else: 3502 suffix = alias 3503 3504 return f"UNNEST({args}){suffix}" 3505 3506 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3507 return "" 3508 3509 def where_sql(self, expression: exp.Where) -> str: 3510 this = self.indent(self.sql(expression, "this")) 3511 return f"{self.seg('WHERE')}{self.sep()}{this}" 3512 3513 def window_sql(self, expression: exp.Window) -> str: 3514 this = self.sql(expression, "this") 3515 partition = self.partition_by_sql(expression) 3516 order = expression.args.get("order") 3517 order = self.order_sql(order, flat=True) if order else "" 3518 spec = self.sql(expression, "spec") 3519 alias = self.sql(expression, "alias") 3520 over = self.sql(expression, "over") or "OVER" 3521 3522 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3523 3524 first = expression.args.get("first") 3525 if first is None: 3526 first = "" 3527 else: 3528 first = "FIRST" if first else "LAST" 3529 3530 if not partition and not order and not spec and alias: 3531 return f"{this} {alias}" 3532 3533 args = self.format_args( 3534 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3535 ) 3536 return f"{this} ({args})" 3537 3538 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3539 partition = self.expressions(expression, key="partition_by", flat=True) 3540 return f"PARTITION BY {partition}" if partition else "" 3541 3542 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3543 kind = self.sql(expression, "kind") 3544 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3545 end = ( 3546 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3547 or "CURRENT ROW" 3548 ) 3549 3550 window_spec = f"{kind} BETWEEN {start} AND {end}" 3551 3552 exclude = self.sql(expression, "exclude") 3553 if exclude: 3554 if self.SUPPORTS_WINDOW_EXCLUDE: 3555 window_spec += f" EXCLUDE {exclude}" 3556 else: 3557 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3558 3559 return window_spec 3560 3561 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3562 this = self.sql(expression, "this") 3563 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3564 return f"{this} WITHIN GROUP ({expression_sql})" 3565 3566 def between_sql(self, expression: exp.Between) -> str: 3567 this = self.sql(expression, "this") 3568 low = self.sql(expression, "low") 3569 high = self.sql(expression, "high") 3570 symmetric = expression.args.get("symmetric") 3571 3572 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3573 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3574 3575 flag = ( 3576 " SYMMETRIC" 3577 if symmetric 3578 else " ASYMMETRIC" 3579 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3580 else "" # silently drop ASYMMETRIC – semantics identical 3581 ) 3582 return f"{this} BETWEEN{flag} {low} AND {high}" 3583 3584 def bracket_offset_expressions( 3585 self, expression: exp.Bracket, index_offset: int | None = None 3586 ) -> list[exp.Expr]: 3587 if expression.args.get("json_access"): 3588 return expression.expressions 3589 3590 return apply_index_offset( 3591 expression.this, 3592 expression.expressions, 3593 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3594 dialect=self.dialect, 3595 ) 3596 3597 def bracket_sql(self, expression: exp.Bracket) -> str: 3598 expressions = self.bracket_offset_expressions(expression) 3599 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3600 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3601 3602 def all_sql(self, expression: exp.All) -> str: 3603 this = self.sql(expression, "this") 3604 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3605 this = self.wrap(this) 3606 return f"ALL {this}" 3607 3608 def any_sql(self, expression: exp.Any) -> str: 3609 this = self.sql(expression, "this") 3610 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3611 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3612 this = self.wrap(this) 3613 return f"ANY{this}" 3614 return f"ANY {this}" 3615 3616 def exists_sql(self, expression: exp.Exists) -> str: 3617 return f"EXISTS{self.wrap(expression)}" 3618 3619 def case_sql(self, expression: exp.Case) -> str: 3620 this = self.sql(expression, "this") 3621 statements = [f"CASE {this}" if this else "CASE"] 3622 3623 for e in expression.args["ifs"]: 3624 statements.append(f"WHEN {self.sql(e, 'this')}") 3625 statements.append(f"THEN {self.sql(e, 'true')}") 3626 3627 default = self.sql(expression, "default") 3628 3629 if default: 3630 statements.append(f"ELSE {default}") 3631 3632 statements.append("END") 3633 3634 if self.pretty and self.too_wide(statements): 3635 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3636 3637 return " ".join(statements) 3638 3639 def constraint_sql(self, expression: exp.Constraint) -> str: 3640 this = self.sql(expression, "this") 3641 expressions = self.expressions(expression, flat=True) 3642 return f"CONSTRAINT {this} {expressions}" 3643 3644 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3645 order = expression.args.get("order") 3646 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3647 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3648 3649 def extract_sql(self, expression: exp.Extract) -> str: 3650 import sqlglot.dialects.dialect 3651 3652 this = ( 3653 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3654 if self.NORMALIZE_EXTRACT_DATE_PARTS 3655 else expression.this 3656 ) 3657 if self.EXTRACT_ALLOWS_QUOTES: 3658 this_sql = self.sql(this) 3659 elif isinstance(this, exp.WeekStart): 3660 this_sql = self.weekstart_name(this) 3661 else: 3662 this_sql = this.name 3663 expression_sql = self.sql(expression, "expression") 3664 3665 return f"EXTRACT({this_sql} FROM {expression_sql})" 3666 3667 def trim_sql(self, expression: exp.Trim) -> str: 3668 trim_type = self.sql(expression, "position") 3669 3670 if trim_type == "LEADING": 3671 func_name = "LTRIM" 3672 elif trim_type == "TRAILING": 3673 func_name = "RTRIM" 3674 else: 3675 func_name = "TRIM" 3676 3677 return self.func(func_name, expression.this, expression.expression) 3678 3679 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3680 args = expression.expressions 3681 if isinstance(expression, exp.ConcatWs): 3682 args = args[1:] # Skip the delimiter 3683 3684 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3685 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3686 3687 concat_coalesce = ( 3688 self.dialect.CONCAT_WS_COALESCE 3689 if isinstance(expression, exp.ConcatWs) 3690 else self.dialect.CONCAT_COALESCE 3691 ) 3692 3693 if not concat_coalesce and expression.args.get("coalesce"): 3694 3695 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3696 if not e.type: 3697 import sqlglot.optimizer.annotate_types 3698 3699 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3700 3701 if e.is_string or e.is_type(exp.DType.ARRAY): 3702 return e 3703 3704 return exp.func("coalesce", e, exp.Literal.string("")) 3705 3706 args = [_wrap_with_coalesce(e) for e in args] 3707 3708 return args 3709 3710 def concat_sql(self, expression: exp.Concat) -> str: 3711 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3712 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3713 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3714 # instead of coalescing them to empty string. 3715 import sqlglot.dialects.dialect 3716 3717 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3718 3719 expressions = self.convert_concat_args(expression) 3720 3721 # Some dialects don't allow a single-argument CONCAT call 3722 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3723 return self.sql(expressions[0]) 3724 3725 return self.func("CONCAT", *expressions) 3726 3727 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3728 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3729 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3730 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3731 all_args = expression.expressions 3732 expression.set("coalesce", True) 3733 return self.sql( 3734 exp.case() 3735 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3736 .else_(expression) 3737 ) 3738 3739 return self.func( 3740 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3741 ) 3742 3743 def check_sql(self, expression: exp.Check) -> str: 3744 this = self.sql(expression, key="this") 3745 return f"CHECK ({this})" 3746 3747 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3748 expressions = self.expressions(expression, flat=True) 3749 expressions = f" ({expressions})" if expressions else "" 3750 reference = self.sql(expression, "reference") 3751 reference = f" {reference}" if reference else "" 3752 delete = self.sql(expression, "delete") 3753 delete = f" ON DELETE {delete}" if delete else "" 3754 update = self.sql(expression, "update") 3755 update = f" ON UPDATE {update}" if update else "" 3756 options = self.expressions(expression, key="options", flat=True, sep=" ") 3757 options = f" {options}" if options else "" 3758 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3759 3760 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3761 this = self.sql(expression, "this") 3762 this = f" {this}" if this else "" 3763 expressions = self.expressions(expression, flat=True) 3764 include = self.sql(expression, "include") 3765 options = self.expressions(expression, key="options", flat=True, sep=" ") 3766 options = f" {options}" if options else "" 3767 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3768 3769 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3770 self.unsupported("TIMESERIES primary key columns are not supported") 3771 return self.sql(expression, "this") 3772 3773 def if_sql(self, expression: exp.If) -> str: 3774 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3775 3776 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3777 if self.MATCH_AGAINST_TABLE_PREFIX: 3778 expressions = [] 3779 for expr in expression.expressions: 3780 if isinstance(expr, exp.Table): 3781 expressions.append(f"TABLE {self.sql(expr)}") 3782 else: 3783 expressions.append(expr) 3784 else: 3785 expressions = expression.expressions 3786 3787 modifier = expression.args.get("modifier") 3788 modifier = f" {modifier}" if modifier else "" 3789 return ( 3790 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3791 ) 3792 3793 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3794 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3795 3796 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3797 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3798 3799 if self.QUOTE_JSON_PATH: 3800 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3801 3802 return path 3803 3804 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3805 if isinstance(expression, exp.JSONPathPart): 3806 transform = self.TRANSFORMS.get(expression.__class__) 3807 if not callable(transform): 3808 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3809 return "" 3810 3811 return transform(self, expression) 3812 3813 if isinstance(expression, int): 3814 return str(expression) 3815 3816 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3817 escaped = expression.replace("'", "\\'") 3818 escaped = f"\\'{expression}\\'" 3819 else: 3820 escaped = expression.replace('"', '\\"') 3821 escaped = f'"{escaped}"' 3822 3823 return escaped 3824 3825 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3826 return f"{self.sql(expression, 'this')} FORMAT JSON" 3827 3828 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3829 # Output the Teradata column FORMAT override. 3830 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3831 this = self.sql(expression, "this") 3832 fmt = self.sql(expression, "format") 3833 return f"{this} (FORMAT {fmt})" 3834 3835 def _jsonobject_sql( 3836 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3837 ) -> str: 3838 null_handling = expression.args.get("null_handling") 3839 null_handling = f" {null_handling}" if null_handling else "" 3840 3841 unique_keys = expression.args.get("unique_keys") 3842 if unique_keys is not None: 3843 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3844 else: 3845 unique_keys = "" 3846 3847 return_type = self.sql(expression, "return_type") 3848 return_type = f" RETURNING {return_type}" if return_type else "" 3849 encoding = self.sql(expression, "encoding") 3850 encoding = f" ENCODING {encoding}" if encoding else "" 3851 3852 if not name: 3853 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3854 3855 return self.func( 3856 name, 3857 *expression.expressions, 3858 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3859 ) 3860 3861 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3862 null_handling = expression.args.get("null_handling") 3863 null_handling = f" {null_handling}" if null_handling else "" 3864 return_type = self.sql(expression, "return_type") 3865 return_type = f" RETURNING {return_type}" if return_type else "" 3866 strict = " STRICT" if expression.args.get("strict") else "" 3867 return self.func( 3868 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3869 ) 3870 3871 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3872 this = self.sql(expression, "this") 3873 order = self.sql(expression, "order") 3874 null_handling = expression.args.get("null_handling") 3875 null_handling = f" {null_handling}" if null_handling else "" 3876 return_type = self.sql(expression, "return_type") 3877 return_type = f" RETURNING {return_type}" if return_type else "" 3878 strict = " STRICT" if expression.args.get("strict") else "" 3879 return self.func( 3880 "JSON_ARRAYAGG", 3881 this, 3882 suffix=f"{order}{null_handling}{return_type}{strict})", 3883 ) 3884 3885 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3886 path = self.sql(expression, "path") 3887 path = f" PATH {path}" if path else "" 3888 nested_schema = self.sql(expression, "nested_schema") 3889 3890 if nested_schema: 3891 return f"NESTED{path} {nested_schema}" 3892 3893 this = self.sql(expression, "this") 3894 kind = self.sql(expression, "kind") 3895 kind = f" {kind}" if kind else "" 3896 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3897 3898 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3899 return f"{this}{kind}{format_json}{path}{ordinality}" 3900 3901 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3902 return self.func("COLUMNS", *expression.expressions) 3903 3904 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3905 this = self.sql(expression, "this") 3906 path = self.sql(expression, "path") 3907 path = f", {path}" if path else "" 3908 error_handling = expression.args.get("error_handling") 3909 error_handling = f" {error_handling}" if error_handling else "" 3910 empty_handling = expression.args.get("empty_handling") 3911 empty_handling = f" {empty_handling}" if empty_handling else "" 3912 schema = self.sql(expression, "schema") 3913 return self.func( 3914 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3915 ) 3916 3917 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3918 this = self.sql(expression, "this") 3919 kind = self.sql(expression, "kind") 3920 path = self.sql(expression, "path") 3921 path = f" {path}" if path else "" 3922 as_json = " AS JSON" if expression.args.get("as_json") else "" 3923 return f"{this} {kind}{path}{as_json}" 3924 3925 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3926 this = self.sql(expression, "this") 3927 path = self.sql(expression, "path") 3928 path = f", {path}" if path else "" 3929 expressions = self.expressions(expression) 3930 with_ = ( 3931 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3932 if expressions 3933 else "" 3934 ) 3935 return f"OPENJSON({this}{path}){with_}" 3936 3937 def in_sql(self, expression: exp.In) -> str: 3938 query = expression.args.get("query") 3939 unnest = expression.args.get("unnest") 3940 field = expression.args.get("field") 3941 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3942 3943 if query: 3944 in_sql = self.sql(query) 3945 elif unnest: 3946 in_sql = self.in_unnest_op(unnest) 3947 elif field: 3948 in_sql = self.sql(field) 3949 else: 3950 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3951 3952 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3953 3954 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3955 return f"(SELECT {self.sql(unnest)})" 3956 3957 def interval_sql(self, expression: exp.Interval) -> str: 3958 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3959 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 3960 exp.AutoRefreshProperty, 3961 ) 3962 interval_keyword = "INTERVAL" if include_keyword else "" 3963 unit_expression = expression.args.get("unit") 3964 unit = self.sql(unit_expression) if unit_expression else "" 3965 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3966 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3967 unit = f" {unit}" if unit else "" 3968 3969 if self.SINGLE_STRING_INTERVAL: 3970 this = expression.this.name if expression.this else "" 3971 if this: 3972 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 3973 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3974 return f"{interval_keyword}'{this}'{unit}" 3975 return f"{interval_keyword}'{this}{unit}'" 3976 return f"{interval_keyword}{unit}" 3977 3978 this = self.sql(expression, "this") 3979 if this: 3980 if not include_keyword and expression.this.is_string: 3981 this = expression.this.name 3982 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 3983 this = f"({this})" 3984 if include_keyword: 3985 this = f" {this}" 3986 3987 return f"{interval_keyword}{this}{unit}" 3988 3989 def return_sql(self, expression: exp.Return) -> str: 3990 return f"RETURN {self.sql(expression, 'this')}" 3991 3992 def reference_sql(self, expression: exp.Reference) -> str: 3993 this = self.sql(expression, "this") 3994 expressions = self.expressions(expression, flat=True) 3995 expressions = f"({expressions})" if expressions else "" 3996 options = self.expressions(expression, key="options", flat=True, sep=" ") 3997 options = f" {options}" if options else "" 3998 return f"REFERENCES {this}{expressions}{options}" 3999 4000 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4001 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4002 parent = expression.parent 4003 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4004 4005 return self.func( 4006 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4007 ) 4008 4009 def paren_sql(self, expression: exp.Paren) -> str: 4010 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4011 return f"({sql}{self.seg(')', sep='')}" 4012 4013 def neg_sql(self, expression: exp.Neg) -> str: 4014 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4015 this_sql = self.sql(expression, "this") 4016 sep = " " if this_sql[0] == "-" else "" 4017 return f"-{sep}{this_sql}" 4018 4019 def not_sql(self, expression: exp.Not) -> str: 4020 return f"NOT {self.sql(expression, 'this')}" 4021 4022 def alias_sql(self, expression: exp.Alias) -> str: 4023 alias = self.sql(expression, "alias") 4024 alias = f" AS {alias}" if alias else "" 4025 return f"{self.sql(expression, 'this')}{alias}" 4026 4027 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4028 alias = expression.args["alias"] 4029 4030 parent = expression.parent 4031 pivot = parent and parent.parent 4032 4033 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4034 identifier_alias = isinstance(alias, exp.Identifier) 4035 literal_alias = isinstance(alias, exp.Literal) 4036 4037 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4038 alias.replace(exp.Literal.string(alias.output_name)) 4039 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4040 alias.replace(exp.to_identifier(alias.output_name)) 4041 4042 return self.alias_sql(expression) 4043 4044 def aliases_sql(self, expression: exp.Aliases) -> str: 4045 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4046 4047 def atindex_sql(self, expression: exp.AtIndex) -> str: 4048 this = self.sql(expression, "this") 4049 index = self.sql(expression, "expression") 4050 return f"{this} AT {index}" 4051 4052 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4053 this = self.sql(expression, "this") 4054 zone = self.sql(expression, "zone") 4055 return f"{this} AT TIME ZONE {zone}" 4056 4057 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4058 this = self.sql(expression, "this") 4059 zone = self.sql(expression, "zone") 4060 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4061 4062 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4063 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4064 4065 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4066 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4067 4068 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4069 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4070 4071 def add_sql(self, expression: exp.Add) -> str: 4072 return self.binary(expression, "+") 4073 4074 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4075 return self.connector_sql(expression, "AND", stack) 4076 4077 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4078 return self.connector_sql(expression, "OR", stack) 4079 4080 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4081 return self.connector_sql(expression, "XOR", stack) 4082 4083 def connector_sql( 4084 self, 4085 expression: exp.Connector, 4086 op: str, 4087 stack: list[str | exp.Expr] | None = None, 4088 ) -> str: 4089 if stack is not None: 4090 stack.append(expression.right) 4091 if expression.comments and self.comments: 4092 op = self.maybe_comment(op, comments=expression.comments) 4093 4094 stack.extend((op, expression.left)) 4095 return op 4096 4097 stack = [expression] 4098 sqls: list[str] = [] 4099 ops = set() 4100 4101 while stack: 4102 node = stack.pop() 4103 if isinstance(node, exp.Connector): 4104 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4105 else: 4106 sql = self.sql(node) 4107 if sqls and sqls[-1] in ops: 4108 sqls[-1] += f" {sql}" 4109 else: 4110 sqls.append(sql) 4111 4112 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4113 return sep.join(sqls) 4114 4115 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4116 return self.binary(expression, "&") 4117 4118 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4119 return self.binary(expression, "<<") 4120 4121 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4122 return f"~{self.sql(expression, 'this')}" 4123 4124 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4125 return self.binary(expression, "|") 4126 4127 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4128 return self.binary(expression, ">>") 4129 4130 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4131 return self.binary(expression, "^") 4132 4133 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4134 format_sql = self.sql(expression, "format") 4135 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4136 to_sql = self.sql(expression, "to") 4137 to_sql = f" {to_sql}" if to_sql else "" 4138 action = self.sql(expression, "action") 4139 action = f" {action}" if action else "" 4140 default = self.sql(expression, "default") 4141 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4142 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4143 4144 # Base implementation that excludes safe, zone, and target_type metadata args 4145 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4146 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4147 4148 # Base implementation that excludes the safe and default_year metadata args 4149 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4150 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4151 4152 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4153 return self.func( 4154 "PARSE_DATETIME", 4155 expression.this, 4156 expression.args.get("format"), 4157 expression.args.get("zone"), 4158 ) 4159 4160 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4161 zone = self.sql(expression, "this") 4162 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4163 4164 def collate_sql(self, expression: exp.Collate) -> str: 4165 if self.COLLATE_IS_FUNC: 4166 return self.function_fallback_sql(expression) 4167 return self.binary(expression, "COLLATE") 4168 4169 def command_sql(self, expression: exp.Command) -> str: 4170 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4171 4172 def comment_sql(self, expression: exp.Comment) -> str: 4173 this = self.sql(expression, "this") 4174 kind = expression.args["kind"] 4175 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4176 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4177 expression_sql = self.sql(expression, "expression") 4178 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4179 4180 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4181 this = self.sql(expression, "this") 4182 delete = " DELETE" if expression.args.get("delete") else "" 4183 recompress = self.sql(expression, "recompress") 4184 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4185 to_disk = self.sql(expression, "to_disk") 4186 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4187 to_volume = self.sql(expression, "to_volume") 4188 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4189 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4190 4191 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4192 where = self.sql(expression, "where") 4193 group = self.sql(expression, "group") 4194 aggregates = self.expressions(expression, key="aggregates") 4195 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4196 4197 if not (where or group or aggregates) and len(expression.expressions) == 1: 4198 return f"TTL {self.expressions(expression, flat=True)}" 4199 4200 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4201 4202 def transaction_sql(self, expression: exp.Transaction) -> str: 4203 modes = self.expressions(expression, key="modes") 4204 modes = f" {modes}" if modes else "" 4205 return f"BEGIN{modes}" 4206 4207 def commit_sql(self, expression: exp.Commit) -> str: 4208 chain = expression.args.get("chain") 4209 if chain is not None: 4210 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4211 4212 return f"COMMIT{chain or ''}" 4213 4214 def rollback_sql(self, expression: exp.Rollback) -> str: 4215 savepoint = expression.args.get("savepoint") 4216 savepoint = f" TO {savepoint}" if savepoint else "" 4217 return f"ROLLBACK{savepoint}" 4218 4219 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4220 this = self.sql(expression, "this") 4221 4222 exists = "" 4223 if expression.args.get("exists"): 4224 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4225 exists = " IF EXISTS" 4226 else: 4227 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4228 4229 dtype = self.sql(expression, "dtype") 4230 if dtype: 4231 collate = self.sql(expression, "collate") 4232 collate = f" COLLATE {collate}" if collate else "" 4233 using = self.sql(expression, "using") 4234 using = f" USING {using}" if using else "" 4235 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4236 null_constraint = self._alter_column_null_constraint_sql(expression) 4237 4238 return ( 4239 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4240 f"{collate}{using}{null_constraint}" 4241 ) 4242 4243 default = self.sql(expression, "default") 4244 if default: 4245 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4246 4247 comment = self.sql(expression, "comment") 4248 if comment: 4249 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4250 4251 visible = expression.args.get("visible") 4252 if visible: 4253 return f"ALTER COLUMN{exists} {this} SET {visible}" 4254 4255 allow_null = expression.args.get("allow_null") 4256 drop = expression.args.get("drop") 4257 4258 if not drop and not allow_null: 4259 self.unsupported("Unsupported ALTER COLUMN syntax") 4260 4261 if allow_null is not None: 4262 keyword = "DROP" if drop else "SET" 4263 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4264 4265 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4266 4267 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4268 allow_null = expression.args.get("allow_null") 4269 if allow_null is None: 4270 return "" 4271 4272 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4273 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4274 return "" 4275 4276 return " NULL" if allow_null else " NOT NULL" 4277 4278 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4279 this = self.sql(expression, "this") 4280 rename_from = self.sql(expression, "rename_from") 4281 if rename_from: 4282 if not self.SUPPORTS_CHANGE_COLUMN: 4283 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4284 return f"CHANGE COLUMN {rename_from} {this}" 4285 if not self.SUPPORTS_MODIFY_COLUMN: 4286 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4287 return f"MODIFY COLUMN {this}" 4288 4289 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4290 this = self.sql(expression, "this") 4291 4292 visible = expression.args.get("visible") 4293 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4294 4295 return f"ALTER INDEX {this} {visible_sql}" 4296 4297 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4298 this = self.sql(expression, "this") 4299 if not isinstance(expression.this, exp.Var): 4300 this = f"KEY DISTKEY {this}" 4301 return f"ALTER DISTSTYLE {this}" 4302 4303 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4304 compound = " COMPOUND" if expression.args.get("compound") else "" 4305 this = self.sql(expression, "this") 4306 expressions = self.expressions(expression, flat=True) 4307 expressions = f"({expressions})" if expressions else "" 4308 return f"ALTER{compound} SORTKEY {this or expressions}" 4309 4310 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4311 if not self.RENAME_TABLE_WITH_DB: 4312 # Remove db from tables 4313 expression = expression.transform( 4314 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4315 ).assert_is(exp.AlterRename) 4316 this = self.sql(expression, "this") 4317 to_kw = " TO" if include_to else "" 4318 return f"RENAME{to_kw} {this}" 4319 4320 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4321 exists = " IF EXISTS" if expression.args.get("exists") else "" 4322 old_column = self.sql(expression, "this") 4323 new_column = self.sql(expression, "to") 4324 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4325 4326 def alterset_sql(self, expression: exp.AlterSet) -> str: 4327 exprs = self.expressions(expression, flat=True) 4328 if self.ALTER_SET_WRAPPED: 4329 exprs = f"({exprs})" 4330 4331 return f"SET {exprs}" 4332 4333 def alter_sql(self, expression: exp.Alter) -> str: 4334 actions = expression.args["actions"] 4335 4336 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4337 actions[0], exp.ColumnDef 4338 ): 4339 actions_sql = self.expressions(expression, key="actions", flat=True) 4340 actions_sql = f"ADD {actions_sql}" 4341 else: 4342 actions_list = [] 4343 for action in actions: 4344 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4345 action_sql = self.add_column_sql(action) 4346 else: 4347 action_sql = self.sql(action) 4348 if isinstance(action, exp.Query): 4349 action_sql = f"AS {action_sql}" 4350 4351 actions_list.append(action_sql) 4352 4353 actions_sql = self.format_args(*actions_list).lstrip("\n") 4354 4355 iceberg = ( 4356 "ICEBERG " 4357 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4358 else "" 4359 ) 4360 exists = " IF EXISTS" if expression.args.get("exists") else "" 4361 on_cluster = self.sql(expression, "cluster") 4362 on_cluster = f" {on_cluster}" if on_cluster else "" 4363 only = " ONLY" if expression.args.get("only") else "" 4364 options = self.expressions(expression, key="options") 4365 options = f", {options}" if options else "" 4366 kind = self.sql(expression, "kind") 4367 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4368 check = " WITH CHECK" if expression.args.get("check") else "" 4369 cascade = ( 4370 " CASCADE" 4371 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4372 else "" 4373 ) 4374 this = self.sql(expression, "this") 4375 this = f" {this}" if this else "" 4376 4377 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4378 4379 def altersession_sql(self, expression: exp.AlterSession) -> str: 4380 items_sql = self.expressions(expression, flat=True) 4381 keyword = "UNSET" if expression.args.get("unset") else "SET" 4382 return f"{keyword} {items_sql}" 4383 4384 def add_column_sql(self, expression: exp.Expr) -> str: 4385 sql = self.sql(expression) 4386 if isinstance(expression, exp.Schema): 4387 column_text = " COLUMNS" 4388 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4389 column_text = " COLUMN" 4390 else: 4391 column_text = "" 4392 4393 return f"ADD{column_text} {sql}" 4394 4395 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4396 expressions = self.expressions(expression) 4397 exists = " IF EXISTS " if expression.args.get("exists") else " " 4398 return f"DROP{exists}{expressions}" 4399 4400 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4401 return "DROP PRIMARY KEY" 4402 4403 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4404 return f"ADD {self.expressions(expression, indent=False)}" 4405 4406 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4407 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4408 location = self.sql(expression, "location") 4409 location = f" {location}" if location else "" 4410 return f"ADD {exists}{self.sql(expression.this)}{location}" 4411 4412 def distinct_sql(self, expression: exp.Distinct) -> str: 4413 this = self.expressions(expression, flat=True) 4414 4415 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4416 case = exp.case() 4417 for arg in expression.expressions: 4418 case = case.when(arg.is_(exp.null()), exp.null()) 4419 this = self.sql(case.else_(f"({this})")) 4420 4421 this = f" {this}" if this else "" 4422 4423 on = self.sql(expression, "on") 4424 on = f" ON {on}" if on else "" 4425 return f"DISTINCT{this}{on}" 4426 4427 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4428 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4429 4430 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4431 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4432 4433 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4434 this_sql = self.sql(expression, "this") 4435 expression_sql = self.sql(expression, "expression") 4436 kind = "MAX" if expression.args.get("max") else "MIN" 4437 return f"{this_sql} HAVING {kind} {expression_sql}" 4438 4439 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4440 return self.sql( 4441 exp.Cast( 4442 this=exp.Div(this=expression.this, expression=expression.expression), 4443 to=exp.DataType(this=exp.DType.INT), 4444 ) 4445 ) 4446 4447 def dpipe_sql(self, expression: exp.DPipe) -> str: 4448 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4449 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4450 return self.binary(expression, "||") 4451 4452 def div_sql(self, expression: exp.Div) -> str: 4453 l, r = expression.left, expression.right 4454 4455 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4456 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4457 4458 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4459 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4460 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4461 4462 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4463 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4464 return self.sql( 4465 exp.cast( 4466 l / r, 4467 to=exp.DType.BIGINT, 4468 ) 4469 ) 4470 4471 return self.binary(expression, "/") 4472 4473 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4474 n = exp._wrap(expression.this, exp.Binary) 4475 d = exp._wrap(expression.expression, exp.Binary) 4476 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4477 4478 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4479 return self.binary(expression, "OVERLAPS") 4480 4481 def distance_sql(self, expression: exp.Distance) -> str: 4482 return self.binary(expression, "<->") 4483 4484 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4485 return self.binary(expression, "<<->>") 4486 4487 def dot_sql(self, expression: exp.Dot) -> str: 4488 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4489 4490 def eq_sql(self, expression: exp.EQ) -> str: 4491 return self.binary(expression, "=") 4492 4493 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4494 return self.binary(expression, ":=") 4495 4496 def escape_sql(self, expression: exp.Escape) -> str: 4497 this = expression.this 4498 if ( 4499 isinstance(this, (exp.Like, exp.ILike)) 4500 and isinstance(this.expression, (exp.All, exp.Any)) 4501 and not self.SUPPORTS_LIKE_QUANTIFIERS 4502 ): 4503 return self._like_sql(this, escape=expression) 4504 return self.binary(expression, "ESCAPE") 4505 4506 def glob_sql(self, expression: exp.Glob) -> str: 4507 return self.binary(expression, "GLOB") 4508 4509 def gt_sql(self, expression: exp.GT) -> str: 4510 return self.binary(expression, ">") 4511 4512 def gte_sql(self, expression: exp.GTE) -> str: 4513 return self.binary(expression, ">=") 4514 4515 def is_sql(self, expression: exp.Is) -> str: 4516 negate = expression.args.get("negate") 4517 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4518 positive = bool(expression.expression.this) != bool(negate) 4519 return self.sql(expression.this if positive else exp.not_(expression.this)) 4520 return self.binary(expression, "IS NOT" if negate else "IS") 4521 4522 def _like_sql( 4523 self, 4524 expression: exp.Like | exp.ILike, 4525 escape: exp.Escape | None = None, 4526 ) -> str: 4527 this = expression.this 4528 rhs = expression.expression 4529 4530 if isinstance(expression, exp.Like): 4531 exp_class: type[exp.Like | exp.ILike] = exp.Like 4532 op = "LIKE" 4533 else: 4534 exp_class = exp.ILike 4535 op = "ILIKE" 4536 4537 if expression.args.get("negate"): 4538 op = f"NOT {op}" 4539 4540 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4541 exprs = rhs.this.unnest() 4542 4543 if isinstance(exprs, exp.Tuple): 4544 exprs = exprs.expressions 4545 else: 4546 exprs = [exprs] 4547 4548 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4549 4550 def _make_like(expr: exp.Expression) -> exp.Expression: 4551 like: exp.Expression = exp_class( 4552 this=this, expression=expr, negate=expression.args.get("negate") 4553 ) 4554 if escape: 4555 like = exp.Escape(this=like, expression=escape.expression.copy()) 4556 return like 4557 4558 like_expr: exp.Expr = _make_like(exprs[0]) 4559 for expr in exprs[1:]: 4560 like_expr = connective(like_expr, _make_like(expr), copy=False) 4561 4562 parent = escape.parent if escape else expression.parent 4563 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4564 parent, exp.Condition 4565 ): 4566 like_expr = exp.paren(like_expr, copy=False) 4567 4568 return self.sql(like_expr) 4569 4570 return self.binary(expression, op) 4571 4572 def like_sql(self, expression: exp.Like) -> str: 4573 return self._like_sql(expression) 4574 4575 def ilike_sql(self, expression: exp.ILike) -> str: 4576 return self._like_sql(expression) 4577 4578 def match_sql(self, expression: exp.Match) -> str: 4579 return self.binary(expression, "MATCH") 4580 4581 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4582 return self.binary(expression, "SIMILAR TO") 4583 4584 def lt_sql(self, expression: exp.LT) -> str: 4585 return self.binary(expression, "<") 4586 4587 def lte_sql(self, expression: exp.LTE) -> str: 4588 return self.binary(expression, "<=") 4589 4590 def mod_sql(self, expression: exp.Mod) -> str: 4591 return self.binary(expression, "%") 4592 4593 def mul_sql(self, expression: exp.Mul) -> str: 4594 return self.binary(expression, "*") 4595 4596 def neq_sql(self, expression: exp.NEQ) -> str: 4597 return self.binary(expression, "<>") 4598 4599 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4600 return self.binary(expression, "IS NOT DISTINCT FROM") 4601 4602 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4603 return self.binary(expression, "IS DISTINCT FROM") 4604 4605 def sub_sql(self, expression: exp.Sub) -> str: 4606 return self.binary(expression, "-") 4607 4608 def trycast_sql(self, expression: exp.TryCast) -> str: 4609 return self.cast_sql(expression, safe_prefix="TRY_") 4610 4611 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4612 return self.cast_sql(expression) 4613 4614 def try_sql(self, expression: exp.Try) -> str: 4615 if not self.TRY_SUPPORTED: 4616 self.unsupported("Unsupported TRY function") 4617 return self.sql(expression, "this") 4618 4619 return self.func("TRY", expression.this) 4620 4621 def log_sql(self, expression: exp.Log) -> str: 4622 this = expression.this 4623 expr = expression.expression 4624 4625 if self.dialect.LOG_BASE_FIRST is False: 4626 this, expr = expr, this 4627 elif self.dialect.LOG_BASE_FIRST is None and expr: 4628 if this.name in ("2", "10"): 4629 return self.func(f"LOG{this.name}", expr) 4630 4631 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4632 4633 return self.func("LOG", this, expr) 4634 4635 def use_sql(self, expression: exp.Use) -> str: 4636 kind = self.sql(expression, "kind") 4637 kind = f" {kind}" if kind else "" 4638 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4639 this = f" {this}" if this else "" 4640 return f"USE{kind}{this}" 4641 4642 def binary(self, expression: exp.Binary, op: str) -> str: 4643 sqls: list[str] = [] 4644 stack: list[None | str | exp.Expr] = [expression] 4645 binary_type = type(expression) 4646 4647 while stack: 4648 node = stack.pop() 4649 4650 if type(node) is binary_type: 4651 op_func = node.args.get("operator") 4652 if op_func: 4653 op = f"OPERATOR({self.sql(op_func)})" 4654 4655 stack.append(node.args.get("expression")) 4656 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4657 stack.append(node.args.get("this")) 4658 else: 4659 sqls.append(self.sql(node)) 4660 4661 return "".join(sqls) 4662 4663 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4664 to_clause = self.sql(expression, "to") 4665 if to_clause: 4666 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4667 4668 return self.function_fallback_sql(expression) 4669 4670 def function_fallback_sql(self, expression: exp.Func) -> str: 4671 args = [] 4672 4673 for key in expression.arg_types: 4674 arg_value = expression.args.get(key) 4675 4676 if isinstance(arg_value, list): 4677 for value in arg_value: 4678 args.append(value) 4679 elif arg_value is not None: 4680 args.append(arg_value) 4681 4682 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4683 name = expression.meta_get("name") or expression.sql_name() 4684 else: 4685 name = expression.sql_name() 4686 4687 return self.func(name, *args) 4688 4689 def func( 4690 self, 4691 name: str, 4692 *args: t.Any, 4693 prefix: str = "(", 4694 suffix: str = ")", 4695 normalize: bool = True, 4696 ) -> str: 4697 name = self.normalize_func(name) if normalize else name 4698 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4699 4700 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4701 arg_sqls = tuple( 4702 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4703 ) 4704 if self.pretty and self.too_wide(arg_sqls): 4705 return self.indent( 4706 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4707 ) 4708 return sep.join(arg_sqls) 4709 4710 def too_wide(self, args: t.Iterable) -> bool: 4711 return sum(len(arg) for arg in args) > self.max_text_width 4712 4713 def format_time( 4714 self, 4715 expression: exp.Expr, 4716 inverse_time_mapping: dict[str, str] | None = None, 4717 inverse_time_trie: dict | None = None, 4718 ) -> str | None: 4719 return format_time( 4720 self.sql(expression, "format"), 4721 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4722 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4723 ) 4724 4725 def expressions( 4726 self, 4727 expression: exp.Expr | None = None, 4728 key: str | None = None, 4729 sqls: t.Collection[str | exp.Expr] | None = None, 4730 flat: bool = False, 4731 indent: bool = True, 4732 skip_first: bool = False, 4733 skip_last: bool = False, 4734 sep: str = ", ", 4735 prefix: str = "", 4736 dynamic: bool = False, 4737 new_line: bool = False, 4738 ) -> str: 4739 expressions = expression.args.get(key or "expressions") if expression else sqls 4740 4741 if not expressions: 4742 return "" 4743 4744 if flat: 4745 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4746 4747 num_sqls = len(expressions) 4748 result_sqls = [] 4749 4750 for i, e in enumerate(expressions): 4751 sql = self.sql(e, comment=False) 4752 if not sql: 4753 continue 4754 4755 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4756 4757 if self.pretty: 4758 if self.leading_comma: 4759 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4760 else: 4761 result_sqls.append( 4762 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4763 ) 4764 else: 4765 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4766 4767 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4768 if new_line: 4769 result_sqls.insert(0, "") 4770 result_sqls.append("") 4771 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4772 else: 4773 result_sql = "".join(result_sqls) 4774 4775 return ( 4776 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4777 if indent 4778 else result_sql 4779 ) 4780 4781 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4782 flat = flat or isinstance(expression.parent, exp.Properties) 4783 expressions_sql = self.expressions(expression, flat=flat) 4784 if flat: 4785 return f"{op} {expressions_sql}" 4786 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4787 4788 def naked_property(self, expression: exp.Property) -> str: 4789 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4790 if not property_name: 4791 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4792 return f"{property_name} {self.sql(expression, 'this')}" 4793 4794 def tag_sql(self, expression: exp.Tag) -> str: 4795 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4796 4797 def token_sql(self, token_type: TokenType) -> str: 4798 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4799 4800 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4801 this = self.sql(expression, "this") 4802 expressions = self.no_identify(self.expressions, expression) 4803 expressions = ( 4804 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4805 ) 4806 return f"{this}{expressions}" if expressions.strip() != "" else this 4807 4808 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4809 return self.expressions(expression, flat=True) 4810 4811 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4812 params = self.no_identify(self.expressions, expression, flat=True) 4813 body = self.sql(expression, "this") 4814 prefix = "TABLE " if expression.args.get("is_table") else "" 4815 return f"({params}) AS {prefix}{body}" 4816 4817 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4818 this = self.sql(expression, "this") 4819 expressions = self.expressions(expression, flat=True) 4820 return f"{this}({expressions})" 4821 4822 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4823 return self.binary(expression, "=>") 4824 4825 def when_sql(self, expression: exp.When) -> str: 4826 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4827 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4828 condition = self.sql(expression, "condition") 4829 condition = f" AND {condition}" if condition else "" 4830 4831 then_expression = expression.args.get("then") 4832 if isinstance(then_expression, exp.Insert): 4833 this = self.sql(then_expression, "this") 4834 this = f"INSERT {this}" if this else "INSERT" 4835 then = self.sql(then_expression, "expression") 4836 then = f"{this} VALUES {then}" if then else this 4837 elif isinstance(then_expression, exp.Update): 4838 if isinstance(then_expression.args.get("expressions"), exp.Star): 4839 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4840 else: 4841 expressions_sql = self.expressions(then_expression) 4842 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4843 else: 4844 then = self.sql(then_expression) 4845 4846 if isinstance(then_expression, (exp.Insert, exp.Update)): 4847 where = self.sql(then_expression, "where") 4848 if where and not self.SUPPORTS_MERGE_WHERE: 4849 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4850 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4851 where = "" 4852 then = f"{then}{where}" 4853 return f"WHEN {matched}{source}{condition} THEN {then}" 4854 4855 def whens_sql(self, expression: exp.Whens) -> str: 4856 return self.expressions(expression, sep=" ", indent=False) 4857 4858 def merge_sql(self, expression: exp.Merge) -> str: 4859 table = expression.this 4860 table_alias = "" 4861 4862 hints = table.args.get("hints") 4863 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4864 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4865 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4866 4867 this = self.sql(table) 4868 using = f"USING {self.sql(expression, 'using')}" 4869 whens = self.sql(expression, "whens") 4870 4871 on = self.sql(expression, "on") 4872 on = f"ON {on}" if on else "" 4873 4874 if not on: 4875 on = self.expressions(expression, key="using_cond") 4876 on = f"USING ({on})" if on else "" 4877 4878 returning = self.sql(expression, "returning") 4879 if returning: 4880 whens = f"{whens}{returning}" 4881 4882 sep = self.sep() 4883 4884 return self.prepend_ctes( 4885 expression, 4886 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4887 ) 4888 4889 @unsupported_args("format") 4890 def tochar_sql(self, expression: exp.ToChar) -> str: 4891 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4892 4893 @unsupported_args("default") 4894 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4895 if not self.SUPPORTS_TO_NUMBER: 4896 self.unsupported("Unsupported TO_NUMBER function") 4897 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4898 4899 fmt = expression.args.get("format") 4900 if not fmt: 4901 self.unsupported("Conversion format is required for TO_NUMBER") 4902 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4903 4904 return self.func("TO_NUMBER", expression.this, fmt) 4905 4906 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4907 this = self.sql(expression, "this") 4908 kind = self.sql(expression, "kind") 4909 settings_sql = self.expressions(expression, key="settings", sep=" ") 4910 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4911 return f"{this}({kind}{args})" 4912 4913 def dictrange_sql(self, expression: exp.DictRange) -> str: 4914 this = self.sql(expression, "this") 4915 max = self.sql(expression, "max") 4916 min = self.sql(expression, "min") 4917 return f"{this}(MIN {min} MAX {max})" 4918 4919 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4920 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4921 4922 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4923 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4924 4925 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4926 def uniquekeyproperty_sql( 4927 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4928 ) -> str: 4929 return f"{prefix} ({self.expressions(expression, flat=True)})" 4930 4931 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4932 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4933 expressions = self.expressions(expression, flat=True) 4934 expressions = f" {self.wrap(expressions)}" if expressions else "" 4935 buckets = self.sql(expression, "buckets") 4936 kind = self.sql(expression, "kind") 4937 buckets = f" BUCKETS {buckets}" if buckets else "" 4938 order = self.sql(expression, "order") 4939 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4940 4941 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4942 return "" 4943 4944 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4945 expressions = self.expressions(expression, key="expressions", flat=True) 4946 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4947 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4948 buckets = self.sql(expression, "buckets") 4949 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4950 4951 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4952 this = self.sql(expression, "this") 4953 having = self.sql(expression, "having") 4954 4955 if having: 4956 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4957 4958 return self.func("ANY_VALUE", this) 4959 4960 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4961 transform = self.func("TRANSFORM", *expression.expressions) 4962 row_format_before = self.sql(expression, "row_format_before") 4963 row_format_before = f" {row_format_before}" if row_format_before else "" 4964 record_writer = self.sql(expression, "record_writer") 4965 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4966 using = f" USING {self.sql(expression, 'command_script')}" 4967 schema = self.sql(expression, "schema") 4968 schema = f" AS {schema}" if schema else "" 4969 row_format_after = self.sql(expression, "row_format_after") 4970 row_format_after = f" {row_format_after}" if row_format_after else "" 4971 record_reader = self.sql(expression, "record_reader") 4972 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4973 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4974 4975 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4976 key_block_size = self.sql(expression, "key_block_size") 4977 if key_block_size: 4978 return f"KEY_BLOCK_SIZE = {key_block_size}" 4979 4980 using = self.sql(expression, "using") 4981 if using: 4982 return f"USING {using}" 4983 4984 parser = self.sql(expression, "parser") 4985 if parser: 4986 return f"WITH PARSER {parser}" 4987 4988 comment = self.sql(expression, "comment") 4989 if comment: 4990 return f"COMMENT {comment}" 4991 4992 visible = expression.args.get("visible") 4993 if visible is not None: 4994 return "VISIBLE" if visible else "INVISIBLE" 4995 4996 engine_attr = self.sql(expression, "engine_attr") 4997 if engine_attr: 4998 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4999 5000 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5001 if secondary_engine_attr: 5002 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5003 5004 self.unsupported("Unsupported index constraint option.") 5005 return "" 5006 5007 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5008 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5009 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5010 5011 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5012 kind = self.sql(expression, "kind") 5013 kind = f"{kind} INDEX" if kind else "INDEX" 5014 this = self.sql(expression, "this") 5015 this = f" {this}" if this else "" 5016 index_type = self.sql(expression, "index_type") 5017 index_type = f" USING {index_type}" if index_type else "" 5018 expressions = self.expressions(expression, flat=True) 5019 expressions = f" ({expressions})" if expressions else "" 5020 options = self.expressions(expression, key="options", sep=" ") 5021 options = f" {options}" if options else "" 5022 return f"{kind}{this}{index_type}{expressions}{options}" 5023 5024 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5025 if self.NVL2_SUPPORTED: 5026 return self.function_fallback_sql(expression) 5027 5028 case = exp.Case().when( 5029 expression.this.is_(exp.null()).not_(copy=False), 5030 expression.args["true"], 5031 copy=False, 5032 ) 5033 else_cond = expression.args.get("false") 5034 if else_cond: 5035 case.else_(else_cond, copy=False) 5036 5037 return self.sql(case) 5038 5039 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5040 this = self.sql(expression, "this") 5041 expr = self.sql(expression, "expression") 5042 position = self.sql(expression, "position") 5043 position = f", {position}" if position else "" 5044 iterator = self.sql(expression, "iterator") 5045 condition = self.sql(expression, "condition") 5046 condition = f" IF {condition}" if condition else "" 5047 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5048 5049 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5050 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5051 5052 def opclass_sql(self, expression: exp.Opclass) -> str: 5053 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5054 5055 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5056 model = self.sql(expression, "this") 5057 model = f"MODEL {model}" 5058 expr = expression.expression 5059 if expr: 5060 expr_sql = self.sql(expression, "expression") 5061 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5062 else: 5063 expr_sql = None 5064 5065 parameters = self.sql(expression, "params_struct") or None 5066 5067 return self.func(name, model, expr_sql, parameters) 5068 5069 def predict_sql(self, expression: exp.Predict) -> str: 5070 return self._ml_sql(expression, "PREDICT") 5071 5072 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5073 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5074 return self._ml_sql(expression, name) 5075 5076 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5077 return self._ml_sql(expression, "GENERATE_TEXT") 5078 5079 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5080 return self._ml_sql(expression, "GENERATE_TABLE") 5081 5082 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5083 return self._ml_sql(expression, "GENERATE_BOOL") 5084 5085 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5086 return self._ml_sql(expression, "GENERATE_INT") 5087 5088 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5089 return self._ml_sql(expression, "GENERATE_DOUBLE") 5090 5091 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5092 return self._ml_sql(expression, "TRANSLATE") 5093 5094 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5095 return self._ml_sql(expression, "FORECAST") 5096 5097 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5098 this_sql = self.sql(expression, "this") 5099 if isinstance(expression.this, exp.Table): 5100 this_sql = f"TABLE {this_sql}" 5101 5102 return self.func( 5103 "FORECAST", 5104 this_sql, 5105 expression.args.get("data_col"), 5106 expression.args.get("timestamp_col"), 5107 expression.args.get("model"), 5108 expression.args.get("id_cols"), 5109 expression.args.get("horizon"), 5110 expression.args.get("forecast_end_timestamp"), 5111 expression.args.get("confidence_level"), 5112 expression.args.get("output_historical_time_series"), 5113 expression.args.get("context_window"), 5114 ) 5115 5116 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5117 this_sql = self.sql(expression, "this") 5118 if isinstance(expression.this, exp.Table): 5119 this_sql = f"TABLE {this_sql}" 5120 5121 return self.func( 5122 "FEATURES_AT_TIME", 5123 this_sql, 5124 expression.args.get("time"), 5125 expression.args.get("num_rows"), 5126 expression.args.get("ignore_feature_nulls"), 5127 ) 5128 5129 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5130 this_sql = self.sql(expression, "this") 5131 if isinstance(expression.this, exp.Table): 5132 this_sql = f"TABLE {this_sql}" 5133 5134 query_table = self.sql(expression, "query_table") 5135 if isinstance(expression.args["query_table"], exp.Table): 5136 query_table = f"TABLE {query_table}" 5137 5138 return self.func( 5139 "VECTOR_SEARCH", 5140 this_sql, 5141 expression.args.get("column_to_search"), 5142 query_table, 5143 expression.args.get("query_column_to_search"), 5144 expression.args.get("top_k"), 5145 expression.args.get("distance_type"), 5146 expression.args.get("options"), 5147 ) 5148 5149 def forin_sql(self, expression: exp.ForIn) -> str: 5150 this = self.sql(expression, "this") 5151 expression_sql = self.sql(expression, "expression") 5152 return f"FOR {this} DO {expression_sql}" 5153 5154 def refresh_sql(self, expression: exp.Refresh) -> str: 5155 this = self.sql(expression, "this") 5156 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5157 return f"REFRESH {kind}{this}" 5158 5159 def toarray_sql(self, expression: exp.ToArray) -> str: 5160 arg = expression.this 5161 if not arg.type: 5162 import sqlglot.optimizer.annotate_types 5163 5164 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5165 5166 if arg.is_type(exp.DType.ARRAY): 5167 return self.sql(arg) 5168 5169 cond_for_null = arg.is_(exp.null()) 5170 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5171 5172 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5173 this = expression.this 5174 time_format = self.format_time(expression) 5175 5176 if time_format: 5177 return self.sql( 5178 exp.cast( 5179 exp.StrToTime(this=this, format=expression.args["format"]), 5180 exp.DType.TIME, 5181 ) 5182 ) 5183 5184 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5185 return self.sql(this) 5186 5187 return self.sql(exp.cast(this, exp.DType.TIME)) 5188 5189 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5190 this = expression.this 5191 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5192 return self.sql(this) 5193 5194 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5195 5196 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5197 this = expression.this 5198 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5199 return self.sql(this) 5200 5201 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5202 5203 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5204 this = expression.this 5205 time_format = self.format_time(expression) 5206 safe = expression.args.get("safe") 5207 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5208 return self.sql( 5209 exp.cast( 5210 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5211 exp.DType.DATE, 5212 ) 5213 ) 5214 5215 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5216 return self.sql(this) 5217 5218 if safe: 5219 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5220 5221 return self.sql(exp.cast(this, exp.DType.DATE)) 5222 5223 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5224 return self.sql( 5225 exp.func( 5226 "DATEDIFF", 5227 expression.this, 5228 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5229 "day", 5230 ) 5231 ) 5232 5233 def lastday_sql(self, expression: exp.LastDay) -> str: 5234 if self.LAST_DAY_SUPPORTS_DATE_PART: 5235 return self.function_fallback_sql(expression) 5236 5237 unit = expression.args.get("unit") 5238 if unit and unit.name.upper() != "MONTH": 5239 self.unsupported("Date parts are not supported in LAST_DAY.") 5240 5241 return self.func("LAST_DAY", expression.this) 5242 5243 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5244 import sqlglot.dialects.dialect 5245 5246 return self.func( 5247 "DATE_ADD", 5248 expression.this, 5249 expression.expression, 5250 sqlglot.dialects.dialect.unit_to_str(expression), 5251 ) 5252 5253 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5254 if self.CAN_IMPLEMENT_ARRAY_ANY: 5255 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5256 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5257 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5258 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5259 5260 import sqlglot.dialects.dialect 5261 5262 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5263 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5264 self.unsupported("ARRAY_ANY is unsupported") 5265 5266 return self.function_fallback_sql(expression) 5267 5268 def struct_sql(self, expression: exp.Struct) -> str: 5269 expression.set( 5270 "expressions", 5271 [ 5272 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5273 if isinstance(e, exp.PropertyEQ) 5274 else e 5275 for e in expression.expressions 5276 ], 5277 ) 5278 5279 return self.function_fallback_sql(expression) 5280 5281 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5282 low = self.sql(expression, "this") 5283 high = self.sql(expression, "expression") 5284 5285 return f"{low} TO {high}" 5286 5287 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5288 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5289 tables = f" {self.expressions(expression)}" 5290 5291 exists = " IF EXISTS" if expression.args.get("exists") else "" 5292 5293 on_cluster = self.sql(expression, "cluster") 5294 on_cluster = f" {on_cluster}" if on_cluster else "" 5295 5296 identity = self.sql(expression, "identity") 5297 identity = f" {identity} IDENTITY" if identity else "" 5298 5299 option = self.sql(expression, "option") 5300 option = f" {option}" if option else "" 5301 5302 partition = self.sql(expression, "partition") 5303 partition = f" {partition}" if partition else "" 5304 5305 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5306 5307 # This transpiles T-SQL's CONVERT function 5308 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5309 def convert_sql(self, expression: exp.Convert) -> str: 5310 to = expression.this 5311 value = expression.expression 5312 style = expression.args.get("style") 5313 safe = expression.args.get("safe") 5314 strict = expression.args.get("strict") 5315 5316 if not to or not value: 5317 return "" 5318 5319 # Retrieve length of datatype and override to default if not specified 5320 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5321 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5322 5323 transformed: exp.Expr | None = None 5324 cast = exp.Cast if strict else exp.TryCast 5325 5326 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5327 if isinstance(style, exp.Literal) and style.is_int: 5328 import sqlglot.dialects.tsql 5329 5330 style_value = style.name 5331 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5332 if not converted_style: 5333 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5334 5335 fmt = exp.Literal.string(converted_style) 5336 5337 if to.this == exp.DType.DATE: 5338 transformed = exp.StrToDate(this=value, format=fmt) 5339 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5340 transformed = exp.StrToTime(this=value, format=fmt) 5341 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5342 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5343 elif to.this == exp.DType.TEXT: 5344 transformed = exp.TimeToStr(this=value, format=fmt) 5345 5346 if not transformed: 5347 transformed = cast(this=value, to=to, safe=safe) 5348 5349 return self.sql(transformed) 5350 5351 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5352 this = expression.this 5353 if isinstance(this, exp.JSONPathWildcard): 5354 this = self.json_path_part(this) 5355 return f".{this}" if this else "" 5356 5357 quoted = expression.args.get("quoted") 5358 if not ( 5359 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5360 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5361 return f".{this}" 5362 5363 this = self.json_path_part(this) 5364 5365 if quoted and self.QUOTE_JSON_PATH: 5366 # The whole path is rendered as a single quoted string literal, so the bracketed key 5367 # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be 5368 # escaped again for the outer string literal (-> ["x \\"y\\"z"]). 5369 this = self.escape_str(this) 5370 5371 return ( 5372 f"[{this}]" 5373 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5374 else f".{this}" 5375 ) 5376 5377 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5378 this = self.json_path_part(expression.this) 5379 return f"[{this}]" if this else "" 5380 5381 def _simplify_unless_literal(self, expression: E) -> E: 5382 if not isinstance(expression, exp.Literal): 5383 import sqlglot.optimizer.simplify 5384 5385 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5386 5387 return expression 5388 5389 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5390 this = expression.this 5391 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5392 self.unsupported( 5393 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5394 ) 5395 return self.sql(this) 5396 5397 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5398 if self.IGNORE_NULLS_BEFORE_ORDER: 5399 # The first modifier here will be the one closest to the AggFunc's arg 5400 mods = sorted( 5401 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5402 key=lambda x: ( 5403 0 5404 if isinstance(x, exp.HavingMax) 5405 else (1 if isinstance(x, exp.Order) else 2) 5406 ), 5407 ) 5408 5409 if mods: 5410 mod = mods[0] 5411 this = expression.__class__(this=mod.this.copy()) 5412 this.meta["inline"] = True 5413 mod.this.replace(this) 5414 return self.sql(expression.this) 5415 5416 agg_func = expression.find(exp.AggFunc) 5417 5418 if agg_func: 5419 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5420 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5421 5422 return f"{self.sql(expression, 'this')} {text}" 5423 5424 def _replace_line_breaks(self, string: str) -> str: 5425 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5426 if self.pretty: 5427 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5428 return string 5429 5430 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5431 option = self.sql(expression, "this") 5432 5433 if expression.expressions: 5434 upper = option.upper() 5435 5436 # Snowflake FILE_FORMAT options are separated by whitespace 5437 sep = " " if upper == "FILE_FORMAT" else ", " 5438 5439 # Databricks copy/format options do not set their list of values with EQ 5440 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5441 values = self.expressions(expression, flat=True, sep=sep) 5442 return f"{option}{op}({values})" 5443 5444 value = self.sql(expression, "expression") 5445 5446 if not value: 5447 return option 5448 5449 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5450 5451 return f"{option}{op}{value}" 5452 5453 def credentials_sql(self, expression: exp.Credentials) -> str: 5454 cred_expr = expression.args.get("credentials") 5455 if isinstance(cred_expr, exp.Literal): 5456 # Redshift case: CREDENTIALS <string> 5457 credentials = self.sql(expression, "credentials") 5458 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5459 else: 5460 # Snowflake case: CREDENTIALS = (...) 5461 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5462 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5463 5464 storage = self.sql(expression, "storage") 5465 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5466 5467 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5468 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5469 5470 iam_role = self.sql(expression, "iam_role") 5471 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5472 5473 region = self.sql(expression, "region") 5474 region = f" REGION {region}" if region else "" 5475 5476 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5477 5478 def copy_sql(self, expression: exp.Copy) -> str: 5479 this = self.sql(expression, "this") 5480 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5481 5482 credentials = self.sql(expression, "credentials") 5483 credentials = self.seg(credentials) if credentials else "" 5484 files = self.expressions(expression, key="files", flat=True) 5485 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5486 5487 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5488 params = self.expressions( 5489 expression, 5490 key="params", 5491 sep=sep, 5492 new_line=True, 5493 skip_last=True, 5494 skip_first=True, 5495 indent=self.COPY_PARAMS_ARE_WRAPPED, 5496 ) 5497 5498 if params: 5499 if self.COPY_PARAMS_ARE_WRAPPED: 5500 params = f" WITH ({params})" 5501 elif not self.pretty and (files or credentials): 5502 params = f" {params}" 5503 5504 return f"COPY{this}{kind} {files}{credentials}{params}" 5505 5506 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5507 return "" 5508 5509 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5510 on_sql = "ON" if expression.args.get("on") else "OFF" 5511 filter_col: str | None = self.sql(expression, "filter_column") 5512 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5513 retention_period: str | None = self.sql(expression, "retention_period") 5514 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5515 5516 if filter_col or retention_period: 5517 on_sql = self.func("ON", filter_col, retention_period) 5518 5519 return f"DATA_DELETION={on_sql}" 5520 5521 def maskingpolicycolumnconstraint_sql( 5522 self, expression: exp.MaskingPolicyColumnConstraint 5523 ) -> str: 5524 this = self.sql(expression, "this") 5525 expressions = self.expressions(expression, flat=True) 5526 expressions = f" USING ({expressions})" if expressions else "" 5527 return f"MASKING POLICY {this}{expressions}" 5528 5529 def gapfill_sql(self, expression: exp.GapFill) -> str: 5530 this = self.sql(expression, "this") 5531 this = f"TABLE {this}" 5532 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5533 5534 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5535 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5536 5537 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5538 this = self.sql(expression, "this") 5539 expr = expression.expression 5540 5541 if isinstance(expr, exp.Func): 5542 # T-SQL's CLR functions are case sensitive 5543 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5544 else: 5545 expr = self.sql(expression, "expression") 5546 5547 return self.scope_resolution(expr, this) 5548 5549 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5550 if self.PARSE_JSON_NAME is None: 5551 return self.sql(expression.this) 5552 5553 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5554 5555 def rand_sql(self, expression: exp.Rand) -> str: 5556 lower = self.sql(expression, "lower") 5557 upper = self.sql(expression, "upper") 5558 5559 if lower and upper: 5560 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5561 return self.func("RAND", expression.this) 5562 5563 def changes_sql(self, expression: exp.Changes) -> str: 5564 information = self.sql(expression, "information") 5565 information = f"INFORMATION => {information}" 5566 at_before = self.sql(expression, "at_before") 5567 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5568 end = self.sql(expression, "end") 5569 end = f"{self.seg('')}{end}" if end else "" 5570 5571 return f"CHANGES ({information}){at_before}{end}" 5572 5573 def pad_sql(self, expression: exp.Pad) -> str: 5574 prefix = "L" if expression.args.get("is_left") else "R" 5575 5576 fill_pattern = self.sql(expression, "fill_pattern") or None 5577 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5578 fill_pattern = "' '" 5579 5580 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5581 5582 def summarize_sql(self, expression: exp.Summarize) -> str: 5583 table = " TABLE" if expression.args.get("table") else "" 5584 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5585 5586 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5587 generate_series = exp.GenerateSeries(**expression.args) 5588 5589 parent = expression.parent 5590 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5591 parent = parent.parent 5592 5593 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5594 return self.sql(exp.Unnest(expressions=[generate_series])) 5595 5596 if isinstance(parent, exp.Select): 5597 self.unsupported("GenerateSeries projection unnesting is not supported.") 5598 5599 return self.sql(generate_series) 5600 5601 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5602 if self.SUPPORTS_CONVERT_TIMEZONE: 5603 return self.function_fallback_sql(expression) 5604 5605 source_tz = expression.args.get("source_tz") 5606 target_tz = expression.args.get("target_tz") 5607 timestamp = expression.args.get("timestamp") 5608 5609 if source_tz and timestamp: 5610 timestamp = exp.AtTimeZone( 5611 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5612 ) 5613 5614 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5615 5616 return self.sql(expr) 5617 5618 def json_sql(self, expression: exp.JSON) -> str: 5619 this = self.sql(expression, "this") 5620 this = f" {this}" if this else "" 5621 5622 _with = expression.args.get("with_") 5623 5624 if _with is None: 5625 with_sql = "" 5626 elif not _with: 5627 with_sql = " WITHOUT" 5628 else: 5629 with_sql = " WITH" 5630 5631 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5632 5633 return f"JSON{this}{with_sql}{unique_sql}" 5634 5635 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5636 path = self.sql(expression, "path") 5637 returning = self.sql(expression, "returning") 5638 returning = f" RETURNING {returning}" if returning else "" 5639 5640 on_condition = self.sql(expression, "on_condition") 5641 on_condition = f" {on_condition}" if on_condition else "" 5642 5643 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5644 5645 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5646 regexp = " REGEXP" if expression.args.get("regexp") else "" 5647 return f"SKIP{regexp} {self.sql(expression.expression)}" 5648 5649 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5650 else_ = "ELSE " if expression.args.get("else_") else "" 5651 condition = self.sql(expression, "expression") 5652 condition = f"WHEN {condition} THEN " if condition else else_ 5653 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5654 return f"{condition}{insert}" 5655 5656 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5657 kind = self.sql(expression, "kind") 5658 expressions = self.seg(self.expressions(expression, sep=" ")) 5659 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5660 return res 5661 5662 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5663 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5664 empty = expression.args.get("empty") 5665 empty = ( 5666 f"DEFAULT {empty} ON EMPTY" 5667 if isinstance(empty, exp.Expr) 5668 else self.sql(expression, "empty") 5669 ) 5670 5671 error = expression.args.get("error") 5672 error = ( 5673 f"DEFAULT {error} ON ERROR" 5674 if isinstance(error, exp.Expr) 5675 else self.sql(expression, "error") 5676 ) 5677 5678 if error and empty: 5679 error = ( 5680 f"{empty} {error}" 5681 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5682 else f"{error} {empty}" 5683 ) 5684 empty = "" 5685 5686 null = self.sql(expression, "null") 5687 5688 return f"{empty}{error}{null}" 5689 5690 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5691 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5692 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5693 5694 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5695 this = self.sql(expression, "this") 5696 path = self.sql(expression, "path") 5697 5698 passing = self.expressions(expression, "passing") 5699 passing = f" PASSING {passing}" if passing else "" 5700 5701 on_condition = self.sql(expression, "on_condition") 5702 on_condition = f" {on_condition}" if on_condition else "" 5703 5704 path = f"{path}{passing}{on_condition}" 5705 5706 return self.func("JSON_EXISTS", this, path) 5707 5708 def _add_arrayagg_null_filter( 5709 self, 5710 array_agg_sql: str, 5711 array_agg_expr: exp.ArrayAgg, 5712 column_expr: exp.Expr, 5713 ) -> str: 5714 """ 5715 Add NULL filter to ARRAY_AGG if dialect requires it. 5716 5717 Args: 5718 array_agg_sql: The generated ARRAY_AGG SQL string 5719 array_agg_expr: The ArrayAgg expression node 5720 column_expr: The column/expression to filter (before ORDER BY wrapping) 5721 5722 Returns: 5723 SQL string with FILTER clause added if needed 5724 """ 5725 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5726 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5727 if not ( 5728 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5729 ): 5730 return array_agg_sql 5731 5732 parent = array_agg_expr.parent 5733 if isinstance(parent, exp.Filter): 5734 parent_cond = parent.expression.this 5735 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5736 elif column_expr.find(exp.Column): 5737 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5738 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5739 this_sql = ( 5740 self.expressions(column_expr) 5741 if isinstance(column_expr, exp.Distinct) 5742 else self.sql(column_expr) 5743 ) 5744 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5745 5746 return array_agg_sql 5747 5748 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5749 array_agg = self.function_fallback_sql(expression) 5750 column_expr = expression.this 5751 if isinstance(column_expr, exp.Order): 5752 column_expr = column_expr.this 5753 5754 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5755 5756 def slice_sql(self, expression: exp.Slice) -> str: 5757 step = self.sql(expression, "step") 5758 end = self.sql(expression.expression) 5759 begin = self.sql(expression.this) 5760 5761 sql = f"{end}:{step}" if step else end 5762 return f"{begin}:{sql}" if sql else f"{begin}:" 5763 5764 def apply_sql(self, expression: exp.Apply) -> str: 5765 this = self.sql(expression, "this") 5766 expr = self.sql(expression, "expression") 5767 5768 return f"{this} APPLY({expr})" 5769 5770 def _grant_or_revoke_sql( 5771 self, 5772 expression: exp.Grant | exp.Revoke, 5773 keyword: str, 5774 preposition: str, 5775 grant_option_prefix: str = "", 5776 grant_option_suffix: str = "", 5777 ) -> str: 5778 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5779 5780 kind = self.sql(expression, "kind") 5781 kind = f" {kind}" if kind else "" 5782 5783 securable = self.sql(expression, "securable") 5784 securable = f" {securable}" if securable else "" 5785 5786 principals = self.expressions(expression, key="principals", flat=True) 5787 5788 if not expression.args.get("grant_option"): 5789 grant_option_prefix = grant_option_suffix = "" 5790 5791 # cascade for revoke only 5792 cascade = self.sql(expression, "cascade") 5793 cascade = f" {cascade}" if cascade else "" 5794 5795 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5796 5797 def grant_sql(self, expression: exp.Grant) -> str: 5798 return self._grant_or_revoke_sql( 5799 expression, 5800 keyword="GRANT", 5801 preposition="TO", 5802 grant_option_suffix=" WITH GRANT OPTION", 5803 ) 5804 5805 def revoke_sql(self, expression: exp.Revoke) -> str: 5806 return self._grant_or_revoke_sql( 5807 expression, 5808 keyword="REVOKE", 5809 preposition="FROM", 5810 grant_option_prefix="GRANT OPTION FOR ", 5811 ) 5812 5813 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5814 this = self.sql(expression, "this") 5815 columns = self.expressions(expression, flat=True) 5816 columns = f"({columns})" if columns else "" 5817 5818 return f"{this}{columns}" 5819 5820 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5821 this = self.sql(expression, "this") 5822 5823 kind = self.sql(expression, "kind") 5824 kind = f"{kind} " if kind else "" 5825 5826 return f"{kind}{this}" 5827 5828 def columns_sql(self, expression: exp.Columns) -> str: 5829 func = self.function_fallback_sql(expression) 5830 if expression.args.get("unpack"): 5831 func = f"*{func}" 5832 5833 return func 5834 5835 def overlay_sql(self, expression: exp.Overlay) -> str: 5836 this = self.sql(expression, "this") 5837 expr = self.sql(expression, "expression") 5838 from_sql = self.sql(expression, "from_") 5839 for_sql = self.sql(expression, "for_") 5840 for_sql = f" FOR {for_sql}" if for_sql else "" 5841 5842 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5843 5844 @unsupported_args("format") 5845 def todouble_sql(self, expression: exp.ToDouble) -> str: 5846 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5847 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5848 5849 def string_sql(self, expression: exp.String) -> str: 5850 this = expression.this 5851 zone = expression.args.get("zone") 5852 5853 if zone: 5854 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5855 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5856 # set for source_tz to transpile the time conversion before the STRING cast 5857 this = exp.ConvertTimezone( 5858 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5859 ) 5860 5861 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5862 5863 def median_sql(self, expression: exp.Median) -> str: 5864 if not self.SUPPORTS_MEDIAN: 5865 return self.sql( 5866 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5867 ) 5868 5869 return self.function_fallback_sql(expression) 5870 5871 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5872 filler = self.sql(expression, "this") 5873 filler = f" {filler}" if filler else "" 5874 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5875 return f"TRUNCATE{filler} {with_count}" 5876 5877 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5878 if self.SUPPORTS_UNIX_SECONDS: 5879 return self.function_fallback_sql(expression) 5880 5881 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5882 5883 return self.sql( 5884 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5885 ) 5886 5887 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5888 dim = expression.expression 5889 5890 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5891 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5892 if not (dim.is_int and dim.name == "1"): 5893 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5894 dim = None 5895 5896 # If dimension is required but not specified, default initialize it 5897 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5898 dim = exp.Literal.number(1) 5899 5900 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5901 5902 def attach_sql(self, expression: exp.Attach) -> str: 5903 this = self.sql(expression, "this") 5904 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5905 expressions = self.expressions(expression) 5906 expressions = f" ({expressions})" if expressions else "" 5907 5908 return f"ATTACH{exists_sql} {this}{expressions}" 5909 5910 def detach_sql(self, expression: exp.Detach) -> str: 5911 kind = self.sql(expression, "kind") 5912 kind = f" {kind}" if kind else "" 5913 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5914 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5915 exists = " IF EXISTS" if expression.args.get("exists") else "" 5916 if exists: 5917 kind = kind or " DATABASE" 5918 5919 this = self.sql(expression, "this") 5920 this = f" {this}" if this else "" 5921 cluster = self.sql(expression, "cluster") 5922 cluster = f" {cluster}" if cluster else "" 5923 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5924 sync = " SYNC" if expression.args.get("sync") else "" 5925 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5926 5927 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5928 this = self.sql(expression, "this") 5929 value = self.sql(expression, "expression") 5930 value = f" {value}" if value else "" 5931 return f"{this}{value}" 5932 5933 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5934 return ( 5935 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5936 ) 5937 5938 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5939 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5940 encode = f"{encode} {self.sql(expression, 'this')}" 5941 5942 properties = expression.args.get("properties") 5943 if properties: 5944 encode = f"{encode} {self.properties(properties)}" 5945 5946 return encode 5947 5948 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5949 this = self.sql(expression, "this") 5950 include = f"INCLUDE {this}" 5951 5952 column_def = self.sql(expression, "column_def") 5953 if column_def: 5954 include = f"{include} {column_def}" 5955 5956 alias = self.sql(expression, "alias") 5957 if alias: 5958 include = f"{include} AS {alias}" 5959 5960 return include 5961 5962 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5963 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5964 name = f"{prefix} {self.sql(expression, 'this')}" 5965 return self.func("XMLELEMENT", name, *expression.expressions) 5966 5967 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5968 this = self.sql(expression, "this") 5969 expr = self.sql(expression, "expression") 5970 expr = f"({expr})" if expr else "" 5971 return f"{this}{expr}" 5972 5973 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5974 partitions = self.expressions(expression, "partition_expressions") 5975 create = self.expressions(expression, "create_expressions") 5976 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5977 5978 def partitionbyrangepropertydynamic_sql( 5979 self, expression: exp.PartitionByRangePropertyDynamic 5980 ) -> str: 5981 start = self.sql(expression, "start") 5982 end = self.sql(expression, "end") 5983 5984 every = expression.args["every"] 5985 if isinstance(every, exp.Interval) and every.this.is_string: 5986 every.this.replace(exp.Literal.number(every.name)) 5987 5988 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5989 5990 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5991 name = self.sql(expression, "this") 5992 values = self.expressions(expression, flat=True) 5993 5994 return f"NAME {name} VALUE {values}" 5995 5996 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5997 kind = self.sql(expression, "kind") 5998 sample = self.sql(expression, "sample") 5999 return f"SAMPLE {sample} {kind}" 6000 6001 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6002 kind = self.sql(expression, "kind") 6003 option = self.sql(expression, "option") 6004 option = f" {option}" if option else "" 6005 this = self.sql(expression, "this") 6006 this = f" {this}" if this else "" 6007 columns = self.expressions(expression) 6008 columns = f" {columns}" if columns else "" 6009 return f"{kind}{option} STATISTICS{this}{columns}" 6010 6011 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6012 this = self.sql(expression, "this") 6013 columns = self.expressions(expression) 6014 inner_expression = self.sql(expression, "expression") 6015 inner_expression = f" {inner_expression}" if inner_expression else "" 6016 update_options = self.sql(expression, "update_options") 6017 update_options = f" {update_options} UPDATE" if update_options else "" 6018 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6019 6020 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6021 kind = self.sql(expression, "kind") 6022 kind = f" {kind}" if kind else "" 6023 return f"DELETE{kind} STATISTICS" 6024 6025 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6026 inner_expression = self.sql(expression, "expression") 6027 return f"LIST CHAINED ROWS{inner_expression}" 6028 6029 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6030 kind = self.sql(expression, "kind") 6031 this = self.sql(expression, "this") 6032 this = f" {this}" if this else "" 6033 inner_expression = self.sql(expression, "expression") 6034 return f"VALIDATE {kind}{this}{inner_expression}" 6035 6036 def analyze_sql(self, expression: exp.Analyze) -> str: 6037 options = self.expressions(expression, key="options", sep=" ") 6038 options = f" {options}" if options else "" 6039 kind = self.sql(expression, "kind") 6040 kind = f" {kind}" if kind else "" 6041 this = self.sql(expression, "this") 6042 this = f" {this}" if this else "" 6043 mode = self.sql(expression, "mode") 6044 mode = f" {mode}" if mode else "" 6045 properties = self.sql(expression, "properties") 6046 properties = f" {properties}" if properties else "" 6047 partition = self.sql(expression, "partition") 6048 partition = f" {partition}" if partition else "" 6049 inner_expression = self.sql(expression, "expression") 6050 inner_expression = f" {inner_expression}" if inner_expression else "" 6051 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 6052 6053 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6054 this = self.sql(expression, "this") 6055 namespaces = self.expressions(expression, key="namespaces") 6056 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6057 passing = self.expressions(expression, key="passing") 6058 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6059 columns = self.expressions(expression, key="columns") 6060 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6061 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6062 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6063 6064 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6065 this = self.sql(expression, "this") 6066 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6067 6068 def export_sql(self, expression: exp.Export) -> str: 6069 this = self.sql(expression, "this") 6070 connection = self.sql(expression, "connection") 6071 connection = f"WITH CONNECTION {connection} " if connection else "" 6072 options = self.sql(expression, "options") 6073 return f"EXPORT DATA {connection}{options} AS {this}" 6074 6075 def declare_sql(self, expression: exp.Declare) -> str: 6076 replace = "OR REPLACE " if expression.args.get("replace") else "" 6077 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6078 6079 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6080 variables = self.expressions(expression, "this") 6081 default = self.sql(expression, "default") 6082 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6083 6084 kind = self.sql(expression, "kind") 6085 if isinstance(expression.args.get("kind"), exp.Schema): 6086 kind = f"TABLE {kind}" 6087 6088 kind = f" {kind}" if kind else "" 6089 6090 return f"{variables}{kind}{default}" 6091 6092 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6093 kind = self.sql(expression, "kind") 6094 this = self.sql(expression, "this") 6095 set = self.sql(expression, "expression") 6096 using = self.sql(expression, "using") 6097 using = f" USING {using}" if using else "" 6098 6099 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6100 6101 return f"{kind_sql} {this} SET {set}{using}" 6102 6103 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6104 params = self.expressions(expression, key="params", flat=True) 6105 return self.func(expression.name, *expression.expressions) + f"({params})" 6106 6107 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6108 return self.func(expression.name, *expression.expressions) 6109 6110 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6111 return self.anonymousaggfunc_sql(expression) 6112 6113 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6114 return self.parameterizedagg_sql(expression) 6115 6116 def show_sql(self, expression: exp.Show) -> str: 6117 self.unsupported("Unsupported SHOW statement") 6118 return "" 6119 6120 def install_sql(self, expression: exp.Install) -> str: 6121 self.unsupported("Unsupported INSTALL statement") 6122 return "" 6123 6124 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6125 # Snowflake GET/PUT statements: 6126 # PUT <file> <internalStage> <properties> 6127 # GET <internalStage> <file> <properties> 6128 props = expression.args.get("properties") 6129 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6130 this = self.sql(expression, "this") 6131 target = self.sql(expression, "target") 6132 6133 if isinstance(expression, exp.Put): 6134 return f"PUT {this} {target}{props_sql}" 6135 else: 6136 return f"GET {target} {this}{props_sql}" 6137 6138 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6139 this = self.sql(expression, "this") 6140 expr = self.sql(expression, "expression") 6141 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6142 return f"TRANSLATE({this} USING {expr}{with_error})" 6143 6144 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6145 if self.SUPPORTS_DECODE_CASE: 6146 return self.func("DECODE", *expression.expressions) 6147 6148 decode_expr, *expressions = expression.expressions 6149 6150 ifs = [] 6151 for search, result in zip(expressions[::2], expressions[1::2]): 6152 if isinstance(search, exp.Literal): 6153 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6154 elif isinstance(search, exp.Null): 6155 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6156 else: 6157 if isinstance(search, exp.Binary): 6158 search = exp.paren(search) 6159 6160 cond = exp.or_( 6161 decode_expr.eq(search), 6162 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6163 copy=False, 6164 ) 6165 ifs.append(exp.If(this=cond, true=result)) 6166 6167 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6168 return self.sql(case) 6169 6170 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6171 this = self.sql(expression, "this") 6172 this = self.seg(this, sep="") 6173 dimensions = self.expressions( 6174 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6175 ) 6176 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6177 metrics = self.expressions( 6178 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6179 ) 6180 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6181 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6182 facts = self.seg(f"FACTS {facts}") if facts else "" 6183 where = self.sql(expression, "where") 6184 where = self.seg(f"WHERE {where}") if where else "" 6185 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6186 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6187 6188 def getextract_sql(self, expression: exp.GetExtract) -> str: 6189 this = expression.this 6190 expr = expression.expression 6191 6192 if not this.type or not expression.type: 6193 import sqlglot.optimizer.annotate_types 6194 6195 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6196 6197 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6198 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6199 6200 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6201 6202 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6203 return self.sql( 6204 exp.DateAdd( 6205 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6206 expression=expression.this, 6207 unit=exp.var("DAY"), 6208 ) 6209 ) 6210 6211 def space_sql(self: Generator, expression: exp.Space) -> str: 6212 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6213 6214 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6215 return f"BUILD {self.sql(expression, 'this')}" 6216 6217 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6218 method = self.sql(expression, "method") 6219 kind = expression.args.get("kind") 6220 if not kind: 6221 return f"REFRESH {method}" 6222 6223 every = self.sql(expression, "every") 6224 unit = self.sql(expression, "unit") 6225 every = f" EVERY {every} {unit}" if every else "" 6226 starts = self.sql(expression, "starts") 6227 starts = f" STARTS {starts}" if starts else "" 6228 6229 return f"REFRESH {method} ON {kind}{every}{starts}" 6230 6231 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6232 self.unsupported("The model!attribute syntax is not supported") 6233 return "" 6234 6235 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6236 return self.func("DIRECTORY", expression.this) 6237 6238 def uuid_sql(self, expression: exp.Uuid) -> str: 6239 is_string = expression.args.get("is_string", False) 6240 uuid_func_sql = self.func("UUID") 6241 6242 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6243 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6244 6245 return uuid_func_sql 6246 6247 def initcap_sql(self, expression: exp.Initcap) -> str: 6248 delimiters = expression.expression 6249 6250 if delimiters: 6251 # do not generate delimiters arg if we are round-tripping from default delimiters 6252 if ( 6253 delimiters.is_string 6254 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6255 ): 6256 delimiters = None 6257 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6258 self.unsupported("INITCAP does not support custom delimiters") 6259 delimiters = None 6260 6261 return self.func("INITCAP", expression.this, delimiters) 6262 6263 def localtime_sql(self, expression: exp.Localtime) -> str: 6264 this = expression.this 6265 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6266 6267 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6268 this = expression.this 6269 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6270 6271 def weekstart_name(self, expression: exp.WeekStart) -> str: 6272 import sqlglot.dialects.dialect 6273 6274 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6275 this = expression.this.name.upper() 6276 6277 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6278 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6279 6280 if dow_from_week_start_day != dow_from_week_offset: 6281 self.unsupported( 6282 f"WEEK({this}) is not supported; falling back to the default week start day" 6283 ) 6284 6285 return "WEEK" 6286 6287 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6288 name = self.weekstart_name(expression) 6289 6290 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6291 if isinstance(expression.parent, exp.DateTrunc): 6292 return self.sql(exp.Literal.string(name)) 6293 6294 return name 6295 6296 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6297 this = self.expressions(expression) 6298 charset = self.sql(expression, "charset") 6299 using = f" USING {charset}" if charset else "" 6300 return self.func(name, this + using) 6301 6302 def block_sql(self, expression: exp.Block) -> str: 6303 expressions = self.expressions(expression, sep="; ", flat=True) 6304 begin = "BEGIN " if expression.args.get("begin") else "" 6305 return f"{begin}{expressions}" if expressions else "" 6306 6307 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6308 self.unsupported("Unsupported Inline UDFs syntax") 6309 return "" 6310 6311 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6312 self.unsupported("Unsupported Stored Procedure syntax") 6313 return "" 6314 6315 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6316 self.unsupported("Unsupported If block syntax") 6317 return "" 6318 6319 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6320 self.unsupported("Unsupported Case statement syntax") 6321 return "" 6322 6323 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6324 self.unsupported("Unsupported While block syntax") 6325 return "" 6326 6327 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6328 self.unsupported("Unsupported Loop block syntax") 6329 return "" 6330 6331 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6332 self.unsupported("Unsupported Repeat block syntax") 6333 return "" 6334 6335 def leave_sql(self, expression: exp.Leave) -> str: 6336 self.unsupported("Unsupported Leave syntax") 6337 return "" 6338 6339 def iterate_sql(self, expression: exp.Iterate) -> str: 6340 self.unsupported("Unsupported Iterate syntax") 6341 return "" 6342 6343 def execute_sql(self, expression: exp.Execute) -> str: 6344 self.unsupported("Unsupported Execute syntax") 6345 return "" 6346 6347 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6348 self.unsupported("Unsupported Execute syntax") 6349 return "" 6350 6351 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6352 props = self.expressions(expression, sep=" ") 6353 return f"MODIFY {props}" 6354 6355 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6356 kind = expression.args.get("kind") 6357 return f"USING {kind} {self.sql(expression, 'this')}" 6358 6359 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6360 this = self.sql(expression, "this") 6361 to = self.sql(expression, "to") 6362 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)
882 def __init__( 883 self, 884 pretty: bool | int | None = None, 885 identify: str | bool = False, 886 normalize: bool = False, 887 pad: int = 2, 888 indent: int = 2, 889 normalize_functions: str | bool | None = None, 890 unsupported_level: ErrorLevel = ErrorLevel.WARN, 891 max_unsupported: int = 3, 892 leading_comma: bool = False, 893 max_text_width: int = 80, 894 comments: bool = True, 895 dialect: DialectType = None, 896 ): 897 import sqlglot 898 import sqlglot.dialects.dialect 899 900 self.pretty = pretty if pretty is not None else sqlglot.pretty 901 self.identify = identify 902 self.normalize = normalize 903 self.pad = pad 904 self._indent = indent 905 self.unsupported_level = unsupported_level 906 self.max_unsupported = max_unsupported 907 self.leading_comma = leading_comma 908 self.max_text_width = max_text_width 909 self.comments = comments 910 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 911 912 # This is both a Dialect property and a Generator argument, so we prioritize the latter 913 self.normalize_functions = ( 914 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 915 ) 916 917 self.unsupported_messages: list[str] = [] 918 self._escaped_quote_end: str = ( 919 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 920 ) 921 self._escaped_byte_quote_end: str = ( 922 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 923 if self.dialect.BYTE_END 924 else "" 925 ) 926 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 927 928 self._next_name = name_sequence("_t") 929 930 self._identifier_start = self.dialect.IDENTIFIER_START 931 self._identifier_end = self.dialect.IDENTIFIER_END 932 933 self._quote_json_path_key_using_brackets = True 934 935 cls = type(self) 936 dispatch = _DISPATCH_CACHE.get(cls) 937 if dispatch is None: 938 dispatch = _build_dispatch(cls) 939 _DISPATCH_CACHE[cls] = dispatch 940 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.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <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.CHAR: 'CHAR'>, <DType.NCHAR: 'NCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
942 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 943 """ 944 Generates the SQL string corresponding to the given syntax tree. 945 946 Args: 947 expression: The syntax tree. 948 copy: Whether to copy the expression. The generator performs mutations so 949 it is safer to copy. 950 951 Returns: 952 The SQL string corresponding to `expression`. 953 """ 954 if copy: 955 expression = expression.copy() 956 957 expression = self.preprocess(expression) 958 959 self.unsupported_messages = [] 960 sql = self.sql(expression).strip() 961 962 if self.pretty: 963 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 964 965 if self.unsupported_level == ErrorLevel.IGNORE: 966 return sql 967 968 if self.unsupported_level == ErrorLevel.WARN: 969 for msg in self.unsupported_messages: 970 logger.warning(msg) 971 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 972 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 973 974 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.
976 def preprocess(self, expression: exp.Expr) -> exp.Expr: 977 """Apply generic preprocessing transformations to a given expression.""" 978 expression = self._move_ctes_to_top_level(expression) 979 980 if self.ENSURE_BOOLS: 981 import sqlglot.transforms 982 983 expression = sqlglot.transforms.ensure_bools(expression) 984 985 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1009 def sanitize_comment(self, comment: str) -> str: 1010 comment = " " + comment if comment[0].strip() else comment 1011 comment = comment + " " if comment[-1].strip() else comment 1012 1013 # Escape block comment markers to prevent premature closure or unintended nesting. 1014 # This is necessary because single-line comments (--) are converted to block comments 1015 # (/* */) on output, and any */ in the original text would close the comment early. 1016 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1017 1018 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1020 def maybe_comment( 1021 self, 1022 sql: str, 1023 expression: exp.Expr | None = None, 1024 comments: list[str] | None = None, 1025 separated: bool = False, 1026 ) -> str: 1027 comments = ( 1028 ((expression and expression.comments) if comments is None else comments) # type: ignore 1029 if self.comments 1030 else None 1031 ) 1032 1033 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1034 return sql 1035 1036 comments_list = [ 1037 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1038 for comment in comments 1039 if comment 1040 ] 1041 1042 if not comments_list: 1043 return sql 1044 1045 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1046 comments_sql = self.sep().join(comments_list) 1047 return ( 1048 f"{self.sep()}{comments_sql}{sql}" 1049 if not sql or sql[0].isspace() 1050 else f"{comments_sql}{self.sep()}{sql}" 1051 ) 1052 1053 return f"{sql} {' '.join(comments_list)}"
1055 def wrap(self, expression: exp.Expr | str) -> str: 1056 this_sql = ( 1057 self.sql(expression) 1058 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1059 else self.sql(expression, "this") 1060 ) 1061 if not this_sql: 1062 return "()" 1063 1064 this_sql = self.indent(this_sql, level=1, pad=0) 1065 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:
1081 def indent( 1082 self, 1083 sql: str, 1084 level: int = 0, 1085 pad: int | None = None, 1086 skip_first: bool = False, 1087 skip_last: bool = False, 1088 ) -> str: 1089 if not self.pretty or not sql: 1090 return sql 1091 1092 pad = self.pad if pad is None else pad 1093 lines = sql.split("\n") 1094 1095 return "\n".join( 1096 ( 1097 line 1098 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1099 else f"{' ' * (level * self._indent + pad)}{line}" 1100 ) 1101 for i, line in enumerate(lines) 1102 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1104 def sql( 1105 self, 1106 expression: str | exp.Expr | None, 1107 key: str | None = None, 1108 comment: bool = True, 1109 ) -> str: 1110 if not expression: 1111 return "" 1112 1113 if isinstance(expression, str): 1114 return expression 1115 1116 if key: 1117 value = expression.args.get(key) 1118 if value: 1119 return self.sql(value) 1120 return "" 1121 1122 handler = self._dispatch.get(expression.__class__) 1123 1124 if handler: 1125 sql = handler(self, expression) 1126 elif isinstance(expression, exp.Func): 1127 sql = self.function_fallback_sql(expression) 1128 elif isinstance(expression, exp.Property): 1129 sql = self.property_sql(expression) 1130 else: 1131 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1132 1133 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1140 def cache_sql(self, expression: exp.Cache) -> str: 1141 lazy = " LAZY" if expression.args.get("lazy") else "" 1142 table = self.sql(expression, "this") 1143 options = expression.args.get("options") 1144 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1145 sql = self.sql(expression, "expression") 1146 sql = f" AS{self.sep()}{sql}" if sql else "" 1147 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1148 return self.prepend_ctes(expression, sql)
1154 def column_parts(self, expression: exp.Column) -> str: 1155 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1156 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1157 return self.sql(expression, "this") 1158 1159 return ".".join( 1160 self.sql(part) 1161 for part in ( 1162 expression.args.get("catalog"), 1163 expression.args.get("db"), 1164 expression.args.get("table"), 1165 expression.args.get("this"), 1166 ) 1167 if part 1168 )
1170 def column_sql(self, expression: exp.Column) -> str: 1171 join_mark = " (+)" if expression.args.get("join_mark") else "" 1172 1173 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1174 join_mark = "" 1175 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1176 1177 return f"{self.column_parts(expression)}{join_mark}"
1188 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1189 column = self.sql(expression, "this") 1190 kind = self.sql(expression, "kind") 1191 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1192 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1193 kind = f"{sep}{kind}" if kind else "" 1194 constraints = f" {constraints}" if constraints else "" 1195 position = self.sql(expression, "position") 1196 position = f" {position}" if position else "" 1197 1198 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1199 kind = "" 1200 1201 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:
1208 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1209 this = self.sql(expression, "this") 1210 if expression.args.get("not_null"): 1211 persisted = " PERSISTED NOT NULL" 1212 elif expression.args.get("persisted"): 1213 persisted = " PERSISTED" 1214 else: 1215 persisted = "" 1216 1217 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:
1230 def generatedasidentitycolumnconstraint_sql( 1231 self, expression: exp.GeneratedAsIdentityColumnConstraint 1232 ) -> str: 1233 this = "" 1234 if expression.this is not None: 1235 on_null = " ON NULL" if expression.args.get("on_null") else "" 1236 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1237 1238 start = expression.args.get("start") 1239 start = f"START WITH {start}" if start else "" 1240 increment = expression.args.get("increment") 1241 increment = f" INCREMENT BY {increment}" if increment else "" 1242 minvalue = expression.args.get("minvalue") 1243 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1244 maxvalue = expression.args.get("maxvalue") 1245 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1246 cycle = expression.args.get("cycle") 1247 cycle_sql = "" 1248 1249 if cycle is not None: 1250 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1251 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1252 1253 sequence_opts = "" 1254 if start or increment or cycle_sql: 1255 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1256 sequence_opts = f" ({sequence_opts.strip()})" 1257 1258 expr = self.sql(expression, "expression") 1259 expr = f"({expr})" if expr else "IDENTITY" 1260 1261 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1263 def generatedasrowcolumnconstraint_sql( 1264 self, expression: exp.GeneratedAsRowColumnConstraint 1265 ) -> str: 1266 start = "START" if expression.args.get("start") else "END" 1267 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1268 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:
1278 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1279 desc = expression.args.get("desc") 1280 if desc is not None: 1281 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1282 options = self.expressions(expression, key="options", flat=True, sep=" ") 1283 options = f" {options}" if options else "" 1284 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1286 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1287 this = self.sql(expression, "this") 1288 this = f" {this}" if this else "" 1289 index_type = expression.args.get("index_type") 1290 index_type = f" USING {index_type}" if index_type else "" 1291 on_conflict = self.sql(expression, "on_conflict") 1292 on_conflict = f" {on_conflict}" if on_conflict else "" 1293 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1294 options = self.expressions(expression, key="options", flat=True, sep=" ") 1295 options = f" {options}" if options else "" 1296 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1298 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1299 input_ = expression.args.get("input_") 1300 output = expression.args.get("output") 1301 variadic = expression.args.get("variadic") 1302 1303 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1304 if variadic: 1305 return "VARIADIC" 1306 1307 if input_ and output: 1308 return f"IN{self.INOUT_SEPARATOR}OUT" 1309 if input_: 1310 return "IN" 1311 if output: 1312 return "OUT" 1313 1314 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1319 def create_sql(self, expression: exp.Create) -> str: 1320 kind = self.sql(expression, "kind") 1321 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1322 1323 properties = expression.args.get("properties") 1324 1325 if ( 1326 kind == "TRIGGER" 1327 and properties 1328 and properties.expressions 1329 and isinstance(properties.expressions[0], exp.TriggerProperties) 1330 and properties.expressions[0].args.get("constraint") 1331 ): 1332 kind = f"CONSTRAINT {kind}" 1333 1334 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1335 1336 this = self.createable_sql(expression, properties_locs) 1337 1338 properties_sql = "" 1339 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1340 exp.Properties.Location.POST_WITH 1341 ): 1342 props_ast = exp.Properties( 1343 expressions=[ 1344 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1345 *properties_locs[exp.Properties.Location.POST_WITH], 1346 ] 1347 ) 1348 props_ast.parent = expression 1349 properties_sql = self.sql(props_ast) 1350 1351 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1352 properties_sql = self.sep() + properties_sql 1353 elif not self.pretty: 1354 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1355 properties_sql = f" {properties_sql}" 1356 1357 begin = " BEGIN" if expression.args.get("begin") else "" 1358 1359 expression_sql = self.sql(expression, "expression") 1360 if expression_sql: 1361 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1362 1363 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1364 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1365 ): 1366 postalias_props_sql = "" 1367 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1368 postalias_props_sql = self.properties( 1369 exp.Properties( 1370 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1371 ), 1372 wrapped=False, 1373 ) 1374 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1375 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1376 1377 postindex_props_sql = "" 1378 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1379 postindex_props_sql = self.properties( 1380 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1381 wrapped=False, 1382 prefix=" ", 1383 ) 1384 1385 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1386 indexes = f" {indexes}" if indexes else "" 1387 index_sql = indexes + postindex_props_sql 1388 1389 replace = " OR REPLACE" if expression.args.get("replace") else "" 1390 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1391 unique = " UNIQUE" if expression.args.get("unique") else "" 1392 1393 clustered = expression.args.get("clustered") 1394 if clustered is None: 1395 clustered_sql = "" 1396 elif clustered: 1397 clustered_sql = " CLUSTERED COLUMNSTORE" 1398 else: 1399 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1400 1401 postcreate_props_sql = "" 1402 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1403 postcreate_props_sql = self.properties( 1404 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1405 sep=" ", 1406 prefix=" ", 1407 wrapped=False, 1408 ) 1409 1410 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1411 1412 postexpression_props_sql = "" 1413 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1414 postexpression_props_sql = self.properties( 1415 exp.Properties( 1416 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1417 ), 1418 sep=" ", 1419 prefix=" ", 1420 wrapped=False, 1421 ) 1422 1423 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1424 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1425 no_schema_binding = ( 1426 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1427 ) 1428 1429 clone = self.sql(expression, "clone") 1430 clone = f" {clone}" if clone else "" 1431 1432 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1433 properties_expression = f"{expression_sql}{properties_sql}" 1434 else: 1435 properties_expression = f"{properties_sql}{expression_sql}" 1436 1437 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1438 return self.prepend_ctes(expression, expression_sql)
1440 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1441 start = self.sql(expression, "start") 1442 start = f"START WITH {start}" if start else "" 1443 increment = self.sql(expression, "increment") 1444 increment = f" INCREMENT BY {increment}" if increment else "" 1445 minvalue = self.sql(expression, "minvalue") 1446 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1447 maxvalue = self.sql(expression, "maxvalue") 1448 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1449 owned = self.sql(expression, "owned") 1450 owned = f" OWNED BY {owned}" if owned else "" 1451 1452 cache = expression.args.get("cache") 1453 if cache is None: 1454 cache_str = "" 1455 elif cache is True: 1456 cache_str = " CACHE" 1457 else: 1458 cache_str = f" CACHE {cache}" 1459 1460 options = self.expressions(expression, key="options", flat=True, sep=" ") 1461 options = f" {options}" if options else "" 1462 1463 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1465 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1466 timing = expression.args.get("timing", "") 1467 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1468 timing_events = f"{timing} {events}".strip() if timing or events else "" 1469 1470 parts = [timing_events, "ON", self.sql(expression, "table")] 1471 1472 if referenced_table := expression.args.get("referenced_table"): 1473 parts.extend(["FROM", self.sql(referenced_table)]) 1474 1475 if deferrable := expression.args.get("deferrable"): 1476 parts.append(deferrable) 1477 1478 if initially := expression.args.get("initially"): 1479 parts.append(f"INITIALLY {initially}") 1480 1481 if referencing := expression.args.get("referencing"): 1482 parts.append(self.sql(referencing)) 1483 1484 if for_each := expression.args.get("for_each"): 1485 parts.append(f"FOR EACH {for_each}") 1486 1487 if when := expression.args.get("when"): 1488 parts.append(f"WHEN ({self.sql(when)})") 1489 1490 parts.append(self.sql(expression, "execute")) 1491 1492 return self.sep().join(parts)
1494 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1495 parts = [] 1496 1497 if old_alias := expression.args.get("old"): 1498 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1499 1500 if new_alias := expression.args.get("new"): 1501 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1502 1503 return f"REFERENCING {' '.join(parts)}"
1512 def clone_sql(self, expression: exp.Clone) -> str: 1513 this = self.sql(expression, "this") 1514 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1515 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1516 return f"{shallow}{keyword} {this}"
1518 def describe_sql(self, expression: exp.Describe) -> str: 1519 style = expression.args.get("style") 1520 style = f" {style}" if style else "" 1521 partition = self.sql(expression, "partition") 1522 partition = f" {partition}" if partition else "" 1523 format = self.sql(expression, "format") 1524 format = f" {format}" if format else "" 1525 as_json = " AS JSON" if expression.args.get("as_json") else "" 1526 1527 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1539 def with_sql(self, expression: exp.With) -> str: 1540 udfs = self.expressions(expression, key="udfs", flat=True) 1541 udfs = f"WITH {udfs}" if udfs else "" 1542 1543 sql = self.expressions(expression, flat=True) 1544 1545 recursive = ( 1546 "RECURSIVE " 1547 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1548 else "" 1549 ) 1550 search = self.sql(expression, "search") 1551 search = f" {search}" if search else "" 1552 1553 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1554 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1556 def cte_sql(self, expression: exp.CTE) -> str: 1557 alias = expression.args.get("alias") 1558 if alias: 1559 alias.add_comments(expression.pop_comments()) 1560 1561 alias_sql = self.sql(expression, "alias") 1562 1563 materialized = expression.args.get("materialized") 1564 if materialized is False: 1565 materialized = "NOT MATERIALIZED " 1566 elif materialized: 1567 materialized = "MATERIALIZED " 1568 1569 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1570 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1571 1572 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1574 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1575 alias = self.sql(expression, "this") 1576 columns = self.expressions(expression, key="columns", flat=True) 1577 columns = f"({columns})" if columns else "" 1578 1579 if ( 1580 columns 1581 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1582 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1583 ): 1584 columns = "" 1585 self.unsupported("Named columns are not supported in table alias.") 1586 1587 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1588 alias = self._next_name() 1589 1590 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1598 def hexstring_sql( 1599 self, expression: exp.HexString, binary_function_repr: str | None = None 1600 ) -> str: 1601 this = self.sql(expression, "this") 1602 is_integer_type = expression.args.get("is_integer") 1603 1604 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1605 not self.dialect.HEX_START and not binary_function_repr 1606 ): 1607 # Integer representation will be returned if: 1608 # - The read dialect treats the hex value as integer literal but not the write 1609 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1610 return f"{int(this, 16)}" 1611 1612 if not is_integer_type: 1613 # Read dialect treats the hex value as BINARY/BLOB 1614 if binary_function_repr: 1615 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1616 return self.func(binary_function_repr, exp.Literal.string(this)) 1617 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1618 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1619 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1620 1621 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1623 def bytestring_sql(self, expression: exp.ByteString) -> str: 1624 this = self.sql(expression, "this") 1625 if self.dialect.BYTE_START: 1626 escaped_byte_string = self.escape_str( 1627 this, 1628 escape_backslash=False, 1629 delimiter=self.dialect.BYTE_END, 1630 escaped_delimiter=self._escaped_byte_quote_end, 1631 is_byte_string=True, 1632 ) 1633 is_bytes = expression.args.get("is_bytes", False) 1634 delimited_byte_string = ( 1635 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1636 ) 1637 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1638 return self.sql( 1639 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1640 ) 1641 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1642 return self.sql( 1643 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1644 ) 1645 1646 return delimited_byte_string 1647 1648 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1649 return self.sql(exp.Literal.string(this)) 1650 1651 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1652 return ""
1654 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1655 this = self.sql(expression, "this") 1656 escape = expression.args.get("escape") 1657 unicode_start = self.dialect.UNICODE_START 1658 1659 if unicode_start: 1660 escape_substitute = r"\\\1" 1661 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1662 else: 1663 escape_substitute = r"\\u\1" 1664 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1665 1666 if escape: 1667 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1668 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1669 else: 1670 escape_pattern = ESCAPED_UNICODE_RE 1671 escape_sql = "" 1672 1673 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1674 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1675 1676 if unicode_start: 1677 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1678 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1679 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1680 else: 1681 this = self.escape_str(this, escape_backslash=False) 1682 1683 return f"{left_quote}{this}{right_quote}{escape_sql}"
1685 def rawstring_sql(self, expression: exp.RawString) -> str: 1686 string = expression.this 1687 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1688 string = string.replace("\\", "\\\\") 1689 1690 string = self.escape_str(string, escape_backslash=False) 1691 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:
1699 def datatype_param_bound_limiter( 1700 self, 1701 expression: exp.DataType, 1702 type_value: exp.DType, 1703 defaults: tuple[int, ...], 1704 bounds: tuple[int | None, ...], 1705 ) -> exp.DataType: 1706 params = expression.expressions 1707 1708 if not params: 1709 if defaults: 1710 expression.set( 1711 "expressions", 1712 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1713 ) 1714 return expression 1715 1716 if not bounds: 1717 return expression 1718 1719 for i, param in enumerate(params): 1720 bound = bounds[i] if i < len(bounds) else None 1721 if bound is None: 1722 continue 1723 1724 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1725 value = ( 1726 param_value.to_py() 1727 if isinstance(param_value, exp.Literal) and param_value.is_number 1728 else None 1729 ) 1730 if isinstance(value, (int, Decimal)) and value > bound: 1731 self.unsupported( 1732 f"{type_value.value} parameter {param_value.name} exceeds " 1733 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1734 ) 1735 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1736 1737 return expression
1739 def datatype_sql(self, expression: exp.DataType) -> str: 1740 nested = "" 1741 values = "" 1742 1743 expr_nested = expression.args.get("nested") 1744 type_value = expression.this 1745 1746 if ( 1747 not expr_nested 1748 and isinstance(type_value, exp.DType) 1749 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1750 ): 1751 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1752 1753 interior = ( 1754 self.expressions( 1755 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1756 ) 1757 if expr_nested and self.pretty 1758 else self.expressions(expression, flat=True) 1759 ) 1760 1761 if type_value in self.UNSUPPORTED_TYPES: 1762 self.unsupported( 1763 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1764 ) 1765 1766 type_sql: t.Any = "" 1767 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1768 type_sql = self.sql(expression, "kind") 1769 elif type_value == exp.DType.CHARACTER_SET: 1770 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1771 else: 1772 type_sql = ( 1773 self.TYPE_MAPPING.get(type_value, type_value.value) 1774 if isinstance(type_value, exp.DType) 1775 else type_value 1776 ) 1777 1778 if interior: 1779 if expr_nested: 1780 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1781 if expression.args.get("values") is not None: 1782 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1783 values = self.expressions(expression, key="values", flat=True) 1784 values = f"{delimiters[0]}{values}{delimiters[1]}" 1785 elif type_value == exp.DType.INTERVAL: 1786 nested = f" {interior}" 1787 else: 1788 nested = f"({interior})" 1789 1790 type_sql = f"{type_sql}{nested}{values}" 1791 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1792 exp.DType.TIMETZ, 1793 exp.DType.TIMESTAMPTZ, 1794 ): 1795 type_sql = f"{type_sql} WITH TIME ZONE" 1796 1797 collate = self.sql(expression, "collate") 1798 if collate: 1799 type_sql = f"{type_sql} COLLATE {collate}" 1800 1801 return type_sql
1803 def directory_sql(self, expression: exp.Directory) -> str: 1804 local = "LOCAL " if expression.args.get("local") else "" 1805 row_format = self.sql(expression, "row_format") 1806 row_format = f" {row_format}" if row_format else "" 1807 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1809 def delete_sql(self, expression: exp.Delete) -> str: 1810 hint = self.sql(expression, "hint") 1811 this = self.sql(expression, "this") 1812 this = f" FROM {this}" if this else "" 1813 using = self.expressions(expression, key="using") 1814 using = f" USING {using}" if using else "" 1815 cluster = self.sql(expression, "cluster") 1816 cluster = f" {cluster}" if cluster else "" 1817 where = self.sql(expression, "where") 1818 returning = self.sql(expression, "returning") 1819 order = self.sql(expression, "order") 1820 limit = self.sql(expression, "limit") 1821 tables = self.expressions(expression, key="tables") 1822 tables = f" {tables}" if tables else "" 1823 if self.RETURNING_END: 1824 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1825 else: 1826 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1827 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1829 def drop_sql(self, expression: exp.Drop) -> str: 1830 this = self.sql(expression, "this") 1831 expressions = self.expressions(expression, flat=True) 1832 expressions = f" ({expressions})" if expressions else "" 1833 kind = expression.args["kind"] 1834 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1835 iceberg = ( 1836 " ICEBERG" 1837 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1838 else "" 1839 ) 1840 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1841 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1842 on_cluster = self.sql(expression, "cluster") 1843 on_cluster = f" {on_cluster}" if on_cluster else "" 1844 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1845 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1846 cascade = " CASCADE" if expression.args.get("cascade") else "" 1847 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1848 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1849 purge = " PURGE" if expression.args.get("purge") else "" 1850 sync = " SYNC" if expression.args.get("sync") else "" 1851 force = " FORCE" if expression.args.get("force") else "" 1852 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1854 def set_operation(self, expression: exp.SetOperation) -> str: 1855 op_type = type(expression) 1856 op_name = op_type.key.upper() 1857 1858 distinct = expression.args.get("distinct") 1859 if ( 1860 distinct is False 1861 and op_type in (exp.Except, exp.Intersect) 1862 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1863 ): 1864 self.unsupported(f"{op_name} ALL is not supported") 1865 1866 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1867 1868 if distinct is None: 1869 distinct = default_distinct 1870 if distinct is None: 1871 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1872 1873 if distinct is default_distinct: 1874 distinct_or_all = "" 1875 else: 1876 distinct_or_all = " DISTINCT" if distinct else " ALL" 1877 1878 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1879 side_kind = f"{side_kind} " if side_kind else "" 1880 1881 by_name = " BY NAME" if expression.args.get("by_name") else "" 1882 on = self.expressions(expression, key="on", flat=True) 1883 on = f" ON ({on})" if on else "" 1884 1885 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1887 def set_operations(self, expression: exp.SetOperation) -> str: 1888 if not self.SET_OP_MODIFIERS: 1889 limit = expression.args.get("limit") 1890 order = expression.args.get("order") 1891 1892 if limit or order: 1893 select = self._move_ctes_to_top_level( 1894 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1895 ) 1896 1897 if limit: 1898 select = select.limit(limit.pop(), copy=False) 1899 if order: 1900 select = select.order_by(order.pop(), copy=False) 1901 return self.sql(select) 1902 1903 sqls: list[str] = [] 1904 stack: list[str | exp.Expr] = [expression] 1905 1906 while stack: 1907 node = stack.pop() 1908 1909 if isinstance(node, exp.SetOperation): 1910 stack.append(node.expression) 1911 stack.append( 1912 self.maybe_comment( 1913 self.set_operation(node), comments=node.comments, separated=True 1914 ) 1915 ) 1916 stack.append(node.this) 1917 else: 1918 sqls.append(self.sql(node)) 1919 1920 this = self.sep().join(sqls) 1921 this = self.query_modifiers(expression, this) 1922 return self.prepend_ctes(expression, this)
1924 def fetch_sql(self, expression: exp.Fetch) -> str: 1925 direction = expression.args.get("direction") 1926 direction = f" {direction}" if direction else "" 1927 count = self.sql(expression, "count") 1928 count = f" {count}" if count else "" 1929 limit_options = self.sql(expression, "limit_options") 1930 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1931 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1933 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1934 percent = " PERCENT" if expression.args.get("percent") else "" 1935 rows = " ROWS" if expression.args.get("rows") else "" 1936 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1937 if not with_ties and rows: 1938 with_ties = " ONLY" 1939 return f"{percent}{rows}{with_ties}"
1953 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1954 using = self.sql(expression, "using") 1955 using = f" USING {using}" if using else "" 1956 columns = self.expressions(expression, key="columns", flat=True) 1957 columns = f"({columns})" if columns else "" 1958 partition_by = self.expressions(expression, key="partition_by", flat=True) 1959 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1960 where = self.sql(expression, "where") 1961 include = self.expressions(expression, key="include", flat=True) 1962 if include: 1963 include = f" INCLUDE ({include})" 1964 with_storage = self.expressions(expression, key="with_storage", flat=True) 1965 with_storage = f" WITH ({with_storage})" if with_storage else "" 1966 tablespace = self.sql(expression, "tablespace") 1967 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1968 on = self.sql(expression, "on") 1969 on = f" ON {on}" if on else "" 1970 1971 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1973 def index_sql(self, expression: exp.Index) -> str: 1974 unique = "UNIQUE " if expression.args.get("unique") else "" 1975 primary = "PRIMARY " if expression.args.get("primary") else "" 1976 amp = "AMP " if expression.args.get("amp") else "" 1977 name = self.sql(expression, "this") 1978 name = f"{name} " if name else "" 1979 table = self.sql(expression, "table") 1980 table = f"{self.INDEX_ON} {table}" if table else "" 1981 1982 index = "INDEX " if not table else "" 1983 1984 params = self.sql(expression, "params") 1985 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1987 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1988 this = expression.this 1989 if this and this.is_string: 1990 resolved = maybe_parse(this.name).sql(self.dialect) 1991 if "expressions" in expression.args: 1992 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1993 # We can't safely emit the call to other dialects since name/arg semantics may differ 1994 self.unsupported( 1995 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1996 ) 1997 return resolved 1998 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1999 return self.func("IDENTIFIER", this)
2001 def identifier_sql(self, expression: exp.Identifier) -> str: 2002 text = expression.name 2003 lower = text.lower() 2004 quoted = expression.quoted 2005 text = lower if self.normalize and not quoted else text 2006 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2007 if ( 2008 quoted 2009 or self.dialect.can_quote(expression, self.identify) 2010 or lower in self.RESERVED_KEYWORDS 2011 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2012 ): 2013 text = ( 2014 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2015 ) 2016 return text
2031 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2032 input_format = self.sql(expression, "input_format") 2033 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2034 output_format = self.sql(expression, "output_format") 2035 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2036 return self.sep().join((input_format, output_format))
2046 def properties_sql(self, expression: exp.Properties) -> str: 2047 root_properties = [] 2048 with_properties = [] 2049 2050 for p in expression.expressions: 2051 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2052 if p_loc == exp.Properties.Location.POST_WITH: 2053 with_properties.append(p) 2054 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2055 root_properties.append(p) 2056 2057 root_props_ast = exp.Properties(expressions=root_properties) 2058 root_props_ast.parent = expression.parent 2059 2060 with_props_ast = exp.Properties(expressions=with_properties) 2061 with_props_ast.parent = expression.parent 2062 2063 root_props = self.root_properties(root_props_ast) 2064 with_props = self.with_properties(with_props_ast) 2065 2066 if root_props and with_props and not self.pretty: 2067 with_props = " " + with_props 2068 2069 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2076 def properties( 2077 self, 2078 properties: exp.Properties, 2079 prefix: str = "", 2080 sep: str = ", ", 2081 suffix: str = "", 2082 wrapped: bool = True, 2083 ) -> str: 2084 if properties.expressions: 2085 expressions = self.expressions(properties, sep=sep, indent=False) 2086 if expressions: 2087 expressions = self.wrap(expressions) if wrapped else expressions 2088 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2089 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2094 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2095 properties_locs = defaultdict(list) 2096 for p in properties.expressions: 2097 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2098 if p_loc != exp.Properties.Location.UNSUPPORTED: 2099 properties_locs[p_loc].append(p) 2100 else: 2101 self.unsupported(f"Unsupported property {p.key}") 2102 2103 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2110 def property_sql(self, expression: exp.Property) -> str: 2111 property_cls = expression.__class__ 2112 if property_cls == exp.Property: 2113 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2114 2115 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2116 if not property_name: 2117 self.unsupported(f"Unsupported property {expression.key}") 2118 2119 return f"{property_name}={self.sql(expression, 'this')}"
2124 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2125 if self.SUPPORTS_CREATE_TABLE_LIKE: 2126 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2127 options = f" {options}" if options else "" 2128 2129 like = f"LIKE {self.sql(expression, 'this')}{options}" 2130 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2131 like = f"({like})" 2132 2133 return like 2134 2135 if expression.expressions: 2136 self.unsupported("Transpilation of LIKE property options is unsupported") 2137 2138 select = exp.select("*").from_(expression.this).limit(0) 2139 return f"AS {self.sql(select)}"
2146 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2147 no = "NO " if expression.args.get("no") else "" 2148 local = expression.args.get("local") 2149 local = f"{local} " if local else "" 2150 dual = "DUAL " if expression.args.get("dual") else "" 2151 before = "BEFORE " if expression.args.get("before") else "" 2152 after = "AFTER " if expression.args.get("after") else "" 2153 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:
2169 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2170 if expression.args.get("no"): 2171 return "NO MERGEBLOCKRATIO" 2172 if expression.args.get("default"): 2173 return "DEFAULT MERGEBLOCKRATIO" 2174 2175 percent = " PERCENT" if expression.args.get("percent") else "" 2176 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2183 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2184 default = expression.args.get("default") 2185 minimum = expression.args.get("minimum") 2186 maximum = expression.args.get("maximum") 2187 if default or minimum or maximum: 2188 if default: 2189 prop = "DEFAULT" 2190 elif minimum: 2191 prop = "MINIMUM" 2192 else: 2193 prop = "MAXIMUM" 2194 return f"{prop} DATABLOCKSIZE" 2195 units = expression.args.get("units") 2196 units = f" {units}" if units else "" 2197 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2199 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2200 autotemp = expression.args.get("autotemp") 2201 always = expression.args.get("always") 2202 default = expression.args.get("default") 2203 manual = expression.args.get("manual") 2204 never = expression.args.get("never") 2205 2206 if autotemp is not None: 2207 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2208 elif always: 2209 prop = "ALWAYS" 2210 elif default: 2211 prop = "DEFAULT" 2212 elif manual: 2213 prop = "MANUAL" 2214 elif never: 2215 prop = "NEVER" 2216 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2218 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2219 no = expression.args.get("no") 2220 no = " NO" if no else "" 2221 concurrent = expression.args.get("concurrent") 2222 concurrent = " CONCURRENT" if concurrent else "" 2223 target = self.sql(expression, "target") 2224 target = f" {target}" if target else "" 2225 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2227 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2228 if isinstance(expression.this, list): 2229 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2230 if expression.this: 2231 modulus = self.sql(expression, "this") 2232 remainder = self.sql(expression, "expression") 2233 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2234 2235 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2236 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2237 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2239 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2240 this = self.sql(expression, "this") 2241 2242 for_values_or_default = expression.expression 2243 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2244 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2245 else: 2246 for_values_or_default = " DEFAULT" 2247 2248 return f"PARTITION OF {this}{for_values_or_default}"
2250 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2251 kind = expression.args.get("kind") 2252 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2253 for_or_in = expression.args.get("for_or_in") 2254 for_or_in = f" {for_or_in}" if for_or_in else "" 2255 lock_type = expression.args.get("lock_type") 2256 override = " OVERRIDE" if expression.args.get("override") else "" 2257 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2259 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2260 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2261 statistics = expression.args.get("statistics") 2262 statistics_sql = "" 2263 if statistics is not None: 2264 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2265 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2267 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2268 this = self.sql(expression, "this") 2269 this = f"HISTORY_TABLE={this}" if this else "" 2270 data_consistency: str | None = self.sql(expression, "data_consistency") 2271 data_consistency = ( 2272 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2273 ) 2274 retention_period: str | None = self.sql(expression, "retention_period") 2275 retention_period = ( 2276 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2277 ) 2278 2279 if this: 2280 on_sql = self.func("ON", this, data_consistency, retention_period) 2281 else: 2282 on_sql = "ON" if expression.args.get("on") else "OFF" 2283 2284 sql = f"SYSTEM_VERSIONING={on_sql}" 2285 2286 return f"WITH({sql})" if expression.args.get("with_") else sql
2288 def insert_sql(self, expression: exp.Insert) -> str: 2289 hint = self.sql(expression, "hint") 2290 overwrite = expression.args.get("overwrite") 2291 2292 if isinstance(expression.this, exp.Directory): 2293 this = " OVERWRITE" if overwrite else " INTO" 2294 else: 2295 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2296 2297 stored = self.sql(expression, "stored") 2298 stored = f" {stored}" if stored else "" 2299 alternative = expression.args.get("alternative") 2300 alternative = f" OR {alternative}" if alternative else "" 2301 ignore = " IGNORE" if expression.args.get("ignore") else "" 2302 is_function = expression.args.get("is_function") 2303 if is_function: 2304 this = f"{this} FUNCTION" 2305 this = f"{this} {self.sql(expression, 'this')}" 2306 2307 exists = " IF EXISTS" if expression.args.get("exists") else "" 2308 where = self.sql(expression, "where") 2309 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2310 using = self.expressions(expression, key="using", flat=True) 2311 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2312 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2313 on_conflict = self.sql(expression, "conflict") 2314 on_conflict = f" {on_conflict}" if on_conflict else "" 2315 by_name = " BY NAME" if expression.args.get("by_name") else "" 2316 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2317 returning = self.sql(expression, "returning") 2318 2319 if self.RETURNING_END: 2320 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2321 else: 2322 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2323 2324 partition_by = self.sql(expression, "partition") 2325 partition_by = f" {partition_by}" if partition_by else "" 2326 settings = self.sql(expression, "settings") 2327 settings = f" {settings}" if settings else "" 2328 2329 source = self.sql(expression, "source") 2330 source = f"TABLE {source}" if source else "" 2331 2332 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2333 return self.prepend_ctes(expression, sql)
2351 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2352 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2353 2354 constraint = self.sql(expression, "constraint") 2355 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2356 2357 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2358 if conflict_keys: 2359 conflict_keys = f"({conflict_keys})" 2360 2361 index_predicate = self.sql(expression, "index_predicate") 2362 conflict_keys = f"{conflict_keys}{index_predicate} " 2363 2364 action = self.sql(expression, "action") 2365 2366 expressions = self.expressions(expression, flat=True) 2367 if expressions: 2368 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2369 expressions = f" {set_keyword}{expressions}" 2370 2371 where = self.sql(expression, "where") 2372 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2377 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2378 fields = self.sql(expression, "fields") 2379 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2380 escaped = self.sql(expression, "escaped") 2381 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2382 items = self.sql(expression, "collection_items") 2383 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2384 keys = self.sql(expression, "map_keys") 2385 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2386 lines = self.sql(expression, "lines") 2387 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2388 null = self.sql(expression, "null") 2389 null = f" NULL DEFINED AS {null}" if null else "" 2390 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2418 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2419 table = self.table_parts(expression) 2420 only = "ONLY " if expression.args.get("only") else "" 2421 partition = self.sql(expression, "partition") 2422 partition = f" {partition}" if partition else "" 2423 version = self.sql(expression, "version") 2424 version = f" {version}" if version else "" 2425 alias = self.sql(expression, "alias") 2426 alias = f"{sep}{alias}" if alias else "" 2427 2428 sample = self.sql(expression, "sample") 2429 post_alias = "" 2430 pre_alias = "" 2431 2432 if self.dialect.ALIAS_POST_TABLESAMPLE: 2433 pre_alias = sample 2434 else: 2435 post_alias = sample 2436 2437 if self.dialect.ALIAS_POST_VERSION: 2438 pre_alias = f"{pre_alias}{version}" 2439 else: 2440 post_alias = f"{post_alias}{version}" 2441 2442 hints = self.expressions(expression, key="hints", sep=" ") 2443 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2444 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2445 joins = self.indent( 2446 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2447 ) 2448 laterals = self.expressions(expression, key="laterals", sep="") 2449 2450 file_format = self.sql(expression, "format") 2451 pattern = self.sql(expression, "pattern") 2452 if file_format: 2453 pattern = f", PATTERN => {pattern}" if pattern else "" 2454 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2455 elif pattern: 2456 file_format = f" (PATTERN => {pattern})" 2457 2458 ordinality = expression.args.get("ordinality") or "" 2459 if ordinality: 2460 ordinality = f" WITH ORDINALITY{alias}" 2461 alias = "" 2462 2463 when = self.sql(expression, "when") 2464 if when: 2465 if self.HISTORICAL_DATA_POST_ALIAS: 2466 alias = f"{alias} {when}" 2467 else: 2468 table = f"{table} {when}" 2469 2470 changes = self.sql(expression, "changes") 2471 changes = f" {changes}" if changes else "" 2472 2473 rows_from = self.expressions(expression, key="rows_from") 2474 if rows_from: 2475 table = f"ROWS FROM {self.wrap(rows_from)}" 2476 2477 indexed = expression.args.get("indexed") 2478 if indexed is not None: 2479 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2480 else: 2481 indexed = "" 2482 2483 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2485 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2486 table = self.func("TABLE", expression.this) 2487 alias = self.sql(expression, "alias") 2488 alias = f" AS {alias}" if alias else "" 2489 sample = self.sql(expression, "sample") 2490 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2491 joins = self.indent( 2492 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2493 ) 2494 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2496 def tablesample_sql( 2497 self, 2498 expression: exp.TableSample, 2499 tablesample_keyword: str | None = None, 2500 ) -> str: 2501 method = self.sql(expression, "method") 2502 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2503 numerator = self.sql(expression, "bucket_numerator") 2504 denominator = self.sql(expression, "bucket_denominator") 2505 field = self.sql(expression, "bucket_field") 2506 field = f" ON {field}" if field else "" 2507 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2508 seed = self.sql(expression, "seed") 2509 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2510 2511 size = self.sql(expression, "size") 2512 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2513 size = f"{size} ROWS" 2514 2515 percent = self.sql(expression, "percent") 2516 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2517 percent = f"{percent} PERCENT" 2518 2519 expr = f"{bucket}{percent}{size}" 2520 if self.TABLESAMPLE_REQUIRES_PARENS: 2521 expr = f"({expr})" 2522 2523 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2600 def pivot_sql(self, expression: exp.Pivot) -> str: 2601 expressions = self.expressions(expression, flat=True) 2602 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2603 2604 group = self.sql(expression, "group") 2605 2606 if expression.this: 2607 this = self.sql(expression, "this") 2608 if not expressions: 2609 sql = f"UNPIVOT {this}" 2610 else: 2611 on = f"{self.seg('ON')} {expressions}" 2612 into = self.sql(expression, "into") 2613 into = f"{self.seg('INTO')} {into}" if into else "" 2614 using = self.expressions(expression, key="using", flat=True) 2615 using = f"{self.seg('USING')} {using}" if using else "" 2616 sql = f"{direction} {this}{on}{into}{using}{group}" 2617 return self.prepend_ctes(expression, sql) 2618 2619 if not expression.unpivot: 2620 # Wrap IN-list values with explicit aliases where the target dialect would differ 2621 new_field_exprs = self._pivot_in_value_aliases(expression) 2622 if new_field_exprs is not None: 2623 expression.fields[0].set("expressions", new_field_exprs) 2624 2625 alias = self.sql(expression, "alias") 2626 if alias: 2627 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2628 2629 fields = self.expressions( 2630 expression, 2631 "fields", 2632 sep=" ", 2633 dynamic=True, 2634 new_line=True, 2635 skip_first=True, 2636 skip_last=True, 2637 ) 2638 2639 include_nulls = expression.args.get("include_nulls") 2640 if include_nulls is not None: 2641 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2642 else: 2643 nulls = "" 2644 2645 default_on_null = self.sql(expression, "default_on_null") 2646 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2647 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2648 return self.prepend_ctes(expression, sql)
2691 def update_sql(self, expression: exp.Update) -> str: 2692 hint = self.sql(expression, "hint") 2693 this = self.sql(expression, "this") 2694 join_sql, from_sql = self._update_from_joins_sql(expression) 2695 set_sql = self.expressions(expression, flat=True) 2696 where_sql = self.sql(expression, "where") 2697 returning = self.sql(expression, "returning") 2698 order = self.sql(expression, "order") 2699 limit = self.sql(expression, "limit") 2700 if self.RETURNING_END: 2701 expression_sql = f"{from_sql}{where_sql}{returning}" 2702 else: 2703 expression_sql = f"{returning}{from_sql}{where_sql}" 2704 options = self.expressions(expression, key="options") 2705 options = f" OPTION({options})" if options else "" 2706 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2707 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2709 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2710 values_as_table = values_as_table and self.VALUES_AS_TABLE 2711 2712 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2713 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2714 args = self.expressions(expression) 2715 alias = self.sql(expression, "alias") 2716 values = f"VALUES{self.seg('')}{args}" 2717 values = ( 2718 f"({values})" 2719 if self.WRAP_DERIVED_VALUES 2720 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2721 else values 2722 ) 2723 values = self.query_modifiers(expression, values) 2724 return f"{values} AS {alias}" if alias else values 2725 2726 # Converts `VALUES...` expression into a series of select unions. 2727 alias_node = expression.args.get("alias") 2728 column_names = alias_node and alias_node.columns 2729 2730 selects: list[exp.Query] = [] 2731 2732 for i, tup in enumerate(expression.expressions): 2733 row = tup.expressions 2734 2735 if i == 0 and column_names: 2736 row = [ 2737 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2738 ] 2739 2740 selects.append(exp.Select(expressions=row)) 2741 2742 if self.pretty: 2743 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2744 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2745 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2746 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2747 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2748 2749 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2750 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2751 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2756 @unsupported_args("expressions") 2757 def into_sql(self, expression: exp.Into) -> str: 2758 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2759 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2760 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2773 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2774 this = self.sql(expression, "this") 2775 2776 columns = self.expressions(expression, flat=True) 2777 2778 from_sql = self.sql(expression, "from_index") 2779 from_sql = f" FROM {from_sql}" if from_sql else "" 2780 2781 properties = expression.args.get("properties") 2782 properties_sql = ( 2783 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2784 ) 2785 2786 return f"{this}({columns}){from_sql}{properties_sql}"
2795 def group_sql(self, expression: exp.Group) -> str: 2796 group_by_all = expression.args.get("all") 2797 if group_by_all is True: 2798 modifier = " ALL" 2799 elif group_by_all is False: 2800 modifier = " DISTINCT" 2801 else: 2802 modifier = "" 2803 2804 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2805 2806 grouping_sets = self.expressions(expression, key="grouping_sets") 2807 cube = self.expressions(expression, key="cube") 2808 rollup = self.expressions(expression, key="rollup") 2809 2810 groupings = csv( 2811 self.seg(grouping_sets) if grouping_sets else "", 2812 self.seg(cube) if cube else "", 2813 self.seg(rollup) if rollup else "", 2814 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2815 sep=self.GROUPINGS_SEP, 2816 ) 2817 2818 if ( 2819 expression.expressions 2820 and groupings 2821 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2822 ): 2823 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2824 2825 return f"{group_by}{groupings}"
2831 def connect_sql(self, expression: exp.Connect) -> str: 2832 start = self.sql(expression, "start") 2833 start = self.seg(f"START WITH {start}") if start else "" 2834 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2835 connect = self.sql(expression, "connect") 2836 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2837 return start + connect
2842 def join_sql(self, expression: exp.Join) -> str: 2843 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2844 side = None 2845 else: 2846 side = expression.side 2847 2848 op_sql = " ".join( 2849 op 2850 for op in ( 2851 expression.method, 2852 "GLOBAL" if expression.args.get("global_") else None, 2853 side, 2854 expression.kind, 2855 expression.hint if self.JOIN_HINTS else None, 2856 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2857 ) 2858 if op 2859 ) 2860 match_cond = self.sql(expression, "match_condition") 2861 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2862 on_sql = self.sql(expression, "on") 2863 using = expression.args.get("using") 2864 2865 if not on_sql and using: 2866 on_sql = csv(*(self.sql(column) for column in using)) 2867 2868 this = expression.this 2869 this_sql = self.sql(this) 2870 2871 exprs = self.expressions(expression) 2872 if exprs: 2873 this_sql = f"{this_sql},{self.seg(exprs)}" 2874 2875 if on_sql: 2876 on_sql = self.indent(on_sql, skip_first=True) 2877 space = self.seg(" " * self.pad) if self.pretty else " " 2878 if using: 2879 on_sql = f"{space}USING ({on_sql})" 2880 else: 2881 on_sql = f"{space}ON {on_sql}" 2882 elif not op_sql: 2883 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2884 return f" {this_sql}" 2885 2886 return f", {this_sql}" 2887 2888 if op_sql != "STRAIGHT_JOIN": 2889 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2890 2891 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2892 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:
2899 def lateral_op(self, expression: exp.Lateral) -> str: 2900 cross_apply = expression.args.get("cross_apply") 2901 2902 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2903 if cross_apply is True: 2904 op = "INNER JOIN " 2905 elif cross_apply is False: 2906 op = "LEFT JOIN " 2907 else: 2908 op = "" 2909 2910 return f"{op}LATERAL"
2912 def lateral_sql(self, expression: exp.Lateral) -> str: 2913 this = self.sql(expression, "this") 2914 2915 if expression.args.get("view"): 2916 alias = expression.args["alias"] 2917 columns = self.expressions(alias, key="columns", flat=True) 2918 table = f" {alias.name}" if alias.name else "" 2919 columns = f" AS {columns}" if columns else "" 2920 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2921 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2922 2923 alias = self.sql(expression, "alias") 2924 alias = f" AS {alias}" if alias else "" 2925 2926 ordinality = expression.args.get("ordinality") or "" 2927 if ordinality: 2928 ordinality = f" WITH ORDINALITY{alias}" 2929 alias = "" 2930 2931 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2933 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2934 this = self.sql(expression, "this") 2935 2936 args = [ 2937 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2938 for e in (expression.args.get(k) for k in ("offset", "expression")) 2939 if e 2940 ] 2941 2942 args_sql = ", ".join(self.sql(e) for e in args) 2943 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2944 expressions = self.expressions(expression, flat=True) 2945 limit_options = self.sql(expression, "limit_options") 2946 expressions = f" BY {expressions}" if expressions else "" 2947 2948 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2950 def offset_sql(self, expression: exp.Offset) -> str: 2951 this = self.sql(expression, "this") 2952 value = expression.expression 2953 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2954 expressions = self.expressions(expression, flat=True) 2955 expressions = f" BY {expressions}" if expressions else "" 2956 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2958 def setitem_sql(self, expression: exp.SetItem) -> str: 2959 kind = self.sql(expression, "kind") 2960 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2961 kind = "" 2962 else: 2963 kind = f"{kind} " if kind else "" 2964 this = self.sql(expression, "this") 2965 expressions = self.expressions(expression) 2966 collate = self.sql(expression, "collate") 2967 collate = f" COLLATE {collate}" if collate else "" 2968 global_ = "GLOBAL " if expression.args.get("global_") else "" 2969 return f"{global_}{kind}{this}{expressions}{collate}"
2976 def queryband_sql(self, expression: exp.QueryBand) -> str: 2977 this = self.sql(expression, "this") 2978 update = " UPDATE" if expression.args.get("update") else "" 2979 scope = self.sql(expression, "scope") 2980 scope = f" FOR {scope}" if scope else "" 2981 2982 return f"QUERY_BAND = {this}{update}{scope}"
2987 def lock_sql(self, expression: exp.Lock) -> str: 2988 if not self.LOCKING_READS_SUPPORTED: 2989 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2990 return "" 2991 2992 update = expression.args["update"] 2993 key = expression.args.get("key") 2994 if update: 2995 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2996 else: 2997 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2998 expressions = self.expressions(expression, flat=True) 2999 expressions = f" OF {expressions}" if expressions else "" 3000 wait = expression.args.get("wait") 3001 3002 if wait is not None: 3003 if isinstance(wait, exp.Literal): 3004 wait = f" WAIT {self.sql(wait)}" 3005 else: 3006 wait = " NOWAIT" if wait else " SKIP LOCKED" 3007 3008 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:
3016 def escape_str( 3017 self, 3018 text: str, 3019 escape_backslash: bool = True, 3020 delimiter: str | None = None, 3021 escaped_delimiter: str | None = None, 3022 is_byte_string: bool = False, 3023 ) -> str: 3024 if is_byte_string: 3025 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3026 else: 3027 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3028 3029 if supports_escape_sequences: 3030 text = "".join( 3031 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3032 for ch in text 3033 ) 3034 3035 delimiter = delimiter or self.dialect.QUOTE_END 3036 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3037 3038 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3040 def loaddata_sql(self, expression: exp.LoadData) -> str: 3041 is_overwrite = expression.args.get("overwrite") 3042 overwrite = " OVERWRITE" if is_overwrite else "" 3043 this = self.sql(expression, "this") 3044 3045 files = expression.args.get("files") 3046 if files: 3047 files_sql = self.expressions(files, flat=True) 3048 files_sql = f"FILES{self.wrap(files_sql)}" 3049 if is_overwrite: 3050 this = f" {this}" 3051 elif expression.args.get("temp"): 3052 this = f" INTO TEMP TABLE {this}" 3053 else: 3054 this = f" INTO TABLE {this}" 3055 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3056 3057 local = " LOCAL" if expression.args.get("local") else "" 3058 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3059 this = f" INTO TABLE {this}" 3060 partition = self.sql(expression, "partition") 3061 partition = f" {partition}" if partition else "" 3062 input_format = self.sql(expression, "input_format") 3063 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3064 serde = self.sql(expression, "serde") 3065 serde = f" SERDE {serde}" if serde else "" 3066 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3080 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3081 this = self.sql(expression, "this") 3082 this = f"{this} " if this else this 3083 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3084 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3086 def withfill_sql(self, expression: exp.WithFill) -> str: 3087 from_sql = self.sql(expression, "from_") 3088 from_sql = f" FROM {from_sql}" if from_sql else "" 3089 to_sql = self.sql(expression, "to") 3090 to_sql = f" TO {to_sql}" if to_sql else "" 3091 step_sql = self.sql(expression, "step") 3092 step_sql = f" STEP {step_sql}" if step_sql else "" 3093 interpolated_values = [ 3094 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3095 if isinstance(e, exp.Alias) 3096 else self.sql(e, "this") 3097 for e in expression.args.get("interpolate") or [] 3098 ] 3099 interpolate = ( 3100 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3101 ) 3102 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3154 def ordered_sql(self, expression: exp.Ordered) -> str: 3155 desc = expression.args.get("desc") 3156 asc = not desc 3157 3158 nulls_first = expression.args.get("nulls_first") 3159 nulls_last = not nulls_first 3160 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3161 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3162 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3163 3164 this = self.sql(expression, "this") 3165 3166 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3167 nulls_sort_change = "" 3168 if nulls_first and ( 3169 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3170 ): 3171 nulls_sort_change = " NULLS FIRST" 3172 elif ( 3173 nulls_last 3174 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3175 and not nulls_are_last 3176 ): 3177 nulls_sort_change = " NULLS LAST" 3178 3179 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3180 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3181 window = expression.find_ancestor(exp.Window, exp.Select) 3182 3183 if isinstance(window, exp.Window): 3184 window_this = window.this 3185 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3186 window_this = window_this.this 3187 spec = window.args.get("spec") 3188 else: 3189 window_this = None 3190 spec = None 3191 3192 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3193 # without a spec or with a ROWS spec, but not with RANGE 3194 if not ( 3195 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3196 and (not spec or spec.text("kind").upper() == "ROWS") 3197 ): 3198 if window_this and spec: 3199 self.unsupported( 3200 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3201 ) 3202 nulls_sort_change = "" 3203 elif self.NULL_ORDERING_SUPPORTED is False and ( 3204 (asc and nulls_sort_change == " NULLS LAST") 3205 or (desc and nulls_sort_change == " NULLS FIRST") 3206 ): 3207 # BigQuery does not allow these ordering/nulls combinations when used under 3208 # an aggregation func or under a window containing one 3209 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3210 3211 if isinstance(ancestor, exp.Window): 3212 ancestor = ancestor.this 3213 if isinstance(ancestor, exp.AggFunc): 3214 self.unsupported( 3215 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3216 ) 3217 nulls_sort_change = "" 3218 elif self.NULL_ORDERING_SUPPORTED is None: 3219 if expression.this.is_int: 3220 self.unsupported( 3221 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3222 ) 3223 elif not isinstance(expression.this, exp.Rand): 3224 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3225 target = self.sql(resolved) if resolved is not None else this 3226 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3227 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3228 nulls_sort_change = "" 3229 3230 with_fill = self.sql(expression, "with_fill") 3231 with_fill = f" {with_fill}" if with_fill else "" 3232 3233 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3243 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3244 partition = self.partition_by_sql(expression) 3245 order = self.sql(expression, "order") 3246 measures = self.expressions(expression, key="measures") 3247 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3248 rows = self.sql(expression, "rows") 3249 rows = self.seg(rows) if rows else "" 3250 after = self.sql(expression, "after") 3251 after = self.seg(after) if after else "" 3252 pattern = self.sql(expression, "pattern") 3253 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3254 definition_sqls = [ 3255 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3256 for definition in expression.args.get("define", []) 3257 ] 3258 definitions = self.expressions(sqls=definition_sqls) 3259 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3260 body = "".join( 3261 ( 3262 partition, 3263 order, 3264 measures, 3265 rows, 3266 after, 3267 pattern, 3268 define, 3269 ) 3270 ) 3271 alias = self.sql(expression, "alias") 3272 alias = f" {alias}" if alias else "" 3273 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3275 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3276 limit = expression.args.get("limit") 3277 3278 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3279 count = limit.args.get("count") 3280 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3281 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3282 limit = exp.Limit( 3283 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3284 ) 3285 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3286 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3287 3288 return csv( 3289 *sqls, 3290 *[self.sql(join) for join in expression.args.get("joins") or []], 3291 self.sql(expression, "match"), 3292 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3293 self.sql(expression, "prewhere"), 3294 self.sql(expression, "where"), 3295 self.sql(expression, "connect"), 3296 self.sql(expression, "group"), 3297 self.sql(expression, "having"), 3298 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3299 self.sql(expression, "order"), 3300 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3301 *self.after_limit_modifiers(expression), 3302 self.options_modifier(expression), 3303 self.sql(expression, "for_"), 3304 sep="", 3305 )
3311 def forclause_sql(self, expression: exp.ForClause) -> str: 3312 kind = expression.args["kind"] 3313 if kind == "BROWSE": 3314 return f"{self.sep()}FOR BROWSE" 3315 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3316 # the target dialect doesn't support QueryOption, so we drop the clause. 3317 options = self.expressions(expression, key="expressions") 3318 if not options: 3319 return "" 3320 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]:
3339 def select_sql(self, expression: exp.Select) -> str: 3340 into = expression.args.get("into") 3341 if not self.SUPPORTS_SELECT_INTO and into: 3342 into.pop() 3343 3344 hint = self.sql(expression, "hint") 3345 distinct = self.sql(expression, "distinct") 3346 distinct = f" {distinct}" if distinct else "" 3347 kind = self.sql(expression, "kind") 3348 3349 limit = expression.args.get("limit") 3350 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3351 top = self.limit_sql(limit, top=True) 3352 limit.pop() 3353 else: 3354 top = "" 3355 3356 expressions = self.expressions(expression) 3357 3358 if kind: 3359 if kind in self.SELECT_KINDS: 3360 kind = f" AS {kind}" 3361 else: 3362 if kind == "STRUCT": 3363 expressions = self.expressions( 3364 sqls=[ 3365 self.sql( 3366 exp.Struct( 3367 expressions=[ 3368 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3369 if isinstance(e, exp.Alias) 3370 else e 3371 for e in expression.expressions 3372 ] 3373 ) 3374 ) 3375 ] 3376 ) 3377 kind = "" 3378 3379 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3380 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3381 3382 exclude = expression.args.get("exclude") 3383 3384 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3385 exclude_sql = self.expressions(sqls=exclude, flat=True) 3386 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3387 3388 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3389 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3390 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3391 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3392 sql = self.query_modifiers( 3393 expression, 3394 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3395 self.sql(expression, "into", comment=False), 3396 self.sql(expression, "from_", comment=False), 3397 ) 3398 3399 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3400 if expression.args.get("with_"): 3401 sql = self.maybe_comment(sql, expression) 3402 expression.pop_comments() 3403 3404 sql = self.prepend_ctes(expression, sql) 3405 3406 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3407 expression.set("exclude", None) 3408 subquery = expression.subquery(copy=False) 3409 star = exp.Star(except_=exclude) 3410 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3411 3412 if not self.SUPPORTS_SELECT_INTO and into: 3413 if into.args.get("temporary"): 3414 table_kind = " TEMPORARY" 3415 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3416 table_kind = " UNLOGGED" 3417 else: 3418 table_kind = "" 3419 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3420 3421 return sql
3433 def star_sql(self, expression: exp.Star) -> str: 3434 except_ = self.expressions(expression, key="except_", flat=True) 3435 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3436 replace = self.expressions(expression, key="replace", flat=True) 3437 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3438 rename = self.expressions(expression, key="rename", flat=True) 3439 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3440 ilike = self.sql(expression, "ilike") 3441 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3442 return f"*{ilike}{except_}{replace}{rename}"
3458 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3459 alias = self.sql(expression, "alias") 3460 alias = f"{sep}{alias}" if alias else "" 3461 sample = self.sql(expression, "sample") 3462 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3463 alias = f"{sample}{alias}" 3464 3465 # Set to None so it's not generated again by self.query_modifiers() 3466 expression.set("sample", None) 3467 3468 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3469 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3470 return self.prepend_ctes(expression, sql)
3476 def unnest_sql(self, expression: exp.Unnest) -> str: 3477 args = self.expressions(expression, flat=True) 3478 3479 alias = expression.args.get("alias") 3480 offset = expression.args.get("offset") 3481 3482 if self.UNNEST_WITH_ORDINALITY: 3483 if alias and isinstance(offset, exp.Expr): 3484 alias.append("columns", offset) 3485 expression.set("offset", None) 3486 3487 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3488 columns = alias.columns 3489 alias = self.sql(columns[0]) if columns else "" 3490 else: 3491 alias = self.sql(alias) 3492 3493 alias = f" AS {alias}" if alias else alias 3494 if self.UNNEST_WITH_ORDINALITY: 3495 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3496 else: 3497 if isinstance(offset, exp.Expr): 3498 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3499 elif offset: 3500 suffix = f"{alias} WITH OFFSET" 3501 else: 3502 suffix = alias 3503 3504 return f"UNNEST({args}){suffix}"
3513 def window_sql(self, expression: exp.Window) -> str: 3514 this = self.sql(expression, "this") 3515 partition = self.partition_by_sql(expression) 3516 order = expression.args.get("order") 3517 order = self.order_sql(order, flat=True) if order else "" 3518 spec = self.sql(expression, "spec") 3519 alias = self.sql(expression, "alias") 3520 over = self.sql(expression, "over") or "OVER" 3521 3522 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3523 3524 first = expression.args.get("first") 3525 if first is None: 3526 first = "" 3527 else: 3528 first = "FIRST" if first else "LAST" 3529 3530 if not partition and not order and not spec and alias: 3531 return f"{this} {alias}" 3532 3533 args = self.format_args( 3534 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3535 ) 3536 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3542 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3543 kind = self.sql(expression, "kind") 3544 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3545 end = ( 3546 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3547 or "CURRENT ROW" 3548 ) 3549 3550 window_spec = f"{kind} BETWEEN {start} AND {end}" 3551 3552 exclude = self.sql(expression, "exclude") 3553 if exclude: 3554 if self.SUPPORTS_WINDOW_EXCLUDE: 3555 window_spec += f" EXCLUDE {exclude}" 3556 else: 3557 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3558 3559 return window_spec
3566 def between_sql(self, expression: exp.Between) -> str: 3567 this = self.sql(expression, "this") 3568 low = self.sql(expression, "low") 3569 high = self.sql(expression, "high") 3570 symmetric = expression.args.get("symmetric") 3571 3572 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3573 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3574 3575 flag = ( 3576 " SYMMETRIC" 3577 if symmetric 3578 else " ASYMMETRIC" 3579 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3580 else "" # silently drop ASYMMETRIC – semantics identical 3581 ) 3582 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]:
3584 def bracket_offset_expressions( 3585 self, expression: exp.Bracket, index_offset: int | None = None 3586 ) -> list[exp.Expr]: 3587 if expression.args.get("json_access"): 3588 return expression.expressions 3589 3590 return apply_index_offset( 3591 expression.this, 3592 expression.expressions, 3593 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3594 dialect=self.dialect, 3595 )
3608 def any_sql(self, expression: exp.Any) -> str: 3609 this = self.sql(expression, "this") 3610 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3611 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3612 this = self.wrap(this) 3613 return f"ANY{this}" 3614 return f"ANY {this}"
3619 def case_sql(self, expression: exp.Case) -> str: 3620 this = self.sql(expression, "this") 3621 statements = [f"CASE {this}" if this else "CASE"] 3622 3623 for e in expression.args["ifs"]: 3624 statements.append(f"WHEN {self.sql(e, 'this')}") 3625 statements.append(f"THEN {self.sql(e, 'true')}") 3626 3627 default = self.sql(expression, "default") 3628 3629 if default: 3630 statements.append(f"ELSE {default}") 3631 3632 statements.append("END") 3633 3634 if self.pretty and self.too_wide(statements): 3635 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3636 3637 return " ".join(statements)
3649 def extract_sql(self, expression: exp.Extract) -> str: 3650 import sqlglot.dialects.dialect 3651 3652 this = ( 3653 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3654 if self.NORMALIZE_EXTRACT_DATE_PARTS 3655 else expression.this 3656 ) 3657 if self.EXTRACT_ALLOWS_QUOTES: 3658 this_sql = self.sql(this) 3659 elif isinstance(this, exp.WeekStart): 3660 this_sql = self.weekstart_name(this) 3661 else: 3662 this_sql = this.name 3663 expression_sql = self.sql(expression, "expression") 3664 3665 return f"EXTRACT({this_sql} FROM {expression_sql})"
3667 def trim_sql(self, expression: exp.Trim) -> str: 3668 trim_type = self.sql(expression, "position") 3669 3670 if trim_type == "LEADING": 3671 func_name = "LTRIM" 3672 elif trim_type == "TRAILING": 3673 func_name = "RTRIM" 3674 else: 3675 func_name = "TRIM" 3676 3677 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3679 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3680 args = expression.expressions 3681 if isinstance(expression, exp.ConcatWs): 3682 args = args[1:] # Skip the delimiter 3683 3684 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3685 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3686 3687 concat_coalesce = ( 3688 self.dialect.CONCAT_WS_COALESCE 3689 if isinstance(expression, exp.ConcatWs) 3690 else self.dialect.CONCAT_COALESCE 3691 ) 3692 3693 if not concat_coalesce and expression.args.get("coalesce"): 3694 3695 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3696 if not e.type: 3697 import sqlglot.optimizer.annotate_types 3698 3699 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3700 3701 if e.is_string or e.is_type(exp.DType.ARRAY): 3702 return e 3703 3704 return exp.func("coalesce", e, exp.Literal.string("")) 3705 3706 args = [_wrap_with_coalesce(e) for e in args] 3707 3708 return args
3710 def concat_sql(self, expression: exp.Concat) -> str: 3711 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3712 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3713 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3714 # instead of coalescing them to empty string. 3715 import sqlglot.dialects.dialect 3716 3717 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3718 3719 expressions = self.convert_concat_args(expression) 3720 3721 # Some dialects don't allow a single-argument CONCAT call 3722 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3723 return self.sql(expressions[0]) 3724 3725 return self.func("CONCAT", *expressions)
3727 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3728 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3729 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3730 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3731 all_args = expression.expressions 3732 expression.set("coalesce", True) 3733 return self.sql( 3734 exp.case() 3735 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3736 .else_(expression) 3737 ) 3738 3739 return self.func( 3740 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3741 )
3747 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3748 expressions = self.expressions(expression, flat=True) 3749 expressions = f" ({expressions})" if expressions else "" 3750 reference = self.sql(expression, "reference") 3751 reference = f" {reference}" if reference else "" 3752 delete = self.sql(expression, "delete") 3753 delete = f" ON DELETE {delete}" if delete else "" 3754 update = self.sql(expression, "update") 3755 update = f" ON UPDATE {update}" if update else "" 3756 options = self.expressions(expression, key="options", flat=True, sep=" ") 3757 options = f" {options}" if options else "" 3758 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3760 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3761 this = self.sql(expression, "this") 3762 this = f" {this}" if this else "" 3763 expressions = self.expressions(expression, flat=True) 3764 include = self.sql(expression, "include") 3765 options = self.expressions(expression, key="options", flat=True, sep=" ") 3766 options = f" {options}" if options else "" 3767 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3776 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3777 if self.MATCH_AGAINST_TABLE_PREFIX: 3778 expressions = [] 3779 for expr in expression.expressions: 3780 if isinstance(expr, exp.Table): 3781 expressions.append(f"TABLE {self.sql(expr)}") 3782 else: 3783 expressions.append(expr) 3784 else: 3785 expressions = expression.expressions 3786 3787 modifier = expression.args.get("modifier") 3788 modifier = f" {modifier}" if modifier else "" 3789 return ( 3790 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3791 )
3804 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3805 if isinstance(expression, exp.JSONPathPart): 3806 transform = self.TRANSFORMS.get(expression.__class__) 3807 if not callable(transform): 3808 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3809 return "" 3810 3811 return transform(self, expression) 3812 3813 if isinstance(expression, int): 3814 return str(expression) 3815 3816 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3817 escaped = expression.replace("'", "\\'") 3818 escaped = f"\\'{expression}\\'" 3819 else: 3820 escaped = expression.replace('"', '\\"') 3821 escaped = f'"{escaped}"' 3822 3823 return escaped
3828 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3829 # Output the Teradata column FORMAT override. 3830 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3831 this = self.sql(expression, "this") 3832 fmt = self.sql(expression, "format") 3833 return f"{this} (FORMAT {fmt})"
3861 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3862 null_handling = expression.args.get("null_handling") 3863 null_handling = f" {null_handling}" if null_handling else "" 3864 return_type = self.sql(expression, "return_type") 3865 return_type = f" RETURNING {return_type}" if return_type else "" 3866 strict = " STRICT" if expression.args.get("strict") else "" 3867 return self.func( 3868 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3869 )
3871 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3872 this = self.sql(expression, "this") 3873 order = self.sql(expression, "order") 3874 null_handling = expression.args.get("null_handling") 3875 null_handling = f" {null_handling}" if null_handling else "" 3876 return_type = self.sql(expression, "return_type") 3877 return_type = f" RETURNING {return_type}" if return_type else "" 3878 strict = " STRICT" if expression.args.get("strict") else "" 3879 return self.func( 3880 "JSON_ARRAYAGG", 3881 this, 3882 suffix=f"{order}{null_handling}{return_type}{strict})", 3883 )
3885 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3886 path = self.sql(expression, "path") 3887 path = f" PATH {path}" if path else "" 3888 nested_schema = self.sql(expression, "nested_schema") 3889 3890 if nested_schema: 3891 return f"NESTED{path} {nested_schema}" 3892 3893 this = self.sql(expression, "this") 3894 kind = self.sql(expression, "kind") 3895 kind = f" {kind}" if kind else "" 3896 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3897 3898 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3899 return f"{this}{kind}{format_json}{path}{ordinality}"
3904 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3905 this = self.sql(expression, "this") 3906 path = self.sql(expression, "path") 3907 path = f", {path}" if path else "" 3908 error_handling = expression.args.get("error_handling") 3909 error_handling = f" {error_handling}" if error_handling else "" 3910 empty_handling = expression.args.get("empty_handling") 3911 empty_handling = f" {empty_handling}" if empty_handling else "" 3912 schema = self.sql(expression, "schema") 3913 return self.func( 3914 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3915 )
3917 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3918 this = self.sql(expression, "this") 3919 kind = self.sql(expression, "kind") 3920 path = self.sql(expression, "path") 3921 path = f" {path}" if path else "" 3922 as_json = " AS JSON" if expression.args.get("as_json") else "" 3923 return f"{this} {kind}{path}{as_json}"
3925 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3926 this = self.sql(expression, "this") 3927 path = self.sql(expression, "path") 3928 path = f", {path}" if path else "" 3929 expressions = self.expressions(expression) 3930 with_ = ( 3931 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3932 if expressions 3933 else "" 3934 ) 3935 return f"OPENJSON({this}{path}){with_}"
3937 def in_sql(self, expression: exp.In) -> str: 3938 query = expression.args.get("query") 3939 unnest = expression.args.get("unnest") 3940 field = expression.args.get("field") 3941 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3942 3943 if query: 3944 in_sql = self.sql(query) 3945 elif unnest: 3946 in_sql = self.in_unnest_op(unnest) 3947 elif field: 3948 in_sql = self.sql(field) 3949 else: 3950 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3951 3952 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3957 def interval_sql(self, expression: exp.Interval) -> str: 3958 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3959 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 3960 exp.AutoRefreshProperty, 3961 ) 3962 interval_keyword = "INTERVAL" if include_keyword else "" 3963 unit_expression = expression.args.get("unit") 3964 unit = self.sql(unit_expression) if unit_expression else "" 3965 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3966 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3967 unit = f" {unit}" if unit else "" 3968 3969 if self.SINGLE_STRING_INTERVAL: 3970 this = expression.this.name if expression.this else "" 3971 if this: 3972 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 3973 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3974 return f"{interval_keyword}'{this}'{unit}" 3975 return f"{interval_keyword}'{this}{unit}'" 3976 return f"{interval_keyword}{unit}" 3977 3978 this = self.sql(expression, "this") 3979 if this: 3980 if not include_keyword and expression.this.is_string: 3981 this = expression.this.name 3982 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 3983 this = f"({this})" 3984 if include_keyword: 3985 this = f" {this}" 3986 3987 return f"{interval_keyword}{this}{unit}"
3992 def reference_sql(self, expression: exp.Reference) -> str: 3993 this = self.sql(expression, "this") 3994 expressions = self.expressions(expression, flat=True) 3995 expressions = f"({expressions})" if expressions else "" 3996 options = self.expressions(expression, key="options", flat=True, sep=" ") 3997 options = f" {options}" if options else "" 3998 return f"REFERENCES {this}{expressions}{options}"
4000 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4001 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4002 parent = expression.parent 4003 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4004 4005 return self.func( 4006 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4007 )
4027 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4028 alias = expression.args["alias"] 4029 4030 parent = expression.parent 4031 pivot = parent and parent.parent 4032 4033 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4034 identifier_alias = isinstance(alias, exp.Identifier) 4035 literal_alias = isinstance(alias, exp.Literal) 4036 4037 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4038 alias.replace(exp.Literal.string(alias.output_name)) 4039 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4040 alias.replace(exp.to_identifier(alias.output_name)) 4041 4042 return self.alias_sql(expression)
def
fromiso8601timestamp_sql( self, expression: sqlglot.expressions.temporal.FromISO8601Timestamp) -> str:
def
fromiso8601timestampnanos_sql( self, expression: sqlglot.expressions.temporal.FromISO8601TimestampNanos) -> str:
def
and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.core.Or, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.core.Xor, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.core.Connector, op: str, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
4083 def connector_sql( 4084 self, 4085 expression: exp.Connector, 4086 op: str, 4087 stack: list[str | exp.Expr] | None = None, 4088 ) -> str: 4089 if stack is not None: 4090 stack.append(expression.right) 4091 if expression.comments and self.comments: 4092 op = self.maybe_comment(op, comments=expression.comments) 4093 4094 stack.extend((op, expression.left)) 4095 return op 4096 4097 stack = [expression] 4098 sqls: list[str] = [] 4099 ops = set() 4100 4101 while stack: 4102 node = stack.pop() 4103 if isinstance(node, exp.Connector): 4104 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4105 else: 4106 sql = self.sql(node) 4107 if sqls and sqls[-1] in ops: 4108 sqls[-1] += f" {sql}" 4109 else: 4110 sqls.append(sql) 4111 4112 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4113 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4133 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4134 format_sql = self.sql(expression, "format") 4135 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4136 to_sql = self.sql(expression, "to") 4137 to_sql = f" {to_sql}" if to_sql else "" 4138 action = self.sql(expression, "action") 4139 action = f" {action}" if action else "" 4140 default = self.sql(expression, "default") 4141 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4142 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4172 def comment_sql(self, expression: exp.Comment) -> str: 4173 this = self.sql(expression, "this") 4174 kind = expression.args["kind"] 4175 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4176 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4177 expression_sql = self.sql(expression, "expression") 4178 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4180 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4181 this = self.sql(expression, "this") 4182 delete = " DELETE" if expression.args.get("delete") else "" 4183 recompress = self.sql(expression, "recompress") 4184 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4185 to_disk = self.sql(expression, "to_disk") 4186 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4187 to_volume = self.sql(expression, "to_volume") 4188 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4189 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4191 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4192 where = self.sql(expression, "where") 4193 group = self.sql(expression, "group") 4194 aggregates = self.expressions(expression, key="aggregates") 4195 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4196 4197 if not (where or group or aggregates) and len(expression.expressions) == 1: 4198 return f"TTL {self.expressions(expression, flat=True)}" 4199 4200 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4219 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4220 this = self.sql(expression, "this") 4221 4222 exists = "" 4223 if expression.args.get("exists"): 4224 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4225 exists = " IF EXISTS" 4226 else: 4227 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4228 4229 dtype = self.sql(expression, "dtype") 4230 if dtype: 4231 collate = self.sql(expression, "collate") 4232 collate = f" COLLATE {collate}" if collate else "" 4233 using = self.sql(expression, "using") 4234 using = f" USING {using}" if using else "" 4235 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4236 null_constraint = self._alter_column_null_constraint_sql(expression) 4237 4238 return ( 4239 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4240 f"{collate}{using}{null_constraint}" 4241 ) 4242 4243 default = self.sql(expression, "default") 4244 if default: 4245 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4246 4247 comment = self.sql(expression, "comment") 4248 if comment: 4249 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4250 4251 visible = expression.args.get("visible") 4252 if visible: 4253 return f"ALTER COLUMN{exists} {this} SET {visible}" 4254 4255 allow_null = expression.args.get("allow_null") 4256 drop = expression.args.get("drop") 4257 4258 if not drop and not allow_null: 4259 self.unsupported("Unsupported ALTER COLUMN syntax") 4260 4261 if allow_null is not None: 4262 keyword = "DROP" if drop else "SET" 4263 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4264 4265 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4278 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4279 this = self.sql(expression, "this") 4280 rename_from = self.sql(expression, "rename_from") 4281 if rename_from: 4282 if not self.SUPPORTS_CHANGE_COLUMN: 4283 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4284 return f"CHANGE COLUMN {rename_from} {this}" 4285 if not self.SUPPORTS_MODIFY_COLUMN: 4286 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4287 return f"MODIFY COLUMN {this}"
4303 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4304 compound = " COMPOUND" if expression.args.get("compound") else "" 4305 this = self.sql(expression, "this") 4306 expressions = self.expressions(expression, flat=True) 4307 expressions = f"({expressions})" if expressions else "" 4308 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4310 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4311 if not self.RENAME_TABLE_WITH_DB: 4312 # Remove db from tables 4313 expression = expression.transform( 4314 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4315 ).assert_is(exp.AlterRename) 4316 this = self.sql(expression, "this") 4317 to_kw = " TO" if include_to else "" 4318 return f"RENAME{to_kw} {this}"
4333 def alter_sql(self, expression: exp.Alter) -> str: 4334 actions = expression.args["actions"] 4335 4336 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4337 actions[0], exp.ColumnDef 4338 ): 4339 actions_sql = self.expressions(expression, key="actions", flat=True) 4340 actions_sql = f"ADD {actions_sql}" 4341 else: 4342 actions_list = [] 4343 for action in actions: 4344 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4345 action_sql = self.add_column_sql(action) 4346 else: 4347 action_sql = self.sql(action) 4348 if isinstance(action, exp.Query): 4349 action_sql = f"AS {action_sql}" 4350 4351 actions_list.append(action_sql) 4352 4353 actions_sql = self.format_args(*actions_list).lstrip("\n") 4354 4355 iceberg = ( 4356 "ICEBERG " 4357 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4358 else "" 4359 ) 4360 exists = " IF EXISTS" if expression.args.get("exists") else "" 4361 on_cluster = self.sql(expression, "cluster") 4362 on_cluster = f" {on_cluster}" if on_cluster else "" 4363 only = " ONLY" if expression.args.get("only") else "" 4364 options = self.expressions(expression, key="options") 4365 options = f", {options}" if options else "" 4366 kind = self.sql(expression, "kind") 4367 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4368 check = " WITH CHECK" if expression.args.get("check") else "" 4369 cascade = ( 4370 " CASCADE" 4371 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4372 else "" 4373 ) 4374 this = self.sql(expression, "this") 4375 this = f" {this}" if this else "" 4376 4377 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4384 def add_column_sql(self, expression: exp.Expr) -> str: 4385 sql = self.sql(expression) 4386 if isinstance(expression, exp.Schema): 4387 column_text = " COLUMNS" 4388 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4389 column_text = " COLUMN" 4390 else: 4391 column_text = "" 4392 4393 return f"ADD{column_text} {sql}"
4406 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4407 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4408 location = self.sql(expression, "location") 4409 location = f" {location}" if location else "" 4410 return f"ADD {exists}{self.sql(expression.this)}{location}"
4412 def distinct_sql(self, expression: exp.Distinct) -> str: 4413 this = self.expressions(expression, flat=True) 4414 4415 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4416 case = exp.case() 4417 for arg in expression.expressions: 4418 case = case.when(arg.is_(exp.null()), exp.null()) 4419 this = self.sql(case.else_(f"({this})")) 4420 4421 this = f" {this}" if this else "" 4422 4423 on = self.sql(expression, "on") 4424 on = f" ON {on}" if on else "" 4425 return f"DISTINCT{this}{on}"
4452 def div_sql(self, expression: exp.Div) -> str: 4453 l, r = expression.left, expression.right 4454 4455 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4456 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4457 4458 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4459 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4460 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4461 4462 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4463 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4464 return self.sql( 4465 exp.cast( 4466 l / r, 4467 to=exp.DType.BIGINT, 4468 ) 4469 ) 4470 4471 return self.binary(expression, "/")
4496 def escape_sql(self, expression: exp.Escape) -> str: 4497 this = expression.this 4498 if ( 4499 isinstance(this, (exp.Like, exp.ILike)) 4500 and isinstance(this.expression, (exp.All, exp.Any)) 4501 and not self.SUPPORTS_LIKE_QUANTIFIERS 4502 ): 4503 return self._like_sql(this, escape=expression) 4504 return self.binary(expression, "ESCAPE")
4515 def is_sql(self, expression: exp.Is) -> str: 4516 negate = expression.args.get("negate") 4517 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4518 positive = bool(expression.expression.this) != bool(negate) 4519 return self.sql(expression.this if positive else exp.not_(expression.this)) 4520 return self.binary(expression, "IS NOT" if negate else "IS")
4621 def log_sql(self, expression: exp.Log) -> str: 4622 this = expression.this 4623 expr = expression.expression 4624 4625 if self.dialect.LOG_BASE_FIRST is False: 4626 this, expr = expr, this 4627 elif self.dialect.LOG_BASE_FIRST is None and expr: 4628 if this.name in ("2", "10"): 4629 return self.func(f"LOG{this.name}", expr) 4630 4631 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4632 4633 return self.func("LOG", this, expr)
4642 def binary(self, expression: exp.Binary, op: str) -> str: 4643 sqls: list[str] = [] 4644 stack: list[None | str | exp.Expr] = [expression] 4645 binary_type = type(expression) 4646 4647 while stack: 4648 node = stack.pop() 4649 4650 if type(node) is binary_type: 4651 op_func = node.args.get("operator") 4652 if op_func: 4653 op = f"OPERATOR({self.sql(op_func)})" 4654 4655 stack.append(node.args.get("expression")) 4656 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4657 stack.append(node.args.get("this")) 4658 else: 4659 sqls.append(self.sql(node)) 4660 4661 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4670 def function_fallback_sql(self, expression: exp.Func) -> str: 4671 args = [] 4672 4673 for key in expression.arg_types: 4674 arg_value = expression.args.get(key) 4675 4676 if isinstance(arg_value, list): 4677 for value in arg_value: 4678 args.append(value) 4679 elif arg_value is not None: 4680 args.append(arg_value) 4681 4682 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4683 name = expression.meta_get("name") or expression.sql_name() 4684 else: 4685 name = expression.sql_name() 4686 4687 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:
4700 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4701 arg_sqls = tuple( 4702 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4703 ) 4704 if self.pretty and self.too_wide(arg_sqls): 4705 return self.indent( 4706 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4707 ) 4708 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:
4713 def format_time( 4714 self, 4715 expression: exp.Expr, 4716 inverse_time_mapping: dict[str, str] | None = None, 4717 inverse_time_trie: dict | None = None, 4718 ) -> str | None: 4719 return format_time( 4720 self.sql(expression, "format"), 4721 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4722 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4723 )
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:
4725 def expressions( 4726 self, 4727 expression: exp.Expr | None = None, 4728 key: str | None = None, 4729 sqls: t.Collection[str | exp.Expr] | None = None, 4730 flat: bool = False, 4731 indent: bool = True, 4732 skip_first: bool = False, 4733 skip_last: bool = False, 4734 sep: str = ", ", 4735 prefix: str = "", 4736 dynamic: bool = False, 4737 new_line: bool = False, 4738 ) -> str: 4739 expressions = expression.args.get(key or "expressions") if expression else sqls 4740 4741 if not expressions: 4742 return "" 4743 4744 if flat: 4745 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4746 4747 num_sqls = len(expressions) 4748 result_sqls = [] 4749 4750 for i, e in enumerate(expressions): 4751 sql = self.sql(e, comment=False) 4752 if not sql: 4753 continue 4754 4755 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4756 4757 if self.pretty: 4758 if self.leading_comma: 4759 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4760 else: 4761 result_sqls.append( 4762 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4763 ) 4764 else: 4765 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4766 4767 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4768 if new_line: 4769 result_sqls.insert(0, "") 4770 result_sqls.append("") 4771 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4772 else: 4773 result_sql = "".join(result_sqls) 4774 4775 return ( 4776 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4777 if indent 4778 else result_sql 4779 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4781 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4782 flat = flat or isinstance(expression.parent, exp.Properties) 4783 expressions_sql = self.expressions(expression, flat=flat) 4784 if flat: 4785 return f"{op} {expressions_sql}" 4786 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4788 def naked_property(self, expression: exp.Property) -> str: 4789 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4790 if not property_name: 4791 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4792 return f"{property_name} {self.sql(expression, 'this')}"
4800 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4801 this = self.sql(expression, "this") 4802 expressions = self.no_identify(self.expressions, expression) 4803 expressions = ( 4804 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4805 ) 4806 return f"{this}{expressions}" if expressions.strip() != "" else this
4825 def when_sql(self, expression: exp.When) -> str: 4826 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4827 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4828 condition = self.sql(expression, "condition") 4829 condition = f" AND {condition}" if condition else "" 4830 4831 then_expression = expression.args.get("then") 4832 if isinstance(then_expression, exp.Insert): 4833 this = self.sql(then_expression, "this") 4834 this = f"INSERT {this}" if this else "INSERT" 4835 then = self.sql(then_expression, "expression") 4836 then = f"{this} VALUES {then}" if then else this 4837 elif isinstance(then_expression, exp.Update): 4838 if isinstance(then_expression.args.get("expressions"), exp.Star): 4839 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4840 else: 4841 expressions_sql = self.expressions(then_expression) 4842 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4843 else: 4844 then = self.sql(then_expression) 4845 4846 if isinstance(then_expression, (exp.Insert, exp.Update)): 4847 where = self.sql(then_expression, "where") 4848 if where and not self.SUPPORTS_MERGE_WHERE: 4849 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4850 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4851 where = "" 4852 then = f"{then}{where}" 4853 return f"WHEN {matched}{source}{condition} THEN {then}"
4858 def merge_sql(self, expression: exp.Merge) -> str: 4859 table = expression.this 4860 table_alias = "" 4861 4862 hints = table.args.get("hints") 4863 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4864 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4865 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4866 4867 this = self.sql(table) 4868 using = f"USING {self.sql(expression, 'using')}" 4869 whens = self.sql(expression, "whens") 4870 4871 on = self.sql(expression, "on") 4872 on = f"ON {on}" if on else "" 4873 4874 if not on: 4875 on = self.expressions(expression, key="using_cond") 4876 on = f"USING ({on})" if on else "" 4877 4878 returning = self.sql(expression, "returning") 4879 if returning: 4880 whens = f"{whens}{returning}" 4881 4882 sep = self.sep() 4883 4884 return self.prepend_ctes( 4885 expression, 4886 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4887 )
@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:
4893 @unsupported_args("default") 4894 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4895 if not self.SUPPORTS_TO_NUMBER: 4896 self.unsupported("Unsupported TO_NUMBER function") 4897 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4898 4899 fmt = expression.args.get("format") 4900 if not fmt: 4901 self.unsupported("Conversion format is required for TO_NUMBER") 4902 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4903 4904 return self.func("TO_NUMBER", expression.this, fmt)
4906 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4907 this = self.sql(expression, "this") 4908 kind = self.sql(expression, "kind") 4909 settings_sql = self.expressions(expression, key="settings", sep=" ") 4910 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4911 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:
4932 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4933 expressions = self.expressions(expression, flat=True) 4934 expressions = f" {self.wrap(expressions)}" if expressions else "" 4935 buckets = self.sql(expression, "buckets") 4936 kind = self.sql(expression, "kind") 4937 buckets = f" BUCKETS {buckets}" if buckets else "" 4938 order = self.sql(expression, "order") 4939 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4944 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4945 expressions = self.expressions(expression, key="expressions", flat=True) 4946 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4947 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4948 buckets = self.sql(expression, "buckets") 4949 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4951 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4952 this = self.sql(expression, "this") 4953 having = self.sql(expression, "having") 4954 4955 if having: 4956 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4957 4958 return self.func("ANY_VALUE", this)
4960 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4961 transform = self.func("TRANSFORM", *expression.expressions) 4962 row_format_before = self.sql(expression, "row_format_before") 4963 row_format_before = f" {row_format_before}" if row_format_before else "" 4964 record_writer = self.sql(expression, "record_writer") 4965 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4966 using = f" USING {self.sql(expression, 'command_script')}" 4967 schema = self.sql(expression, "schema") 4968 schema = f" AS {schema}" if schema else "" 4969 row_format_after = self.sql(expression, "row_format_after") 4970 row_format_after = f" {row_format_after}" if row_format_after else "" 4971 record_reader = self.sql(expression, "record_reader") 4972 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4973 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:
4975 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4976 key_block_size = self.sql(expression, "key_block_size") 4977 if key_block_size: 4978 return f"KEY_BLOCK_SIZE = {key_block_size}" 4979 4980 using = self.sql(expression, "using") 4981 if using: 4982 return f"USING {using}" 4983 4984 parser = self.sql(expression, "parser") 4985 if parser: 4986 return f"WITH PARSER {parser}" 4987 4988 comment = self.sql(expression, "comment") 4989 if comment: 4990 return f"COMMENT {comment}" 4991 4992 visible = expression.args.get("visible") 4993 if visible is not None: 4994 return "VISIBLE" if visible else "INVISIBLE" 4995 4996 engine_attr = self.sql(expression, "engine_attr") 4997 if engine_attr: 4998 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4999 5000 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5001 if secondary_engine_attr: 5002 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5003 5004 self.unsupported("Unsupported index constraint option.") 5005 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5011 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5012 kind = self.sql(expression, "kind") 5013 kind = f"{kind} INDEX" if kind else "INDEX" 5014 this = self.sql(expression, "this") 5015 this = f" {this}" if this else "" 5016 index_type = self.sql(expression, "index_type") 5017 index_type = f" USING {index_type}" if index_type else "" 5018 expressions = self.expressions(expression, flat=True) 5019 expressions = f" ({expressions})" if expressions else "" 5020 options = self.expressions(expression, key="options", sep=" ") 5021 options = f" {options}" if options else "" 5022 return f"{kind}{this}{index_type}{expressions}{options}"
5024 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5025 if self.NVL2_SUPPORTED: 5026 return self.function_fallback_sql(expression) 5027 5028 case = exp.Case().when( 5029 expression.this.is_(exp.null()).not_(copy=False), 5030 expression.args["true"], 5031 copy=False, 5032 ) 5033 else_cond = expression.args.get("false") 5034 if else_cond: 5035 case.else_(else_cond, copy=False) 5036 5037 return self.sql(case)
5039 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5040 this = self.sql(expression, "this") 5041 expr = self.sql(expression, "expression") 5042 position = self.sql(expression, "position") 5043 position = f", {position}" if position else "" 5044 iterator = self.sql(expression, "iterator") 5045 condition = self.sql(expression, "condition") 5046 condition = f" IF {condition}" if condition else "" 5047 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5097 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5098 this_sql = self.sql(expression, "this") 5099 if isinstance(expression.this, exp.Table): 5100 this_sql = f"TABLE {this_sql}" 5101 5102 return self.func( 5103 "FORECAST", 5104 this_sql, 5105 expression.args.get("data_col"), 5106 expression.args.get("timestamp_col"), 5107 expression.args.get("model"), 5108 expression.args.get("id_cols"), 5109 expression.args.get("horizon"), 5110 expression.args.get("forecast_end_timestamp"), 5111 expression.args.get("confidence_level"), 5112 expression.args.get("output_historical_time_series"), 5113 expression.args.get("context_window"), 5114 )
5116 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5117 this_sql = self.sql(expression, "this") 5118 if isinstance(expression.this, exp.Table): 5119 this_sql = f"TABLE {this_sql}" 5120 5121 return self.func( 5122 "FEATURES_AT_TIME", 5123 this_sql, 5124 expression.args.get("time"), 5125 expression.args.get("num_rows"), 5126 expression.args.get("ignore_feature_nulls"), 5127 )
5129 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5130 this_sql = self.sql(expression, "this") 5131 if isinstance(expression.this, exp.Table): 5132 this_sql = f"TABLE {this_sql}" 5133 5134 query_table = self.sql(expression, "query_table") 5135 if isinstance(expression.args["query_table"], exp.Table): 5136 query_table = f"TABLE {query_table}" 5137 5138 return self.func( 5139 "VECTOR_SEARCH", 5140 this_sql, 5141 expression.args.get("column_to_search"), 5142 query_table, 5143 expression.args.get("query_column_to_search"), 5144 expression.args.get("top_k"), 5145 expression.args.get("distance_type"), 5146 expression.args.get("options"), 5147 )
5159 def toarray_sql(self, expression: exp.ToArray) -> str: 5160 arg = expression.this 5161 if not arg.type: 5162 import sqlglot.optimizer.annotate_types 5163 5164 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5165 5166 if arg.is_type(exp.DType.ARRAY): 5167 return self.sql(arg) 5168 5169 cond_for_null = arg.is_(exp.null()) 5170 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5172 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5173 this = expression.this 5174 time_format = self.format_time(expression) 5175 5176 if time_format: 5177 return self.sql( 5178 exp.cast( 5179 exp.StrToTime(this=this, format=expression.args["format"]), 5180 exp.DType.TIME, 5181 ) 5182 ) 5183 5184 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5185 return self.sql(this) 5186 5187 return self.sql(exp.cast(this, exp.DType.TIME))
5189 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5190 this = expression.this 5191 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5192 return self.sql(this) 5193 5194 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5196 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5197 this = expression.this 5198 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5199 return self.sql(this) 5200 5201 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5203 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5204 this = expression.this 5205 time_format = self.format_time(expression) 5206 safe = expression.args.get("safe") 5207 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5208 return self.sql( 5209 exp.cast( 5210 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5211 exp.DType.DATE, 5212 ) 5213 ) 5214 5215 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5216 return self.sql(this) 5217 5218 if safe: 5219 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5220 5221 return self.sql(exp.cast(this, exp.DType.DATE))
5233 def lastday_sql(self, expression: exp.LastDay) -> str: 5234 if self.LAST_DAY_SUPPORTS_DATE_PART: 5235 return self.function_fallback_sql(expression) 5236 5237 unit = expression.args.get("unit") 5238 if unit and unit.name.upper() != "MONTH": 5239 self.unsupported("Date parts are not supported in LAST_DAY.") 5240 5241 return self.func("LAST_DAY", expression.this)
5253 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5254 if self.CAN_IMPLEMENT_ARRAY_ANY: 5255 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5256 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5257 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5258 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5259 5260 import sqlglot.dialects.dialect 5261 5262 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5263 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5264 self.unsupported("ARRAY_ANY is unsupported") 5265 5266 return self.function_fallback_sql(expression)
5268 def struct_sql(self, expression: exp.Struct) -> str: 5269 expression.set( 5270 "expressions", 5271 [ 5272 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5273 if isinstance(e, exp.PropertyEQ) 5274 else e 5275 for e in expression.expressions 5276 ], 5277 ) 5278 5279 return self.function_fallback_sql(expression)
5287 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5288 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5289 tables = f" {self.expressions(expression)}" 5290 5291 exists = " IF EXISTS" if expression.args.get("exists") else "" 5292 5293 on_cluster = self.sql(expression, "cluster") 5294 on_cluster = f" {on_cluster}" if on_cluster else "" 5295 5296 identity = self.sql(expression, "identity") 5297 identity = f" {identity} IDENTITY" if identity else "" 5298 5299 option = self.sql(expression, "option") 5300 option = f" {option}" if option else "" 5301 5302 partition = self.sql(expression, "partition") 5303 partition = f" {partition}" if partition else "" 5304 5305 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5309 def convert_sql(self, expression: exp.Convert) -> str: 5310 to = expression.this 5311 value = expression.expression 5312 style = expression.args.get("style") 5313 safe = expression.args.get("safe") 5314 strict = expression.args.get("strict") 5315 5316 if not to or not value: 5317 return "" 5318 5319 # Retrieve length of datatype and override to default if not specified 5320 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5321 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5322 5323 transformed: exp.Expr | None = None 5324 cast = exp.Cast if strict else exp.TryCast 5325 5326 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5327 if isinstance(style, exp.Literal) and style.is_int: 5328 import sqlglot.dialects.tsql 5329 5330 style_value = style.name 5331 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5332 if not converted_style: 5333 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5334 5335 fmt = exp.Literal.string(converted_style) 5336 5337 if to.this == exp.DType.DATE: 5338 transformed = exp.StrToDate(this=value, format=fmt) 5339 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5340 transformed = exp.StrToTime(this=value, format=fmt) 5341 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5342 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5343 elif to.this == exp.DType.TEXT: 5344 transformed = exp.TimeToStr(this=value, format=fmt) 5345 5346 if not transformed: 5347 transformed = cast(this=value, to=to, safe=safe) 5348 5349 return self.sql(transformed)
5430 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5431 option = self.sql(expression, "this") 5432 5433 if expression.expressions: 5434 upper = option.upper() 5435 5436 # Snowflake FILE_FORMAT options are separated by whitespace 5437 sep = " " if upper == "FILE_FORMAT" else ", " 5438 5439 # Databricks copy/format options do not set their list of values with EQ 5440 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5441 values = self.expressions(expression, flat=True, sep=sep) 5442 return f"{option}{op}({values})" 5443 5444 value = self.sql(expression, "expression") 5445 5446 if not value: 5447 return option 5448 5449 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5450 5451 return f"{option}{op}{value}"
5453 def credentials_sql(self, expression: exp.Credentials) -> str: 5454 cred_expr = expression.args.get("credentials") 5455 if isinstance(cred_expr, exp.Literal): 5456 # Redshift case: CREDENTIALS <string> 5457 credentials = self.sql(expression, "credentials") 5458 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5459 else: 5460 # Snowflake case: CREDENTIALS = (...) 5461 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5462 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5463 5464 storage = self.sql(expression, "storage") 5465 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5466 5467 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5468 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5469 5470 iam_role = self.sql(expression, "iam_role") 5471 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5472 5473 region = self.sql(expression, "region") 5474 region = f" REGION {region}" if region else "" 5475 5476 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5478 def copy_sql(self, expression: exp.Copy) -> str: 5479 this = self.sql(expression, "this") 5480 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5481 5482 credentials = self.sql(expression, "credentials") 5483 credentials = self.seg(credentials) if credentials else "" 5484 files = self.expressions(expression, key="files", flat=True) 5485 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5486 5487 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5488 params = self.expressions( 5489 expression, 5490 key="params", 5491 sep=sep, 5492 new_line=True, 5493 skip_last=True, 5494 skip_first=True, 5495 indent=self.COPY_PARAMS_ARE_WRAPPED, 5496 ) 5497 5498 if params: 5499 if self.COPY_PARAMS_ARE_WRAPPED: 5500 params = f" WITH ({params})" 5501 elif not self.pretty and (files or credentials): 5502 params = f" {params}" 5503 5504 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5509 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5510 on_sql = "ON" if expression.args.get("on") else "OFF" 5511 filter_col: str | None = self.sql(expression, "filter_column") 5512 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5513 retention_period: str | None = self.sql(expression, "retention_period") 5514 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5515 5516 if filter_col or retention_period: 5517 on_sql = self.func("ON", filter_col, retention_period) 5518 5519 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5521 def maskingpolicycolumnconstraint_sql( 5522 self, expression: exp.MaskingPolicyColumnConstraint 5523 ) -> str: 5524 this = self.sql(expression, "this") 5525 expressions = self.expressions(expression, flat=True) 5526 expressions = f" USING ({expressions})" if expressions else "" 5527 return f"MASKING POLICY {this}{expressions}"
5537 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5538 this = self.sql(expression, "this") 5539 expr = expression.expression 5540 5541 if isinstance(expr, exp.Func): 5542 # T-SQL's CLR functions are case sensitive 5543 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5544 else: 5545 expr = self.sql(expression, "expression") 5546 5547 return self.scope_resolution(expr, this)
5555 def rand_sql(self, expression: exp.Rand) -> str: 5556 lower = self.sql(expression, "lower") 5557 upper = self.sql(expression, "upper") 5558 5559 if lower and upper: 5560 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5561 return self.func("RAND", expression.this)
5563 def changes_sql(self, expression: exp.Changes) -> str: 5564 information = self.sql(expression, "information") 5565 information = f"INFORMATION => {information}" 5566 at_before = self.sql(expression, "at_before") 5567 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5568 end = self.sql(expression, "end") 5569 end = f"{self.seg('')}{end}" if end else "" 5570 5571 return f"CHANGES ({information}){at_before}{end}"
5573 def pad_sql(self, expression: exp.Pad) -> str: 5574 prefix = "L" if expression.args.get("is_left") else "R" 5575 5576 fill_pattern = self.sql(expression, "fill_pattern") or None 5577 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5578 fill_pattern = "' '" 5579 5580 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5586 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5587 generate_series = exp.GenerateSeries(**expression.args) 5588 5589 parent = expression.parent 5590 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5591 parent = parent.parent 5592 5593 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5594 return self.sql(exp.Unnest(expressions=[generate_series])) 5595 5596 if isinstance(parent, exp.Select): 5597 self.unsupported("GenerateSeries projection unnesting is not supported.") 5598 5599 return self.sql(generate_series)
5601 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5602 if self.SUPPORTS_CONVERT_TIMEZONE: 5603 return self.function_fallback_sql(expression) 5604 5605 source_tz = expression.args.get("source_tz") 5606 target_tz = expression.args.get("target_tz") 5607 timestamp = expression.args.get("timestamp") 5608 5609 if source_tz and timestamp: 5610 timestamp = exp.AtTimeZone( 5611 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5612 ) 5613 5614 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5615 5616 return self.sql(expr)
5618 def json_sql(self, expression: exp.JSON) -> str: 5619 this = self.sql(expression, "this") 5620 this = f" {this}" if this else "" 5621 5622 _with = expression.args.get("with_") 5623 5624 if _with is None: 5625 with_sql = "" 5626 elif not _with: 5627 with_sql = " WITHOUT" 5628 else: 5629 with_sql = " WITH" 5630 5631 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5632 5633 return f"JSON{this}{with_sql}{unique_sql}"
5635 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5636 path = self.sql(expression, "path") 5637 returning = self.sql(expression, "returning") 5638 returning = f" RETURNING {returning}" if returning else "" 5639 5640 on_condition = self.sql(expression, "on_condition") 5641 on_condition = f" {on_condition}" if on_condition else "" 5642 5643 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5649 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5650 else_ = "ELSE " if expression.args.get("else_") else "" 5651 condition = self.sql(expression, "expression") 5652 condition = f"WHEN {condition} THEN " if condition else else_ 5653 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5654 return f"{condition}{insert}"
5662 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5663 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5664 empty = expression.args.get("empty") 5665 empty = ( 5666 f"DEFAULT {empty} ON EMPTY" 5667 if isinstance(empty, exp.Expr) 5668 else self.sql(expression, "empty") 5669 ) 5670 5671 error = expression.args.get("error") 5672 error = ( 5673 f"DEFAULT {error} ON ERROR" 5674 if isinstance(error, exp.Expr) 5675 else self.sql(expression, "error") 5676 ) 5677 5678 if error and empty: 5679 error = ( 5680 f"{empty} {error}" 5681 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5682 else f"{error} {empty}" 5683 ) 5684 empty = "" 5685 5686 null = self.sql(expression, "null") 5687 5688 return f"{empty}{error}{null}"
5694 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5695 this = self.sql(expression, "this") 5696 path = self.sql(expression, "path") 5697 5698 passing = self.expressions(expression, "passing") 5699 passing = f" PASSING {passing}" if passing else "" 5700 5701 on_condition = self.sql(expression, "on_condition") 5702 on_condition = f" {on_condition}" if on_condition else "" 5703 5704 path = f"{path}{passing}{on_condition}" 5705 5706 return self.func("JSON_EXISTS", this, path)
5748 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5749 array_agg = self.function_fallback_sql(expression) 5750 column_expr = expression.this 5751 if isinstance(column_expr, exp.Order): 5752 column_expr = column_expr.this 5753 5754 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5835 def overlay_sql(self, expression: exp.Overlay) -> str: 5836 this = self.sql(expression, "this") 5837 expr = self.sql(expression, "expression") 5838 from_sql = self.sql(expression, "from_") 5839 for_sql = self.sql(expression, "for_") 5840 for_sql = f" FOR {for_sql}" if for_sql else "" 5841 5842 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5849 def string_sql(self, expression: exp.String) -> str: 5850 this = expression.this 5851 zone = expression.args.get("zone") 5852 5853 if zone: 5854 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5855 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5856 # set for source_tz to transpile the time conversion before the STRING cast 5857 this = exp.ConvertTimezone( 5858 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5859 ) 5860 5861 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5871 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5872 filler = self.sql(expression, "this") 5873 filler = f" {filler}" if filler else "" 5874 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5875 return f"TRUNCATE{filler} {with_count}"
5877 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5878 if self.SUPPORTS_UNIX_SECONDS: 5879 return self.function_fallback_sql(expression) 5880 5881 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5882 5883 return self.sql( 5884 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5885 )
5887 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5888 dim = expression.expression 5889 5890 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5891 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5892 if not (dim.is_int and dim.name == "1"): 5893 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5894 dim = None 5895 5896 # If dimension is required but not specified, default initialize it 5897 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5898 dim = exp.Literal.number(1) 5899 5900 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5902 def attach_sql(self, expression: exp.Attach) -> str: 5903 this = self.sql(expression, "this") 5904 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5905 expressions = self.expressions(expression) 5906 expressions = f" ({expressions})" if expressions else "" 5907 5908 return f"ATTACH{exists_sql} {this}{expressions}"
5910 def detach_sql(self, expression: exp.Detach) -> str: 5911 kind = self.sql(expression, "kind") 5912 kind = f" {kind}" if kind else "" 5913 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5914 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5915 exists = " IF EXISTS" if expression.args.get("exists") else "" 5916 if exists: 5917 kind = kind or " DATABASE" 5918 5919 this = self.sql(expression, "this") 5920 this = f" {this}" if this else "" 5921 cluster = self.sql(expression, "cluster") 5922 cluster = f" {cluster}" if cluster else "" 5923 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5924 sync = " SYNC" if expression.args.get("sync") else "" 5925 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5938 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5939 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5940 encode = f"{encode} {self.sql(expression, 'this')}" 5941 5942 properties = expression.args.get("properties") 5943 if properties: 5944 encode = f"{encode} {self.properties(properties)}" 5945 5946 return encode
5948 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5949 this = self.sql(expression, "this") 5950 include = f"INCLUDE {this}" 5951 5952 column_def = self.sql(expression, "column_def") 5953 if column_def: 5954 include = f"{include} {column_def}" 5955 5956 alias = self.sql(expression, "alias") 5957 if alias: 5958 include = f"{include} AS {alias}" 5959 5960 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5973 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5974 partitions = self.expressions(expression, "partition_expressions") 5975 create = self.expressions(expression, "create_expressions") 5976 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5978 def partitionbyrangepropertydynamic_sql( 5979 self, expression: exp.PartitionByRangePropertyDynamic 5980 ) -> str: 5981 start = self.sql(expression, "start") 5982 end = self.sql(expression, "end") 5983 5984 every = expression.args["every"] 5985 if isinstance(every, exp.Interval) and every.this.is_string: 5986 every.this.replace(exp.Literal.number(every.name)) 5987 5988 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6001 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6002 kind = self.sql(expression, "kind") 6003 option = self.sql(expression, "option") 6004 option = f" {option}" if option else "" 6005 this = self.sql(expression, "this") 6006 this = f" {this}" if this else "" 6007 columns = self.expressions(expression) 6008 columns = f" {columns}" if columns else "" 6009 return f"{kind}{option} STATISTICS{this}{columns}"
6011 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6012 this = self.sql(expression, "this") 6013 columns = self.expressions(expression) 6014 inner_expression = self.sql(expression, "expression") 6015 inner_expression = f" {inner_expression}" if inner_expression else "" 6016 update_options = self.sql(expression, "update_options") 6017 update_options = f" {update_options} UPDATE" if update_options else "" 6018 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6029 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6030 kind = self.sql(expression, "kind") 6031 this = self.sql(expression, "this") 6032 this = f" {this}" if this else "" 6033 inner_expression = self.sql(expression, "expression") 6034 return f"VALIDATE {kind}{this}{inner_expression}"
6036 def analyze_sql(self, expression: exp.Analyze) -> str: 6037 options = self.expressions(expression, key="options", sep=" ") 6038 options = f" {options}" if options else "" 6039 kind = self.sql(expression, "kind") 6040 kind = f" {kind}" if kind else "" 6041 this = self.sql(expression, "this") 6042 this = f" {this}" if this else "" 6043 mode = self.sql(expression, "mode") 6044 mode = f" {mode}" if mode else "" 6045 properties = self.sql(expression, "properties") 6046 properties = f" {properties}" if properties else "" 6047 partition = self.sql(expression, "partition") 6048 partition = f" {partition}" if partition else "" 6049 inner_expression = self.sql(expression, "expression") 6050 inner_expression = f" {inner_expression}" if inner_expression else "" 6051 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
6053 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6054 this = self.sql(expression, "this") 6055 namespaces = self.expressions(expression, key="namespaces") 6056 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6057 passing = self.expressions(expression, key="passing") 6058 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6059 columns = self.expressions(expression, key="columns") 6060 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6061 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6062 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6068 def export_sql(self, expression: exp.Export) -> str: 6069 this = self.sql(expression, "this") 6070 connection = self.sql(expression, "connection") 6071 connection = f"WITH CONNECTION {connection} " if connection else "" 6072 options = self.sql(expression, "options") 6073 return f"EXPORT DATA {connection}{options} AS {this}"
6079 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6080 variables = self.expressions(expression, "this") 6081 default = self.sql(expression, "default") 6082 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6083 6084 kind = self.sql(expression, "kind") 6085 if isinstance(expression.args.get("kind"), exp.Schema): 6086 kind = f"TABLE {kind}" 6087 6088 kind = f" {kind}" if kind else "" 6089 6090 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6092 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6093 kind = self.sql(expression, "kind") 6094 this = self.sql(expression, "this") 6095 set = self.sql(expression, "expression") 6096 using = self.sql(expression, "using") 6097 using = f" USING {using}" if using else "" 6098 6099 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6100 6101 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:
6124 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6125 # Snowflake GET/PUT statements: 6126 # PUT <file> <internalStage> <properties> 6127 # GET <internalStage> <file> <properties> 6128 props = expression.args.get("properties") 6129 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6130 this = self.sql(expression, "this") 6131 target = self.sql(expression, "target") 6132 6133 if isinstance(expression, exp.Put): 6134 return f"PUT {this} {target}{props_sql}" 6135 else: 6136 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6138 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6139 this = self.sql(expression, "this") 6140 expr = self.sql(expression, "expression") 6141 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6142 return f"TRANSLATE({this} USING {expr}{with_error})"
6144 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6145 if self.SUPPORTS_DECODE_CASE: 6146 return self.func("DECODE", *expression.expressions) 6147 6148 decode_expr, *expressions = expression.expressions 6149 6150 ifs = [] 6151 for search, result in zip(expressions[::2], expressions[1::2]): 6152 if isinstance(search, exp.Literal): 6153 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6154 elif isinstance(search, exp.Null): 6155 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6156 else: 6157 if isinstance(search, exp.Binary): 6158 search = exp.paren(search) 6159 6160 cond = exp.or_( 6161 decode_expr.eq(search), 6162 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6163 copy=False, 6164 ) 6165 ifs.append(exp.If(this=cond, true=result)) 6166 6167 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6168 return self.sql(case)
6170 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6171 this = self.sql(expression, "this") 6172 this = self.seg(this, sep="") 6173 dimensions = self.expressions( 6174 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6175 ) 6176 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6177 metrics = self.expressions( 6178 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6179 ) 6180 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6181 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6182 facts = self.seg(f"FACTS {facts}") if facts else "" 6183 where = self.sql(expression, "where") 6184 where = self.seg(f"WHERE {where}") if where else "" 6185 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6186 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6188 def getextract_sql(self, expression: exp.GetExtract) -> str: 6189 this = expression.this 6190 expr = expression.expression 6191 6192 if not this.type or not expression.type: 6193 import sqlglot.optimizer.annotate_types 6194 6195 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6196 6197 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6198 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6199 6200 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6217 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6218 method = self.sql(expression, "method") 6219 kind = expression.args.get("kind") 6220 if not kind: 6221 return f"REFRESH {method}" 6222 6223 every = self.sql(expression, "every") 6224 unit = self.sql(expression, "unit") 6225 every = f" EVERY {every} {unit}" if every else "" 6226 starts = self.sql(expression, "starts") 6227 starts = f" STARTS {starts}" if starts else "" 6228 6229 return f"REFRESH {method} ON {kind}{every}{starts}"
6238 def uuid_sql(self, expression: exp.Uuid) -> str: 6239 is_string = expression.args.get("is_string", False) 6240 uuid_func_sql = self.func("UUID") 6241 6242 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6243 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6244 6245 return uuid_func_sql
6247 def initcap_sql(self, expression: exp.Initcap) -> str: 6248 delimiters = expression.expression 6249 6250 if delimiters: 6251 # do not generate delimiters arg if we are round-tripping from default delimiters 6252 if ( 6253 delimiters.is_string 6254 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6255 ): 6256 delimiters = None 6257 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6258 self.unsupported("INITCAP does not support custom delimiters") 6259 delimiters = None 6260 6261 return self.func("INITCAP", expression.this, delimiters)
6271 def weekstart_name(self, expression: exp.WeekStart) -> str: 6272 import sqlglot.dialects.dialect 6273 6274 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6275 this = expression.this.name.upper() 6276 6277 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6278 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6279 6280 if dow_from_week_start_day != dow_from_week_offset: 6281 self.unsupported( 6282 f"WEEK({this}) is not supported; falling back to the default week start day" 6283 ) 6284 6285 return "WEEK"
6287 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6288 name = self.weekstart_name(expression) 6289 6290 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6291 if isinstance(expression.parent, exp.DateTrunc): 6292 return self.sql(exp.Literal.string(name)) 6293 6294 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: