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 28_HIGH_BYTE_RE = re.compile(r"[\x80-\xff]") 29 30ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 31UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 32 33 34def unsupported_args( 35 *args: str | tuple[str, str], 36) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 37 """ 38 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 39 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 40 """ 41 diagnostic_by_arg: dict[str, str | None] = {} 42 for arg in args: 43 if isinstance(arg, str): 44 diagnostic_by_arg[arg] = None 45 else: 46 diagnostic_by_arg[arg[0]] = arg[1] 47 48 def decorator(func: GeneratorMethod) -> GeneratorMethod: 49 @wraps(func) 50 def _func(generator: G, expression: E) -> str: 51 expression_name = expression.__class__.__name__ 52 dialect_name = generator.dialect.__class__.__name__ 53 54 for arg_name, diagnostic in diagnostic_by_arg.items(): 55 if expression.args.get(arg_name): 56 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 57 arg_name, expression_name, dialect_name 58 ) 59 generator.unsupported(diagnostic) 60 61 return func(generator, expression) 62 63 return _func 64 65 return decorator 66 67 68AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 69 "windows": lambda self, e: ( 70 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 71 if e.args.get("windows") 72 else "" 73 ), 74 "qualify": lambda self, e: self.sql(e, "qualify"), 75} 76 77 78_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 79 80 81def _build_dispatch( 82 cls: type[Generator], 83) -> dict[type[exp.Expr], t.Callable[..., str]]: 84 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 85 86 for attr_name in dir(cls): 87 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 88 continue 89 90 expr_key = attr_name[:-4] 91 expr_cls = exp.EXPR_CLASSES.get(expr_key) 92 93 if expr_cls and expr_cls not in dispatch: 94 dispatch[expr_cls] = getattr(cls, attr_name) 95 96 return dispatch 97 98 99class Generator: 100 """ 101 Generator converts a given syntax tree to the corresponding SQL string. 102 103 Args: 104 pretty: Whether to format the produced SQL string. 105 Default: False. 106 identify: Determines when an identifier should be quoted. Possible values are: 107 False (default): Never quote, except in cases where it's mandatory by the dialect. 108 True: Always quote except for specials cases. 109 'safe': Only quote identifiers that are case insensitive. 110 normalize: Whether to normalize identifiers to lowercase. 111 Default: False. 112 pad: The pad size in a formatted string. For example, this affects the indentation of 113 a projection in a query, relative to its nesting level. 114 Default: 2. 115 indent: The indentation size in a formatted string. For example, this affects the 116 indentation of subqueries and filters under a `WHERE` clause. 117 Default: 2. 118 normalize_functions: How to normalize function names. Possible values are: 119 "upper" or True (default): Convert names to uppercase. 120 "lower": Convert names to lowercase. 121 False: Disables function name normalization. 122 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 123 Default ErrorLevel.WARN. 124 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 125 This is only relevant if unsupported_level is ErrorLevel.RAISE. 126 Default: 3 127 leading_comma: Whether the comma is leading or trailing in select expressions. 128 This is only relevant when generating in pretty mode. 129 Default: False 130 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 131 The default is on the smaller end because the length only represents a segment and not the true 132 line length. 133 Default: 80 134 comments: Whether to preserve comments in the output SQL code. 135 Default: True 136 """ 137 138 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 139 **JSON_PATH_PART_TRANSFORMS, 140 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 141 exp.AllowedValuesProperty: lambda self, e: ( 142 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 143 ), 144 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 145 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 146 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 147 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 148 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 149 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 150 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 151 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 152 exp.BinaryColumnConstraint: lambda *_: "BINARY", 153 exp.CaseSpecificColumnConstraint: lambda _, e: ( 154 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 155 ), 156 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 157 exp.Ceil: lambda self, e: self.ceil_floor(e), 158 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 159 exp.CharacterSetProperty: lambda self, e: ( 160 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 161 ), 162 exp.ClusteredColumnConstraint: lambda self, e: ( 163 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 164 ), 165 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 166 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 167 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 168 exp.ConvertToCharset: lambda self, e: self.func( 169 "CONVERT", e.this, e.args["dest"], e.args.get("source") 170 ), 171 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 172 exp.CopyTagsProperty: lambda *_: "COPY TAGS", 173 exp.CredentialsProperty: lambda self, e: ( 174 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 175 ), 176 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 177 exp.SessionUser: lambda *_: "SESSION_USER", 178 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 179 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 180 exp.ApiProperty: lambda *_: "API", 181 exp.ApplicationProperty: lambda *_: "APPLICATION", 182 exp.CatalogProperty: lambda *_: "CATALOG", 183 exp.ComputeProperty: lambda *_: "COMPUTE", 184 exp.DatabaseProperty: lambda *_: "DATABASE", 185 exp.DynamicProperty: lambda *_: "DYNAMIC", 186 exp.EmptyProperty: lambda *_: "EMPTY", 187 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 188 exp.EndStatement: lambda *_: "END", 189 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 190 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 191 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 192 exp.EphemeralColumnConstraint: lambda self, e: ( 193 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 194 ), 195 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 196 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 197 exp.Except: lambda self, e: self.set_operations(e), 198 exp.ExternalProperty: lambda *_: "EXTERNAL", 199 exp.Floor: lambda self, e: self.ceil_floor(e), 200 exp.Get: lambda self, e: self.get_put_sql(e), 201 exp.GlobalProperty: lambda *_: "GLOBAL", 202 exp.HeapProperty: lambda *_: "HEAP", 203 exp.HybridProperty: lambda *_: "HYBRID", 204 exp.IcebergProperty: lambda *_: "ICEBERG", 205 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 206 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 207 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 208 exp.Intersect: lambda self, e: self.set_operations(e), 209 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 210 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 211 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 212 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 213 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 214 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 215 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 216 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 217 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 218 exp.LanguageProperty: lambda self, e: self.naked_property(e), 219 exp.LocationProperty: lambda self, e: self.naked_property(e), 220 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 221 exp.MaskingProperty: lambda *_: "MASKING", 222 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 223 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 224 exp.NetworkProperty: lambda *_: "NETWORK", 225 exp.NonClusteredColumnConstraint: lambda self, e: ( 226 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 227 ), 228 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 229 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 230 exp.OnCommitProperty: lambda _, e: ( 231 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 232 ), 233 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 234 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 235 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 236 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 237 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 238 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 239 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 240 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 241 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 242 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 243 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 244 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 245 f"PROJECTION POLICY {self.sql(e, 'this')}" 246 ), 247 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 248 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 249 exp.Put: lambda self, e: self.get_put_sql(e), 250 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 251 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 252 ), 253 exp.ReturnsProperty: lambda self, e: ( 254 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 255 ), 256 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 257 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 258 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 259 exp.SecureProperty: lambda *_: "SECURE", 260 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 261 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 262 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 263 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 264 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 265 exp.SqlReadWriteProperty: lambda _, e: e.name, 266 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 267 exp.StabilityProperty: lambda _, e: e.name, 268 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 269 exp.StreamingTableProperty: lambda *_: "STREAMING", 270 exp.StrictProperty: lambda *_: "STRICT", 271 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 272 exp.TableColumn: lambda self, e: self.sql(e.this), 273 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 274 exp.TemporaryProperty: lambda *_: "TEMPORARY", 275 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 276 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 277 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 278 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 279 exp.TransientProperty: lambda *_: "TRANSIENT", 280 exp.VirtualProperty: lambda *_: "VIRTUAL", 281 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 282 exp.Union: lambda self, e: self.set_operations(e), 283 exp.UnloggedProperty: lambda *_: "UNLOGGED", 284 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 285 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 286 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 287 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 288 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 289 exp.UtcTimestamp: lambda self, e: self.sql( 290 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 291 ), 292 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 293 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 294 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 295 exp.VolatileProperty: lambda *_: "VOLATILE", 296 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 297 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 298 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 299 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 300 exp.ForceProperty: lambda *_: "FORCE", 301 } 302 303 # Whether null ordering is supported in order by 304 # True: Full Support, None: No support, False: No support for certain cases 305 # such as window specifications, aggregate functions etc 306 NULL_ORDERING_SUPPORTED: bool | None = True 307 308 # Window functions that support NULLS FIRST/LAST 309 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 310 311 # Whether ignore nulls is inside the agg or outside. 312 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 313 IGNORE_NULLS_IN_FUNC = False 314 315 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 316 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 317 IGNORE_NULLS_BEFORE_ORDER = True 318 319 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 320 LOCKING_READS_SUPPORTED = False 321 322 # Whether the EXCEPT and INTERSECT operations can return duplicates 323 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 324 325 # Wrap derived values in parens, usually standard but spark doesn't support it 326 WRAP_DERIVED_VALUES = True 327 328 # Whether create function uses an AS before the RETURN 329 CREATE_FUNCTION_RETURN_AS = True 330 331 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 332 MATCHED_BY_SOURCE = True 333 334 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 335 SUPPORTS_MERGE_WHERE = False 336 337 # Whether MERGE uses ALL BY NAME instead of a star to update or insert all columns 338 MERGE_ALL_BY_NAME = False 339 340 # Whether the INTERVAL expression works only with values like '1 day' 341 SINGLE_STRING_INTERVAL = False 342 343 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 344 INTERVAL_ALLOWS_PLURAL_FORM = True 345 346 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 347 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 348 AUTO_REFRESH_BARE_INTERVALS = False 349 350 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 351 LIMIT_FETCH = "ALL" 352 353 # Whether limit and fetch allows expresions or just limits 354 LIMIT_ONLY_LITERALS = False 355 356 # Whether a table is allowed to be renamed with a db 357 RENAME_TABLE_WITH_DB = True 358 359 # The separator for grouping sets and rollups 360 GROUPINGS_SEP = "," 361 362 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 363 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 364 365 # The string used for creating an index on a table 366 INDEX_ON = "ON" 367 368 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 369 INOUT_SEPARATOR = " " 370 371 # Whether join hints should be generated 372 JOIN_HINTS = True 373 374 # Whether directed joins are supported 375 DIRECTED_JOINS = False 376 377 # Whether top-k distance/similarity joins are supported 378 SUPPORTS_NEAREST_JOIN = False 379 380 # Whether table hints should be generated 381 TABLE_HINTS = True 382 383 # Whether query hints should be generated 384 QUERY_HINTS = True 385 386 # What kind of separator to use for query hints 387 QUERY_HINT_SEP = ", " 388 389 # Whether comparing against booleans (e.g. x IS TRUE) is supported 390 IS_BOOL_ALLOWED = True 391 392 # Whether casting a fractional number to an integer rounds it instead of truncating it 393 CAST_TO_INT_ROUNDS = False 394 395 # Whether the native integer division operator returns NULL for a zero divisor instead of raising 396 SAFE_INT_DIVISION = False 397 398 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 399 DUPLICATE_KEY_UPDATE_WITH_SET = True 400 401 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 402 LIMIT_IS_TOP = False 403 404 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 405 RETURNING_END = True 406 407 # Whether to generate an unquoted value for EXTRACT's date part argument 408 EXTRACT_ALLOWS_QUOTES = True 409 410 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 411 TZ_TO_WITH_TIME_ZONE = False 412 413 # Whether the NVL2 function is supported 414 NVL2_SUPPORTED = True 415 416 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 417 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 418 419 # Whether VALUES statements can be used as derived tables. 420 # MySQL 5 and Redshift do not allow this, so when False, it will convert 421 # SELECT * VALUES into SELECT UNION 422 VALUES_AS_TABLE = True 423 424 # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE. 425 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 426 427 # Whether ALTER TABLE mutli-column additions are wrapped in parentheses. 428 ALTER_TABLE_ADD_COLUMNS_WRAPPED = True 429 430 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 431 UNNEST_WITH_ORDINALITY = True 432 433 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 434 SEMI_ANTI_JOIN_WITH_SIDE = True 435 436 # Whether to include the type of a computed column in the CREATE DDL 437 COMPUTED_COLUMN_WITH_TYPE = True 438 439 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 440 SUPPORTS_TABLE_COPY = True 441 442 # Whether parentheses are required around the table sample's expression 443 TABLESAMPLE_REQUIRES_PARENS = True 444 445 # Whether a table sample clause's size needs to be followed by the ROWS keyword 446 TABLESAMPLE_SIZE_IS_ROWS = True 447 448 # The keyword(s) to use when generating a sample clause 449 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 450 451 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 452 TABLESAMPLE_WITH_METHOD = True 453 454 # The keyword to use when specifying the seed of a sample clause 455 TABLESAMPLE_SEED_KEYWORD = "SEED" 456 457 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 458 HISTORICAL_DATA_POST_ALIAS = False 459 460 # Whether COLLATE is a function instead of a binary operator 461 COLLATE_IS_FUNC = False 462 463 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 464 DATA_TYPE_SPECIFIERS_ALLOWED = False 465 466 # Whether conditions require booleans WHERE x = 0 vs WHERE x 467 ENSURE_BOOLS = False 468 469 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 470 CTE_RECURSIVE_KEYWORD_REQUIRED = True 471 472 # Whether CONCAT requires >1 arguments 473 SUPPORTS_SINGLE_ARG_CONCAT = True 474 475 # Whether LAST_DAY function supports a date part argument 476 LAST_DAY_SUPPORTS_DATE_PART = True 477 478 # Whether named columns are allowed in table aliases 479 SUPPORTS_TABLE_ALIAS_COLUMNS = True 480 481 # Whether named columns are allowed in CTE definitions 482 SUPPORTS_NAMED_CTE_COLUMNS = True 483 484 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 485 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 486 487 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 488 PIVOT_ALIAS_WITH_AS = True 489 490 # What delimiter to use for separating JSON key/value pairs 491 JSON_KEY_VALUE_PAIR_SEP = ":" 492 493 # INSERT OVERWRITE TABLE x override 494 INSERT_OVERWRITE = " OVERWRITE TABLE" 495 496 # Whether the SELECT .. INTO syntax is used instead of CTAS 497 SUPPORTS_SELECT_INTO = False 498 499 # Whether UNLOGGED tables can be created 500 SUPPORTS_UNLOGGED_TABLES = False 501 502 # Whether the CREATE TABLE LIKE statement is supported 503 SUPPORTS_CREATE_TABLE_LIKE = True 504 505 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 506 SUPPORTS_MODIFY_COLUMN = False 507 508 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 509 SUPPORTS_CHANGE_COLUMN = False 510 511 # Whether ALTER COLUMN can set a column's nullability together with its type 512 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 513 514 # Whether ALTER COLUMN IF EXISTS is supported 515 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 516 517 # Whether the LikeProperty needs to be specified inside of the schema clause 518 LIKE_PROPERTY_INSIDE_SCHEMA = False 519 520 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 521 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 522 MULTI_ARG_DISTINCT = True 523 524 # Whether the JSON extraction operators expect a value of type JSON 525 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 526 527 # Whether bracketed keys like ["foo"] are supported in JSON paths 528 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 529 530 # Whether to escape keys using single quotes in JSON paths 531 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 532 533 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 534 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 535 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 536 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 537 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 538 539 # The JSONPathPart expressions supported by this dialect 540 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 541 542 # Whether any(f(x) for x in array) can be implemented by this dialect 543 CAN_IMPLEMENT_ARRAY_ANY = False 544 545 # Whether the function TO_NUMBER is supported 546 SUPPORTS_TO_NUMBER = True 547 548 # Whether EXCLUDE in window specification is supported 549 SUPPORTS_WINDOW_EXCLUDE = False 550 551 # Whether or not set op modifiers apply to the outer set op or select. 552 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 553 # True means limit 1 happens after the set op, False means it it happens on y. 554 SET_OP_MODIFIERS = True 555 556 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 557 SET_OP_LIMITS = False 558 559 # Whether set operation operands can be parenthesized without a SELECT wrapper. 560 SET_OP_PARENTHESIZED_OPERANDS = True 561 562 # Whether parameters from COPY statement are wrapped in parentheses 563 COPY_PARAMS_ARE_WRAPPED = True 564 565 # Whether values of params are set with "=" token or empty space 566 COPY_PARAMS_EQ_REQUIRED = False 567 568 # Whether COPY statement has INTO keyword 569 COPY_HAS_INTO_KEYWORD = True 570 571 # Whether the conditional TRY(expression) function is supported 572 TRY_SUPPORTED = True 573 574 # Whether the UESCAPE syntax in unicode strings is supported 575 SUPPORTS_UESCAPE = True 576 577 # Function used to replace escaped unicode codes in unicode strings 578 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 579 580 # The keyword to use when generating a star projection with excluded columns 581 STAR_EXCEPT = "EXCEPT" 582 583 # The HEX function name 584 HEX_FUNC = "HEX" 585 586 # Function that constructs a binary value from a hex-encoded byte string. 587 BYTE_STRING_FUNCTION = "" 588 589 # The keywords to use when prefixing & separating WITH based properties 590 WITH_PROPERTIES_PREFIX = "WITH" 591 592 # Whether to quote the generated expression of exp.JsonPath 593 QUOTE_JSON_PATH = True 594 595 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 596 PAD_FILL_PATTERN_IS_REQUIRED = False 597 598 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 599 SUPPORTS_EXPLODING_PROJECTIONS = True 600 601 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 602 ARRAY_CONCAT_IS_VAR_LEN = True 603 604 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 605 SUPPORTS_CONVERT_TIMEZONE = False 606 607 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 608 SUPPORTS_MEDIAN = True 609 610 # Whether UNIX_SECONDS(timestamp) is supported 611 SUPPORTS_UNIX_SECONDS = False 612 613 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 614 ALTER_SET_WRAPPED = False 615 616 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 617 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 618 # TODO: The normalization should be done by default once we've tested it across all dialects. 619 NORMALIZE_EXTRACT_DATE_PARTS = False 620 621 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 622 PARSE_JSON_NAME: str | None = "PARSE_JSON" 623 624 # The function name of the exp.ArraySize expression 625 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 626 627 # The syntax to use when altering the type of a column 628 ALTER_SET_TYPE = "SET DATA TYPE" 629 630 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 631 # None -> Doesn't support it at all 632 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 633 # True (Postgres) -> Explicitly requires it 634 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 635 636 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 637 SUPPORTS_DECODE_CASE = True 638 639 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 640 SUPPORTS_BETWEEN_FLAGS = False 641 642 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 643 SUPPORTS_LIKE_QUANTIFIERS = True 644 645 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 646 MATCH_AGAINST_TABLE_PREFIX: str | None = None 647 648 # Whether to include the VARIABLE keyword for SET assignments 649 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 650 651 # The keyword to use for default value assignment in DECLARE statements 652 DECLARE_DEFAULT_ASSIGNMENT = "=" 653 654 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 655 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 656 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 657 UPDATE_STATEMENT_SUPPORTS_FROM = True 658 659 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 660 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 661 662 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 663 # - Snowflake: DROP ICEBERG TABLE a.b; 664 # - DuckDB: DROP TABLE a.b; 665 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 666 667 TYPE_MAPPING: t.ClassVar = { 668 exp.DType.DATETIME2: "TIMESTAMP", 669 exp.DType.NCHAR: "CHAR", 670 exp.DType.NVARCHAR: "VARCHAR", 671 exp.DType.MEDIUMTEXT: "TEXT", 672 exp.DType.LONGTEXT: "TEXT", 673 exp.DType.TINYTEXT: "TEXT", 674 exp.DType.BLOB: "VARBINARY", 675 exp.DType.MEDIUMBLOB: "BLOB", 676 exp.DType.LONGBLOB: "BLOB", 677 exp.DType.TINYBLOB: "BLOB", 678 exp.DType.INET: "INET", 679 exp.DType.ROWVERSION: "VARBINARY", 680 exp.DType.SMALLDATETIME: "TIMESTAMP", 681 } 682 683 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 684 685 # mapping of DType to its default parameters, bounds 686 TYPE_PARAM_SETTINGS: t.ClassVar[ 687 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 688 ] = {} 689 690 TIME_PART_SINGULARS: t.ClassVar = { 691 "MICROSECONDS": "MICROSECOND", 692 "SECONDS": "SECOND", 693 "MINUTES": "MINUTE", 694 "HOURS": "HOUR", 695 "DAYS": "DAY", 696 "WEEKS": "WEEK", 697 "MONTHS": "MONTH", 698 "QUARTERS": "QUARTER", 699 "YEARS": "YEAR", 700 } 701 702 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 703 "cluster": lambda self, e: self.sql(e, "cluster"), 704 "distribute": lambda self, e: self.sql(e, "distribute"), 705 "sort": lambda self, e: self.sql(e, "sort"), 706 **AFTER_HAVING_MODIFIER_TRANSFORMS, 707 } 708 709 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 710 711 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 712 713 PARAMETER_TOKEN = "@" 714 NAMED_PLACEHOLDER_TOKEN = ":" 715 716 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 717 718 PROPERTIES_LOCATION: t.ClassVar = { 719 exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 722 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 723 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 724 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 728 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 730 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 732 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 734 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 737 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 742 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 743 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 745 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 746 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 748 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 752 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 758 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 759 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 760 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 761 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 762 exp.HeapProperty: exp.Properties.Location.POST_WITH, 763 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 764 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 766 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 769 exp.JournalProperty: exp.Properties.Location.POST_NAME, 770 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 775 exp.LogProperty: exp.Properties.Location.POST_NAME, 776 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 777 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 778 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 779 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 781 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 782 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 783 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 784 exp.Order: exp.Properties.Location.POST_SCHEMA, 785 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 787 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED, 789 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 790 exp.Property: exp.Properties.Location.POST_WITH, 791 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 792 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 795 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 796 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 797 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 798 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 801 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 802 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 803 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 804 exp.Set: exp.Properties.Location.POST_SCHEMA, 805 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 806 exp.SetProperty: exp.Properties.Location.POST_CREATE, 807 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 808 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 809 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 810 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 811 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 812 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 813 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 814 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 815 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 816 exp.StorageProperty: exp.Properties.Location.UNSUPPORTED, 817 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 818 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 819 exp.Tags: exp.Properties.Location.POST_WITH, 820 exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED, 821 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 822 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 823 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 824 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 825 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 826 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 827 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 828 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 829 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 830 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 831 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 832 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 833 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 834 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 835 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 836 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 837 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 838 } 839 840 # Keywords that can't be used as unquoted identifier names 841 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 842 843 # Exprs whose comments are separated from them for better formatting 844 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 845 exp.Command, 846 exp.Create, 847 exp.Describe, 848 exp.Delete, 849 exp.Drop, 850 exp.From, 851 exp.Insert, 852 exp.Join, 853 exp.MultitableInserts, 854 exp.Order, 855 exp.Group, 856 exp.Having, 857 exp.Select, 858 exp.SetOperation, 859 exp.Update, 860 exp.Where, 861 exp.With, 862 ) 863 864 # Exprs that should not have their comments generated in maybe_comment 865 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 866 exp.Binary, 867 exp.SetOperation, 868 ) 869 870 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 871 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 872 exp.Column, 873 exp.Literal, 874 exp.Neg, 875 exp.Paren, 876 ) 877 878 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 879 exp.DType.NVARCHAR, 880 exp.DType.VARCHAR, 881 exp.DType.CHAR, 882 exp.DType.NCHAR, 883 } 884 885 # Exprs that need to have all CTEs under them bubbled up to them 886 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 887 888 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 889 890 MOD_OPERATOR = "%" 891 892 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 893 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 894 exp.Mul, 895 exp.Div, 896 exp.IntDiv, 897 exp.Mod, 898 ) 899 900 # ALTER options that follow the others without a comma, e.g. MySQL's 901 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 902 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 903 904 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 905 906 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 907 908 __slots__ = ( 909 "pretty", 910 "identify", 911 "normalize", 912 "pad", 913 "_indent", 914 "normalize_functions", 915 "unsupported_level", 916 "max_unsupported", 917 "leading_comma", 918 "max_text_width", 919 "comments", 920 "dialect", 921 "unsupported_messages", 922 "_escaped_quote_end", 923 "_escaped_byte_quote_end", 924 "_escaped_identifier_end", 925 "_identifier_escaped_sequences", 926 "_next_name", 927 "_identifier_start", 928 "_identifier_end", 929 "_quote_json_path_key_using_brackets", 930 "_dispatch", 931 ) 932 933 def __init__( 934 self, 935 pretty: bool | int | None = None, 936 identify: str | bool = False, 937 normalize: bool = False, 938 pad: int = 2, 939 indent: int = 2, 940 normalize_functions: str | bool | None = None, 941 unsupported_level: ErrorLevel = ErrorLevel.WARN, 942 max_unsupported: int = 3, 943 leading_comma: bool = False, 944 max_text_width: int = 80, 945 comments: bool = True, 946 dialect: DialectType = None, 947 ): 948 import sqlglot 949 import sqlglot.dialects.dialect 950 951 self.pretty = pretty if pretty is not None else sqlglot.pretty 952 self.identify = identify 953 self.normalize = normalize 954 self.pad = pad 955 self._indent = indent 956 self.unsupported_level = unsupported_level 957 self.max_unsupported = max_unsupported 958 self.leading_comma = leading_comma 959 self.max_text_width = max_text_width 960 self.comments = comments 961 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 962 963 # This is both a Dialect property and a Generator argument, so we prioritize the latter 964 self.normalize_functions = ( 965 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 966 ) 967 968 self.unsupported_messages: list[str] = [] 969 self._escaped_quote_end: str = ( 970 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 971 ) 972 self._escaped_byte_quote_end: str = ( 973 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 974 if self.dialect.BYTE_END 975 else "" 976 ) 977 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 978 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 979 980 self._next_name = name_sequence("_t") 981 982 self._identifier_start = self.dialect.IDENTIFIER_START 983 self._identifier_end = self.dialect.IDENTIFIER_END 984 985 self._quote_json_path_key_using_brackets = True 986 987 cls = type(self) 988 dispatch = _DISPATCH_CACHE.get(cls) 989 if dispatch is None: 990 dispatch = _build_dispatch(cls) 991 _DISPATCH_CACHE[cls] = dispatch 992 self._dispatch = dispatch 993 994 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 995 """ 996 Generates the SQL string corresponding to the given syntax tree. 997 998 Args: 999 expression: The syntax tree. 1000 copy: Whether to copy the expression. The generator performs mutations so 1001 it is safer to copy. 1002 1003 Returns: 1004 The SQL string corresponding to `expression`. 1005 """ 1006 if copy: 1007 expression = expression.copy() 1008 1009 expression = self.preprocess(expression) 1010 1011 self.unsupported_messages = [] 1012 sql = self.sql(expression).strip() 1013 1014 if self.pretty: 1015 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1016 1017 if self.unsupported_level == ErrorLevel.IGNORE: 1018 return sql 1019 1020 if self.unsupported_level == ErrorLevel.WARN: 1021 for msg in self.unsupported_messages: 1022 logger.warning(msg) 1023 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1024 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1025 1026 return sql 1027 1028 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1029 """Apply generic preprocessing transformations to a given expression.""" 1030 expression = self._move_ctes_to_top_level(expression) 1031 1032 if self.ENSURE_BOOLS: 1033 import sqlglot.transforms 1034 1035 expression = sqlglot.transforms.ensure_bools(expression) 1036 1037 return expression 1038 1039 def _move_ctes_to_top_level(self, expression: E) -> E: 1040 if ( 1041 not expression.parent 1042 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1043 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1044 ): 1045 import sqlglot.transforms 1046 1047 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1048 return expression 1049 1050 def unsupported(self, message: str) -> None: 1051 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1052 raise UnsupportedError(message) 1053 self.unsupported_messages.append(message) 1054 1055 def sep(self, sep: str = " ") -> str: 1056 return f"{sep.strip()}\n" if self.pretty else sep 1057 1058 def seg(self, sql: str, sep: str = " ") -> str: 1059 return f"{self.sep(sep)}{sql}" 1060 1061 def sanitize_comment(self, comment: str) -> str: 1062 comment = " " + comment if comment[0].strip() else comment 1063 comment = comment + " " if comment[-1].strip() else comment 1064 1065 # Escape block comment markers to prevent premature closure or unintended nesting. 1066 # This is necessary because single-line comments (--) are converted to block comments 1067 # (/* */) on output, and any */ in the original text would close the comment early. 1068 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1069 1070 return comment 1071 1072 def maybe_comment( 1073 self, 1074 sql: str, 1075 expression: exp.Expr | None = None, 1076 comments: list[str] | None = None, 1077 separated: bool = False, 1078 ) -> str: 1079 comments = ( 1080 ((expression and expression.comments) if comments is None else comments) # type: ignore 1081 if self.comments 1082 else None 1083 ) 1084 1085 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1086 return sql 1087 1088 comments_list = [ 1089 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1090 for comment in comments 1091 if comment 1092 ] 1093 1094 if not comments_list: 1095 return sql 1096 1097 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1098 comments_sql = self.sep().join(comments_list) 1099 return ( 1100 f"{self.sep()}{comments_sql}{sql}" 1101 if not sql or sql[0].isspace() 1102 else f"{comments_sql}{self.sep()}{sql}" 1103 ) 1104 1105 return f"{sql} {' '.join(comments_list)}" 1106 1107 def wrap(self, expression: exp.Expr | str) -> str: 1108 this_sql = ( 1109 self.sql(expression) 1110 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1111 else self.sql(expression, "this") 1112 ) 1113 if not this_sql: 1114 return "()" 1115 1116 this_sql = self.indent(this_sql, level=1, pad=0) 1117 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1118 1119 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1120 original = self.identify 1121 self.identify = False 1122 result = func(*args, **kwargs) 1123 self.identify = original 1124 return result 1125 1126 def normalize_func(self, name: str) -> str: 1127 if self.normalize_functions == "upper" or self.normalize_functions is True: 1128 return name.upper() 1129 if self.normalize_functions == "lower": 1130 return name.lower() 1131 return name 1132 1133 def indent( 1134 self, 1135 sql: str, 1136 level: int = 0, 1137 pad: int | None = None, 1138 skip_first: bool = False, 1139 skip_last: bool = False, 1140 ) -> str: 1141 if not self.pretty or not sql: 1142 return sql 1143 1144 pad = self.pad if pad is None else pad 1145 lines = sql.split("\n") 1146 1147 return "\n".join( 1148 ( 1149 line 1150 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1151 else f"{' ' * (level * self._indent + pad)}{line}" 1152 ) 1153 for i, line in enumerate(lines) 1154 ) 1155 1156 def sql( 1157 self, 1158 expression: str | exp.Expr | None, 1159 key: str | None = None, 1160 comment: bool = True, 1161 ) -> str: 1162 if not expression: 1163 return "" 1164 1165 if isinstance(expression, str): 1166 return expression 1167 1168 if key: 1169 value = expression.args.get(key) 1170 if value: 1171 return self.sql(value) 1172 return "" 1173 1174 handler = self._dispatch.get(expression.__class__) 1175 1176 if handler: 1177 sql = handler(self, expression) 1178 elif isinstance(expression, exp.Func): 1179 sql = self.function_fallback_sql(expression) 1180 elif isinstance(expression, exp.Property): 1181 sql = self.property_sql(expression) 1182 else: 1183 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1184 1185 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1186 1187 def uncache_sql(self, expression: exp.Uncache) -> str: 1188 table = self.sql(expression, "this") 1189 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1190 return f"UNCACHE TABLE{exists_sql} {table}" 1191 1192 def cache_sql(self, expression: exp.Cache) -> str: 1193 lazy = " LAZY" if expression.args.get("lazy") else "" 1194 table = self.sql(expression, "this") 1195 options = expression.args.get("options") 1196 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1197 sql = self.sql(expression, "expression") 1198 sql = f" AS{self.sep()}{sql}" if sql else "" 1199 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1200 return self.prepend_ctes(expression, sql) 1201 1202 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1203 default = "DEFAULT " if expression.args.get("default") else "" 1204 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1205 1206 def column_parts(self, expression: exp.Column) -> str: 1207 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1208 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1209 return self.sql(expression, "this") 1210 1211 return ".".join( 1212 self.sql(part) 1213 for part in ( 1214 expression.args.get("catalog"), 1215 expression.args.get("db"), 1216 expression.args.get("table"), 1217 expression.args.get("this"), 1218 ) 1219 if part 1220 ) 1221 1222 def column_sql(self, expression: exp.Column) -> str: 1223 join_mark = " (+)" if expression.args.get("join_mark") else "" 1224 1225 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1226 join_mark = "" 1227 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1228 1229 return f"{self.column_parts(expression)}{join_mark}" 1230 1231 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1232 return self.column_sql(expression) 1233 1234 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1235 this = self.sql(expression, "this") 1236 this = f" {this}" if this else "" 1237 position = self.sql(expression, "position") 1238 return f"{position}{this}" 1239 1240 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1241 column = self.sql(expression, "this") 1242 kind = self.sql(expression, "kind") 1243 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1244 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1245 kind = f"{sep}{kind}" if kind else "" 1246 constraints = f" {constraints}" if constraints else "" 1247 position = self.sql(expression, "position") 1248 position = f" {position}" if position else "" 1249 1250 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1251 kind = "" 1252 1253 return f"{exists}{column}{kind}{constraints}{position}" 1254 1255 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1256 this = self.sql(expression, "this") 1257 kind_sql = self.sql(expression, "kind").strip() 1258 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1259 1260 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1261 this = self.sql(expression, "this") 1262 if expression.args.get("not_null"): 1263 persisted = " PERSISTED NOT NULL" 1264 elif expression.args.get("persisted"): 1265 persisted = " PERSISTED" 1266 else: 1267 persisted = "" 1268 1269 return f"AS {this}{persisted}" 1270 1271 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1272 return self.token_sql(TokenType.AUTO_INCREMENT) 1273 1274 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1275 if isinstance(expression.this, list): 1276 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1277 else: 1278 this = self.sql(expression, "this") 1279 1280 return f"COMPRESS {this}" 1281 1282 def generatedasidentitycolumnconstraint_sql( 1283 self, expression: exp.GeneratedAsIdentityColumnConstraint 1284 ) -> str: 1285 this = "" 1286 if expression.this is not None: 1287 on_null = " ON NULL" if expression.args.get("on_null") else "" 1288 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1289 1290 start = expression.args.get("start") 1291 start = f"START WITH {start}" if start else "" 1292 increment = expression.args.get("increment") 1293 increment = f" INCREMENT BY {increment}" if increment else "" 1294 minvalue = expression.args.get("minvalue") 1295 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1296 maxvalue = expression.args.get("maxvalue") 1297 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1298 cycle = expression.args.get("cycle") 1299 cycle_sql = "" 1300 1301 if cycle is not None: 1302 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1303 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1304 1305 sequence_opts = "" 1306 if start or increment or cycle_sql: 1307 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1308 sequence_opts = f" ({sequence_opts.strip()})" 1309 1310 expr = self.sql(expression, "expression") 1311 expr = f"({expr})" if expr else "IDENTITY" 1312 1313 return f"GENERATED{this} AS {expr}{sequence_opts}" 1314 1315 def generatedasrowcolumnconstraint_sql( 1316 self, expression: exp.GeneratedAsRowColumnConstraint 1317 ) -> str: 1318 start = "START" if expression.args.get("start") else "END" 1319 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1320 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1321 1322 def periodforsystemtimeconstraint_sql( 1323 self, expression: exp.PeriodForSystemTimeConstraint 1324 ) -> str: 1325 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1326 1327 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1328 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1329 1330 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1331 desc = expression.args.get("desc") 1332 if desc is not None: 1333 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1334 options = self.expressions(expression, key="options", flat=True, sep=" ") 1335 options = f" {options}" if options else "" 1336 include = self.sql(expression, "include") 1337 return f"PRIMARY KEY{include}{options}" 1338 1339 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1340 this = self.sql(expression, "this") 1341 this = f" {this}" if this else "" 1342 index_type = expression.args.get("index_type") 1343 index_type = f" USING {index_type}" if index_type else "" 1344 on_conflict = self.sql(expression, "on_conflict") 1345 on_conflict = f" {on_conflict}" if on_conflict else "" 1346 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1347 options = self.expressions(expression, key="options", flat=True, sep=" ") 1348 options = f" {options}" if options else "" 1349 include = self.sql(expression, "include") 1350 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}" 1351 1352 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1353 input_ = expression.args.get("input_") 1354 output = expression.args.get("output") 1355 variadic = expression.args.get("variadic") 1356 1357 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1358 if variadic: 1359 return "VARIADIC" 1360 1361 if input_ and output: 1362 return f"IN{self.INOUT_SEPARATOR}OUT" 1363 if input_: 1364 return "IN" 1365 if output: 1366 return "OUT" 1367 1368 return "" 1369 1370 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1371 return self.sql(expression, "this") 1372 1373 def create_sql(self, expression: exp.Create) -> str: 1374 kind = self.sql(expression, "kind") 1375 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1376 1377 properties = expression.args.get("properties") 1378 1379 if ( 1380 kind == "TRIGGER" 1381 and properties 1382 and properties.expressions 1383 and isinstance(properties.expressions[0], exp.TriggerProperties) 1384 and properties.expressions[0].args.get("constraint") 1385 ): 1386 kind = f"CONSTRAINT {kind}" 1387 1388 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1389 1390 this = self.createable_sql(expression, properties_locs) 1391 1392 properties_sql = "" 1393 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1394 exp.Properties.Location.POST_WITH 1395 ): 1396 props_ast = exp.Properties( 1397 expressions=[ 1398 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1399 *properties_locs[exp.Properties.Location.POST_WITH], 1400 ] 1401 ) 1402 props_ast.parent = expression 1403 properties_sql = self.sql(props_ast) 1404 1405 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1406 properties_sql = self.sep() + properties_sql 1407 elif not self.pretty: 1408 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1409 properties_sql = f" {properties_sql}" 1410 1411 begin = " BEGIN" if expression.args.get("begin") else "" 1412 1413 expression_sql = self.sql(expression, "expression") 1414 if expression_sql: 1415 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1416 1417 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1418 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1419 ): 1420 postalias_props_sql = "" 1421 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1422 postalias_props_sql = self.properties( 1423 exp.Properties( 1424 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1425 ), 1426 wrapped=False, 1427 ) 1428 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1429 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1430 1431 postindex_props_sql = "" 1432 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1433 postindex_props_sql = self.properties( 1434 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1435 wrapped=False, 1436 prefix=" ", 1437 ) 1438 1439 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1440 indexes = f" {indexes}" if indexes else "" 1441 index_sql = indexes + postindex_props_sql 1442 1443 replace = " OR REPLACE" if expression.args.get("replace") else "" 1444 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1445 unique = " UNIQUE" if expression.args.get("unique") else "" 1446 1447 clustered = expression.args.get("clustered") 1448 if clustered is None: 1449 clustered_sql = "" 1450 elif clustered: 1451 clustered_sql = " CLUSTERED COLUMNSTORE" 1452 else: 1453 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1454 1455 postcreate_props_sql = "" 1456 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1457 postcreate_props_sql = self.properties( 1458 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1459 sep=" ", 1460 prefix=" ", 1461 wrapped=False, 1462 ) 1463 1464 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1465 1466 postexpression_props_sql = "" 1467 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1468 postexpression_props_sql = self.properties( 1469 exp.Properties( 1470 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1471 ), 1472 sep=" ", 1473 prefix=" ", 1474 wrapped=False, 1475 ) 1476 1477 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1478 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1479 no_schema_binding = ( 1480 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1481 ) 1482 1483 clone = self.sql(expression, "clone") 1484 clone = f" {clone}" if clone else "" 1485 1486 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1487 properties_expression = f"{expression_sql}{properties_sql}" 1488 else: 1489 properties_expression = f"{properties_sql}{expression_sql}" 1490 1491 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1492 return self.prepend_ctes(expression, expression_sql) 1493 1494 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1495 start = self.sql(expression, "start") 1496 start = f"START WITH {start}" if start else "" 1497 increment = self.sql(expression, "increment") 1498 increment = f" INCREMENT BY {increment}" if increment else "" 1499 minvalue = self.sql(expression, "minvalue") 1500 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1501 maxvalue = self.sql(expression, "maxvalue") 1502 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1503 owned = self.sql(expression, "owned") 1504 owned = f" OWNED BY {owned}" if owned else "" 1505 1506 cache = expression.args.get("cache") 1507 if cache is None: 1508 cache_str = "" 1509 elif cache is True: 1510 cache_str = " CACHE" 1511 else: 1512 cache_str = f" CACHE {cache}" 1513 1514 options = self.expressions(expression, key="options", flat=True, sep=" ") 1515 options = f" {options}" if options else "" 1516 1517 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1518 1519 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1520 timing = expression.args.get("timing", "") 1521 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1522 timing_events = f"{timing} {events}".strip() if timing or events else "" 1523 1524 parts = [timing_events, "ON", self.sql(expression, "table")] 1525 1526 if referenced_table := expression.args.get("referenced_table"): 1527 parts.extend(["FROM", self.sql(referenced_table)]) 1528 1529 if deferrable := expression.args.get("deferrable"): 1530 parts.append(deferrable) 1531 1532 if initially := expression.args.get("initially"): 1533 parts.append(f"INITIALLY {initially}") 1534 1535 if referencing := expression.args.get("referencing"): 1536 parts.append(self.sql(referencing)) 1537 1538 if for_each := expression.args.get("for_each"): 1539 parts.append(f"FOR EACH {for_each}") 1540 1541 if when := expression.args.get("when"): 1542 parts.append(f"WHEN ({self.sql(when)})") 1543 1544 parts.append(self.sql(expression, "execute")) 1545 1546 return self.sep().join(parts) 1547 1548 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1549 parts = [] 1550 1551 if old_alias := expression.args.get("old"): 1552 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1553 1554 if new_alias := expression.args.get("new"): 1555 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1556 1557 return f"REFERENCING {' '.join(parts)}" 1558 1559 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1560 columns = expression.args.get("columns") 1561 if columns: 1562 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1563 1564 return self.sql(expression, "this") 1565 1566 def clone_sql(self, expression: exp.Clone) -> str: 1567 this = self.sql(expression, "this") 1568 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1569 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1570 return f"{shallow}{keyword} {this}" 1571 1572 def describe_sql(self, expression: exp.Describe) -> str: 1573 style = expression.args.get("style") 1574 style = f" {style}" if style else "" 1575 partition = self.sql(expression, "partition") 1576 partition = f" {partition}" if partition else "" 1577 format = self.sql(expression, "format") 1578 format = f" {format}" if format else "" 1579 as_json = " AS JSON" if expression.args.get("as_json") else "" 1580 1581 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1582 1583 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1584 tag = self.sql(expression, "tag") 1585 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1586 1587 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1588 with_ = self.sql(expression, "with_") 1589 if with_: 1590 sql = f"{with_}{self.sep()}{sql}" 1591 return sql 1592 1593 def with_sql(self, expression: exp.With) -> str: 1594 udfs = self.expressions(expression, key="udfs", flat=True) 1595 udfs = f"WITH {udfs}" if udfs else "" 1596 1597 sql = self.expressions(expression, flat=True) 1598 1599 recursive = ( 1600 "RECURSIVE " 1601 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1602 else "" 1603 ) 1604 search = self.sql(expression, "search") 1605 search = f" {search}" if search else "" 1606 cycle = self.sql(expression, "cycle") 1607 cycle = f" {cycle}" if cycle else "" 1608 1609 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1610 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1611 1612 def cte_sql(self, expression: exp.CTE) -> str: 1613 alias = expression.args.get("alias") 1614 if alias: 1615 alias.add_comments(expression.pop_comments()) 1616 1617 alias_sql = self.sql(expression, "alias") 1618 1619 materialized = expression.args.get("materialized") 1620 if materialized is False: 1621 materialized = "NOT MATERIALIZED " 1622 elif materialized: 1623 materialized = "MATERIALIZED " 1624 1625 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1626 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1627 1628 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1629 1630 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1631 alias = self.sql(expression, "this") 1632 columns = self.expressions(expression, key="columns", flat=True) 1633 columns = f"({columns})" if columns else "" 1634 1635 if ( 1636 columns 1637 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1638 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1639 ): 1640 columns = "" 1641 self.unsupported("Named columns are not supported in table alias.") 1642 1643 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1644 alias = self._next_name() 1645 1646 return f"{alias}{columns}" 1647 1648 def bitstring_sql(self, expression: exp.BitString) -> str: 1649 this = self.sql(expression, "this") 1650 if self.dialect.BIT_START: 1651 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1652 return f"{int(this, 2)}" 1653 1654 def hexstring_sql( 1655 self, expression: exp.HexString, binary_function_repr: str | None = None 1656 ) -> str: 1657 this = self.sql(expression, "this") 1658 is_integer_type = expression.args.get("is_integer") 1659 1660 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1661 not self.dialect.HEX_START and not binary_function_repr 1662 ): 1663 # Integer representation will be returned if: 1664 # - The read dialect treats the hex value as integer literal but not the write 1665 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1666 return f"{int(this, 16)}" 1667 1668 if not is_integer_type: 1669 # Read dialect treats the hex value as BINARY/BLOB 1670 if binary_function_repr: 1671 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1672 return self.func(binary_function_repr, exp.Literal.string(this)) 1673 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1674 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1675 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1676 1677 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1678 1679 def bytestring_sql(self, expression: exp.ByteString) -> str: 1680 this = self.sql(expression, "this") 1681 is_bytes = bool(expression.args.get("is_bytes")) 1682 if is_bytes and self.BYTE_STRING_FUNCTION: 1683 # Hex digits can be quoted directly without escaping. 1684 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1685 1686 if self.dialect.BYTE_START: 1687 escaped_byte_string = self.escape_str( 1688 this, 1689 escape_backslash=bool( 1690 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1691 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1692 ), 1693 delimiter=self.dialect.BYTE_END, 1694 escaped_delimiter=self._escaped_byte_quote_end, 1695 is_byte_string=True, 1696 is_bytes=is_bytes, 1697 ) 1698 delimited_byte_string = ( 1699 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1700 ) 1701 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1702 return self.sql( 1703 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1704 ) 1705 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1706 return self.sql( 1707 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1708 ) 1709 1710 return delimited_byte_string 1711 1712 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1713 return self.sql(exp.Literal.string(this)) 1714 1715 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1716 return "" 1717 1718 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1719 this = self.sql(expression, "this") 1720 escape = expression.args.get("escape") 1721 unicode_start = self.dialect.UNICODE_START 1722 1723 if unicode_start: 1724 escape_substitute = r"\\\1" 1725 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1726 else: 1727 escape_substitute = r"\\u\1" 1728 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1729 1730 if escape: 1731 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1732 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1733 else: 1734 escape_pattern = ESCAPED_UNICODE_RE 1735 escape_sql = "" 1736 1737 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1738 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1739 1740 if unicode_start: 1741 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1742 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1743 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1744 else: 1745 this = self.escape_str(this, escape_backslash=False) 1746 1747 return f"{left_quote}{this}{right_quote}{escape_sql}" 1748 1749 def rawstring_sql(self, expression: exp.RawString) -> str: 1750 string = expression.this 1751 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1752 string = string.replace("\\", "\\\\") 1753 1754 string = self.escape_str(string, escape_backslash=False) 1755 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1756 1757 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1758 this = self.sql(expression, "this") 1759 specifier = self.sql(expression, "expression") 1760 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1761 return f"{this}{specifier}" 1762 1763 def datatype_param_bound_limiter( 1764 self, 1765 expression: exp.DataType, 1766 type_value: exp.DType, 1767 defaults: tuple[int, ...], 1768 bounds: tuple[int | None, ...], 1769 ) -> exp.DataType: 1770 params = expression.expressions 1771 1772 if not params: 1773 if defaults: 1774 expression.set( 1775 "expressions", 1776 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1777 ) 1778 return expression 1779 1780 if not bounds: 1781 return expression 1782 1783 for i, param in enumerate(params): 1784 bound = bounds[i] if i < len(bounds) else None 1785 if bound is None: 1786 continue 1787 1788 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1789 value = ( 1790 param_value.to_py() 1791 if isinstance(param_value, exp.Literal) and param_value.is_number 1792 else None 1793 ) 1794 if isinstance(value, (int, Decimal)) and value > bound: 1795 self.unsupported( 1796 f"{type_value.value} parameter {param_value.name} exceeds " 1797 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1798 ) 1799 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1800 1801 return expression 1802 1803 def datatype_sql(self, expression: exp.DataType) -> str: 1804 nested = "" 1805 values = "" 1806 1807 expr_nested = expression.args.get("nested") 1808 type_value = expression.this 1809 1810 if ( 1811 not expr_nested 1812 and isinstance(type_value, exp.DType) 1813 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1814 ): 1815 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1816 1817 interior = ( 1818 self.expressions( 1819 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1820 ) 1821 if expr_nested and self.pretty 1822 else self.expressions(expression, flat=True) 1823 ) 1824 1825 if type_value in self.UNSUPPORTED_TYPES: 1826 self.unsupported( 1827 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1828 ) 1829 1830 type_sql: t.Any = "" 1831 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1832 type_sql = self.sql(expression, "kind") 1833 elif type_value == exp.DType.CHARACTER_SET: 1834 char = f"CHAR({interior})" if interior else "CHAR" 1835 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1836 else: 1837 type_sql = ( 1838 self.TYPE_MAPPING.get(type_value, type_value.value) 1839 if isinstance(type_value, exp.DType) 1840 else type_value 1841 ) 1842 1843 if interior: 1844 if expr_nested: 1845 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1846 if expression.args.get("values") is not None: 1847 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1848 values = self.expressions(expression, key="values", flat=True) 1849 values = f"{delimiters[0]}{values}{delimiters[1]}" 1850 elif type_value == exp.DType.INTERVAL: 1851 nested = f" {interior}" 1852 else: 1853 nested = f"({interior})" 1854 1855 type_sql = f"{type_sql}{nested}{values}" 1856 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1857 exp.DType.TIMETZ, 1858 exp.DType.TIMESTAMPTZ, 1859 ): 1860 type_sql = f"{type_sql} WITH TIME ZONE" 1861 1862 collate = self.sql(expression, "collate") 1863 if collate: 1864 type_sql = f"{type_sql} COLLATE {collate}" 1865 1866 return type_sql 1867 1868 def directory_sql(self, expression: exp.Directory) -> str: 1869 local = "LOCAL " if expression.args.get("local") else "" 1870 row_format = self.sql(expression, "row_format") 1871 row_format = f" {row_format}" if row_format else "" 1872 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1873 1874 def delete_sql(self, expression: exp.Delete) -> str: 1875 hint = self.sql(expression, "hint") 1876 this = self.sql(expression, "this") 1877 this = f" FROM {this}" if this else "" 1878 using = self.expressions(expression, key="using") 1879 using = f" USING {using}" if using else "" 1880 cluster = self.sql(expression, "cluster") 1881 cluster = f" {cluster}" if cluster else "" 1882 where = self.sql(expression, "where") 1883 returning = self.sql(expression, "returning") 1884 order = self.sql(expression, "order") 1885 limit = self.sql(expression, "limit") 1886 tables = self.expressions(expression, key="tables") 1887 tables = f" {tables}" if tables else "" 1888 if self.RETURNING_END: 1889 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1890 else: 1891 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1892 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1893 1894 def drop_sql(self, expression: exp.Drop) -> str: 1895 tables = self.expressions(expression, key="tables", flat=True) 1896 expressions = self.expressions(expression, flat=True) 1897 expressions = f" ({expressions})" if expressions else "" 1898 kind = expression.args["kind"] 1899 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1900 iceberg = ( 1901 " ICEBERG" 1902 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1903 else "" 1904 ) 1905 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1906 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1907 on_cluster = self.sql(expression, "cluster") 1908 on_cluster = f" {on_cluster}" if on_cluster else "" 1909 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1910 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1911 cascade = " CASCADE" if expression.args.get("cascade") else "" 1912 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1913 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1914 purge = " PURGE" if expression.args.get("purge") else "" 1915 sync = " SYNC" if expression.args.get("sync") else "" 1916 force = " FORCE" if expression.args.get("force") else "" 1917 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1918 1919 def set_operation(self, expression: exp.SetOperation) -> str: 1920 op_type = type(expression) 1921 op_name = op_type.key.upper() 1922 1923 distinct = expression.args.get("distinct") 1924 if ( 1925 distinct is False 1926 and op_type in (exp.Except, exp.Intersect) 1927 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1928 ): 1929 self.unsupported(f"{op_name} ALL is not supported") 1930 1931 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1932 1933 if distinct is None: 1934 distinct = default_distinct 1935 if distinct is None: 1936 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1937 1938 if distinct is default_distinct: 1939 distinct_or_all = "" 1940 else: 1941 distinct_or_all = " DISTINCT" if distinct else " ALL" 1942 1943 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1944 side_kind = f"{side_kind} " if side_kind else "" 1945 1946 by_name = " BY NAME" if expression.args.get("by_name") else "" 1947 on = self.expressions(expression, key="on", flat=True) 1948 on = f" ON ({on})" if on else "" 1949 1950 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1951 1952 def set_operations(self, expression: exp.SetOperation) -> str: 1953 if not self.SET_OP_MODIFIERS: 1954 limit = expression.args.get("limit") 1955 order = expression.args.get("order") 1956 offset = expression.args.get("offset") 1957 1958 if limit or order or offset: 1959 select = self._move_ctes_to_top_level( 1960 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1961 ) 1962 1963 for arg in ("limit", "order", "offset"): 1964 if value := expression.args.get(arg): 1965 select.set(arg, value.pop()) 1966 return self.sql(select) 1967 1968 sqls: list[str] = [] 1969 stack: list[str | exp.Expr | int | None] = [expression] 1970 depth = 0 1971 open_parens = "" 1972 1973 while stack: 1974 node = stack.pop() 1975 1976 if isinstance(node, int): 1977 # Closes a plain grouping opened below, restoring its depth 1978 depth = node 1979 sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0) 1980 elif isinstance(node, exp.SetOperation): 1981 stack.append(node.expression) 1982 stack.append( 1983 self.maybe_comment( 1984 self.set_operation(node), comments=node.comments, separated=True 1985 ) 1986 ) 1987 left = node.this 1988 if ( 1989 not self.SET_OP_PARENTHESIZED_OPERANDS 1990 and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 1991 ): 1992 # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper: 1993 # it renames duplicate columns and deepens recursion. Inner CTEs and 1994 # modifiers still need the wrapper. 1995 wrappers: list[exp.Subquery] = [] 1996 inner = left 1997 while isinstance(inner, exp.Subquery) and inner.is_wrapper: 1998 wrappers.append(inner) 1999 inner = inner.this 2000 2001 if ( 2002 isinstance(inner, exp.SetOperation) 2003 and not inner.args.get("with_") 2004 and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS) 2005 ): 2006 for wrapper in wrappers: 2007 inner.add_comments(wrapper.pop_comments()) 2008 left = inner 2009 elif ( 2010 self.SET_OP_PARENTHESIZED_OPERANDS 2011 and isinstance(left, exp.Subquery) 2012 and left.is_wrapper 2013 and not left.comments 2014 and isinstance(left.this, exp.SetOperation) 2015 and not left.this.args.get("with_") 2016 and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS) 2017 ): 2018 # Write a plain grouping's parentheses here rather than recursing through 2019 # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and 2020 # long ones would exhaust the stack. The depth pushed here marks the close. 2021 open_parens += self.indent("(", level=depth, pad=0) + self.sep("") 2022 stack.append(depth) 2023 depth += 1 2024 left = left.this 2025 stack.append(left) 2026 else: 2027 if ( 2028 not self.SET_OP_LIMITS 2029 and isinstance(node, exp.Select) 2030 and node.args.get("limit") 2031 ): 2032 node = node.subquery(copy=False) 2033 if not self.SET_OP_PARENTHESIZED_OPERANDS: 2034 node = exp.select("*").from_(node, copy=False) 2035 elif ( 2036 not self.SET_OP_PARENTHESIZED_OPERANDS 2037 and isinstance(node, exp.Subquery) 2038 and isinstance(node.unnest(), exp.SetOperation) 2039 ): 2040 node = exp.select("*").from_(node, copy=False) 2041 sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0)) 2042 open_parens = "" 2043 2044 this = self.sep().join(sqls) 2045 this = self.query_modifiers(expression, this) 2046 return self.prepend_ctes(expression, this) 2047 2048 def fetch_sql(self, expression: exp.Fetch) -> str: 2049 direction = expression.args.get("direction") 2050 direction = f" {direction}" if direction else "" 2051 count = self.sql(expression, "count") 2052 count = f" {count}" if count else "" 2053 limit_options = self.sql(expression, "limit_options") 2054 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 2055 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 2056 2057 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 2058 percent = " PERCENT" if expression.args.get("percent") else "" 2059 rows = " ROWS" if expression.args.get("rows") else "" 2060 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 2061 if not with_ties and rows: 2062 with_ties = " ONLY" 2063 return f"{percent}{rows}{with_ties}" 2064 2065 def filter_sql(self, expression: exp.Filter) -> str: 2066 this = self.sql(expression, "this") 2067 where = self.sql(expression, "expression").strip() 2068 return f"{this} FILTER({where})" 2069 2070 def per_sql(self, expression: exp.Per) -> str: 2071 return f"{self.sql(expression, 'this')} PER ({self.expressions(expression, flat=True)})" 2072 2073 def hint_sql(self, expression: exp.Hint) -> str: 2074 if not self.QUERY_HINTS: 2075 self.unsupported("Hints are not supported") 2076 return "" 2077 2078 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2079 2080 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2081 using = self.sql(expression, "using") 2082 using = f" USING {using}" if using else "" 2083 columns = self.expressions(expression, key="columns", flat=True) 2084 columns = f"({columns})" if columns else "" 2085 partition_by = self.expressions(expression, key="partition_by", flat=True) 2086 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2087 where = self.sql(expression, "where") 2088 include = self.expressions(expression, key="include", flat=True) 2089 if include: 2090 include = f" INCLUDE ({include})" 2091 with_storage = self.expressions(expression, key="with_storage", flat=True) 2092 with_storage = f" WITH ({with_storage})" if with_storage else "" 2093 tablespace = self.sql(expression, "tablespace") 2094 if tablespace: 2095 prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX" 2096 tablespace = f"{prefix} TABLESPACE {tablespace}" 2097 on = self.sql(expression, "on") 2098 on = f" ON {on}" if on else "" 2099 2100 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2101 2102 def index_sql(self, expression: exp.Index) -> str: 2103 unique = "UNIQUE " if expression.args.get("unique") else "" 2104 primary = "PRIMARY " if expression.args.get("primary") else "" 2105 amp = "AMP " if expression.args.get("amp") else "" 2106 name = self.sql(expression, "this") 2107 name = f"{name} " if name else "" 2108 table = self.sql(expression, "table") 2109 table = f"{self.INDEX_ON} {table}" if table else "" 2110 2111 index = "INDEX " if not table else "" 2112 2113 params = self.sql(expression, "params") 2114 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2115 2116 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2117 this = expression.this 2118 if this and this.is_string: 2119 resolved = maybe_parse(this.name).sql(self.dialect) 2120 if "expressions" in expression.args: 2121 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2122 # We can't safely emit the call to other dialects since name/arg semantics may differ 2123 self.unsupported( 2124 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2125 ) 2126 return resolved 2127 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2128 return self.func("IDENTIFIER", this) 2129 2130 def identifier_sql(self, expression: exp.Identifier) -> str: 2131 text = expression.name 2132 lower = text.lower() 2133 quoted = expression.quoted 2134 text = lower if self.normalize and not quoted else text 2135 if self._identifier_escaped_sequences: 2136 for char, escaped in self._identifier_escaped_sequences.items(): 2137 text = text.replace(char, escaped) 2138 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2139 if ( 2140 quoted 2141 or self.dialect.can_quote(expression, self.identify) 2142 or lower in self.RESERVED_KEYWORDS 2143 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2144 ): 2145 text = ( 2146 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2147 ) 2148 return text 2149 2150 def hex_sql(self, expression: exp.Hex) -> str: 2151 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2152 if self.dialect.HEX_LOWERCASE: 2153 text = self.func("LOWER", text) 2154 2155 return text 2156 2157 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2158 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2159 if not self.dialect.HEX_LOWERCASE: 2160 text = self.func("LOWER", text) 2161 return text 2162 2163 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2164 input_format = self.sql(expression, "input_format") 2165 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2166 output_format = self.sql(expression, "output_format") 2167 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2168 return self.sep().join((input_format, output_format)) 2169 2170 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2171 string = self.sql(exp.Literal.string(expression.name)) 2172 return f"{prefix}{string}" 2173 2174 def partition_sql(self, expression: exp.Partition) -> str: 2175 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2176 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2177 2178 def properties_sql(self, expression: exp.Properties) -> str: 2179 root_properties = [] 2180 with_properties = [] 2181 2182 for p in expression.expressions: 2183 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2184 if p_loc == exp.Properties.Location.POST_WITH: 2185 with_properties.append(p) 2186 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2187 root_properties.append(p) 2188 2189 root_props_ast = exp.Properties(expressions=root_properties) 2190 root_props_ast.parent = expression.parent 2191 2192 with_props_ast = exp.Properties(expressions=with_properties) 2193 with_props_ast.parent = expression.parent 2194 2195 root_props = self.root_properties(root_props_ast) 2196 with_props = self.with_properties(with_props_ast) 2197 2198 if root_props and with_props and not self.pretty: 2199 with_props = " " + with_props 2200 2201 return root_props + with_props 2202 2203 def root_properties(self, properties: exp.Properties) -> str: 2204 if properties.expressions: 2205 return self.expressions(properties, indent=False, sep=" ") 2206 return "" 2207 2208 def properties( 2209 self, 2210 properties: exp.Properties, 2211 prefix: str = "", 2212 sep: str = ", ", 2213 suffix: str = "", 2214 wrapped: bool = True, 2215 ) -> str: 2216 if properties.expressions: 2217 expressions = self.expressions(properties, sep=sep, indent=False) 2218 if expressions: 2219 expressions = self.wrap(expressions) if wrapped else expressions 2220 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2221 return "" 2222 2223 def with_properties(self, properties: exp.Properties) -> str: 2224 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2225 2226 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2227 properties_locs = defaultdict(list) 2228 for p in properties.expressions: 2229 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2230 if p_loc != exp.Properties.Location.UNSUPPORTED: 2231 properties_locs[p_loc].append(p) 2232 else: 2233 self.unsupported(f"Unsupported property {p.key}") 2234 2235 return properties_locs 2236 2237 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2238 if isinstance(expression.this, exp.Dot): 2239 return self.sql(expression, "this") 2240 return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name 2241 2242 def property_sql(self, expression: exp.Property) -> str: 2243 property_cls = expression.__class__ 2244 if property_cls == exp.Property: 2245 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2246 2247 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2248 if not property_name: 2249 self.unsupported(f"Unsupported property {expression.key}") 2250 return "" 2251 2252 return f"{property_name}={self.sql(expression, 'this')}" 2253 2254 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2255 return f"UUID {self.sql(expression, 'this')}" 2256 2257 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2258 if self.SUPPORTS_CREATE_TABLE_LIKE: 2259 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2260 options = f" {options}" if options else "" 2261 2262 like = f"LIKE {self.sql(expression, 'this')}{options}" 2263 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2264 like = f"({like})" 2265 2266 return like 2267 2268 if expression.expressions: 2269 self.unsupported("Transpilation of LIKE property options is unsupported") 2270 2271 select = exp.select("*").from_(expression.this).limit(0) 2272 return f"AS {self.sql(select)}" 2273 2274 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2275 no = "NO " if expression.args.get("no") else "" 2276 protection = " PROTECTION" if expression.args.get("protection") else "" 2277 return f"{no}FALLBACK{protection}" 2278 2279 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2280 no = "NO " if expression.args.get("no") else "" 2281 local = expression.args.get("local") 2282 local = f"{local} " if local else "" 2283 dual = "DUAL " if expression.args.get("dual") else "" 2284 before = "BEFORE " if expression.args.get("before") else "" 2285 after = "AFTER " if expression.args.get("after") else "" 2286 return f"{no}{local}{dual}{before}{after}JOURNAL" 2287 2288 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2289 freespace = self.sql(expression, "this") 2290 percent = " PERCENT" if expression.args.get("percent") else "" 2291 return f"FREESPACE={freespace}{percent}" 2292 2293 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2294 if expression.args.get("default"): 2295 property = "DEFAULT" 2296 elif expression.args.get("on"): 2297 property = "ON" 2298 else: 2299 property = "OFF" 2300 return f"CHECKSUM={property}" 2301 2302 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2303 if expression.args.get("no"): 2304 return "NO MERGEBLOCKRATIO" 2305 if expression.args.get("default"): 2306 return "DEFAULT MERGEBLOCKRATIO" 2307 2308 percent = " PERCENT" if expression.args.get("percent") else "" 2309 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2310 2311 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2312 expressions = self.expressions(expression, flat=True) 2313 expressions = f"({expressions})" if expressions else "" 2314 return f"USING {self.sql(expression, 'this')}{expressions}" 2315 2316 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2317 default = expression.args.get("default") 2318 minimum = expression.args.get("minimum") 2319 maximum = expression.args.get("maximum") 2320 if default or minimum or maximum: 2321 if default: 2322 prop = "DEFAULT" 2323 elif minimum: 2324 prop = "MINIMUM" 2325 else: 2326 prop = "MAXIMUM" 2327 return f"{prop} DATABLOCKSIZE" 2328 units = expression.args.get("units") 2329 units = f" {units}" if units else "" 2330 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2331 2332 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2333 autotemp = expression.args.get("autotemp") 2334 always = expression.args.get("always") 2335 default = expression.args.get("default") 2336 manual = expression.args.get("manual") 2337 never = expression.args.get("never") 2338 2339 if autotemp is not None: 2340 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2341 elif always: 2342 prop = "ALWAYS" 2343 elif default: 2344 prop = "DEFAULT" 2345 elif manual: 2346 prop = "MANUAL" 2347 elif never: 2348 prop = "NEVER" 2349 return f"BLOCKCOMPRESSION={prop}" 2350 2351 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2352 no = expression.args.get("no") 2353 no = " NO" if no else "" 2354 concurrent = expression.args.get("concurrent") 2355 concurrent = " CONCURRENT" if concurrent else "" 2356 target = self.sql(expression, "target") 2357 target = f" {target}" if target else "" 2358 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2359 2360 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2361 if isinstance(expression.this, list): 2362 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2363 if expression.this: 2364 modulus = self.sql(expression, "this") 2365 remainder = self.sql(expression, "expression") 2366 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2367 2368 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2369 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2370 return f"FROM ({from_expressions}) TO ({to_expressions})" 2371 2372 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2373 this = self.sql(expression, "this") 2374 2375 for_values_or_default = expression.expression 2376 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2377 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2378 else: 2379 for_values_or_default = " DEFAULT" 2380 2381 return f"PARTITION OF {this}{for_values_or_default}" 2382 2383 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2384 kind = expression.args.get("kind") 2385 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2386 for_or_in = expression.args.get("for_or_in") 2387 for_or_in = f" {for_or_in}" if for_or_in else "" 2388 lock_type = expression.args.get("lock_type") 2389 override = " OVERRIDE" if expression.args.get("override") else "" 2390 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2391 2392 def lockingstatement_sql(self, expression: exp.LockingStatement) -> str: 2393 self.unsupported("LOCKING request modifier is not supported") 2394 return self.sql(expression, "expression") 2395 2396 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2397 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2398 statistics = expression.args.get("statistics") 2399 statistics_sql = "" 2400 if statistics is not None: 2401 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2402 return f"{data_sql}{statistics_sql}" 2403 2404 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2405 this = self.sql(expression, "this") 2406 this = f"HISTORY_TABLE={this}" if this else "" 2407 data_consistency: str | None = self.sql(expression, "data_consistency") 2408 data_consistency = ( 2409 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2410 ) 2411 retention_period: str | None = self.sql(expression, "retention_period") 2412 retention_period = ( 2413 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2414 ) 2415 2416 if this: 2417 on_sql = self.func("ON", this, data_consistency, retention_period) 2418 else: 2419 on_sql = "ON" if expression.args.get("on") else "OFF" 2420 2421 sql = f"SYSTEM_VERSIONING={on_sql}" 2422 2423 return f"WITH({sql})" if expression.args.get("with_") else sql 2424 2425 def insert_sql(self, expression: exp.Insert) -> str: 2426 hint = self.sql(expression, "hint") 2427 overwrite = expression.args.get("overwrite") 2428 2429 if isinstance(expression.this, exp.Directory): 2430 this = " OVERWRITE" if overwrite else " INTO" 2431 else: 2432 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2433 2434 stored = self.sql(expression, "stored") 2435 stored = f" {stored}" if stored else "" 2436 alternative = expression.args.get("alternative") 2437 alternative = f" OR {alternative}" if alternative else "" 2438 ignore = " IGNORE" if expression.args.get("ignore") else "" 2439 is_function = expression.args.get("is_function") 2440 if is_function: 2441 this = f"{this} FUNCTION" 2442 this = f"{this} {self.sql(expression, 'this')}" 2443 2444 exists = " IF EXISTS" if expression.args.get("exists") else "" 2445 where = self.sql(expression, "where") 2446 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2447 using = self.expressions(expression, key="using", flat=True) 2448 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2449 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2450 on_conflict = self.sql(expression, "conflict") 2451 on_conflict = f" {on_conflict}" if on_conflict else "" 2452 by_name = " BY NAME" if expression.args.get("by_name") else "" 2453 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2454 returning = self.sql(expression, "returning") 2455 2456 if self.RETURNING_END: 2457 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2458 else: 2459 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2460 2461 partition_by = self.sql(expression, "partition") 2462 partition_by = f" {partition_by}" if partition_by else "" 2463 settings = self.sql(expression, "settings") 2464 settings = f" {settings}" if settings else "" 2465 2466 source = self.sql(expression, "source") 2467 source = f"TABLE {source}" if source else "" 2468 2469 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2470 return self.prepend_ctes(expression, sql) 2471 2472 def introducer_sql(self, expression: exp.Introducer) -> str: 2473 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2474 2475 def kill_sql(self, expression: exp.Kill) -> str: 2476 kind = self.sql(expression, "kind") 2477 kind = f" {kind}" if kind else "" 2478 this = self.sql(expression, "this") 2479 this = f" {this}" if this else "" 2480 return f"KILL{kind}{this}" 2481 2482 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2483 return expression.name 2484 2485 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2486 return expression.name 2487 2488 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2489 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2490 2491 constraint = self.sql(expression, "constraint") 2492 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2493 2494 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2495 if conflict_keys: 2496 conflict_keys = f"({conflict_keys})" 2497 2498 index_predicate = self.sql(expression, "index_predicate") 2499 conflict_keys = f"{conflict_keys}{index_predicate} " 2500 2501 action = self.sql(expression, "action") 2502 2503 expressions = self.expressions(expression, flat=True) 2504 if expressions: 2505 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2506 expressions = f" {set_keyword}{expressions}" 2507 2508 where = self.sql(expression, "where") 2509 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2510 2511 def returning_sql(self, expression: exp.Returning) -> str: 2512 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2513 2514 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2515 fields = self.sql(expression, "fields") 2516 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2517 escaped = self.sql(expression, "escaped") 2518 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2519 items = self.sql(expression, "collection_items") 2520 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2521 keys = self.sql(expression, "map_keys") 2522 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2523 lines = self.sql(expression, "lines") 2524 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2525 null = self.sql(expression, "null") 2526 null = f" NULL DEFINED AS {null}" if null else "" 2527 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2528 2529 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2530 return f"WITH ({self.expressions(expression, flat=True)})" 2531 2532 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2533 this = f"{self.sql(expression, 'this')} INDEX" 2534 target = self.sql(expression, "target") 2535 target = f" FOR {target}" if target else "" 2536 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2537 2538 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2539 this = self.sql(expression, "this") 2540 kind = self.sql(expression, "kind") 2541 expr = self.sql(expression, "expression") 2542 return f"{this} ({kind} => {expr})" 2543 2544 def table_parts(self, expression: exp.Table) -> str: 2545 return ".".join( 2546 self.sql(part) 2547 for part in ( 2548 expression.args.get("catalog"), 2549 expression.args.get("db"), 2550 expression.args.get("this"), 2551 ) 2552 if part is not None 2553 ) 2554 2555 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2556 table = self.table_parts(expression) 2557 only = "ONLY " if expression.args.get("only") else "" 2558 partition = self.sql(expression, "partition") 2559 partition = f" {partition}" if partition else "" 2560 version = self.sql(expression, "version") 2561 version = f" {version}" if version else "" 2562 alias = self.sql(expression, "alias") 2563 alias = f"{sep}{alias}" if alias else "" 2564 2565 sample = self.sql(expression, "sample") 2566 post_alias = "" 2567 pre_alias = "" 2568 2569 if self.dialect.ALIAS_POST_TABLESAMPLE: 2570 pre_alias = sample 2571 else: 2572 post_alias = sample 2573 2574 if self.dialect.ALIAS_POST_VERSION: 2575 pre_alias = f"{pre_alias}{version}" 2576 else: 2577 post_alias = f"{post_alias}{version}" 2578 2579 hints = self.expressions(expression, key="hints", sep=" ") 2580 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2581 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2582 joins = self.indent( 2583 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2584 ) 2585 laterals = self.expressions(expression, key="laterals", sep="") 2586 2587 file_format = self.sql(expression, "format") 2588 pattern = self.sql(expression, "pattern") 2589 if file_format: 2590 pattern = f", PATTERN => {pattern}" if pattern else "" 2591 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2592 elif pattern: 2593 file_format = f" (PATTERN => {pattern})" 2594 2595 ordinality = expression.args.get("ordinality") or "" 2596 if ordinality: 2597 ordinality = f" WITH ORDINALITY{alias}" 2598 alias = "" 2599 2600 when = self.sql(expression, "when") 2601 if when: 2602 if self.HISTORICAL_DATA_POST_ALIAS: 2603 alias = f"{alias} {when}" 2604 else: 2605 table = f"{table} {when}" 2606 2607 changes = self.sql(expression, "changes") 2608 changes = f" {changes}" if changes else "" 2609 2610 rows_from = self.expressions(expression, key="rows_from") 2611 if rows_from: 2612 table = f"ROWS FROM {self.wrap(rows_from)}" 2613 2614 indexed = expression.args.get("indexed") 2615 if indexed is not None: 2616 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2617 else: 2618 indexed = "" 2619 2620 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2621 2622 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2623 table = self.func("TABLE", expression.this) 2624 alias = self.sql(expression, "alias") 2625 alias = f" AS {alias}" if alias else "" 2626 sample = self.sql(expression, "sample") 2627 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2628 joins = self.indent( 2629 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2630 ) 2631 return f"{table}{alias}{pivots}{sample}{joins}" 2632 2633 def tablesample_sql( 2634 self, 2635 expression: exp.TableSample, 2636 tablesample_keyword: str | None = None, 2637 ) -> str: 2638 if expression.expressions: 2639 self.unsupported("Multiple sample sets are not supported") 2640 return "" 2641 2642 method = self.sql(expression, "method") 2643 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2644 numerator = self.sql(expression, "bucket_numerator") 2645 denominator = self.sql(expression, "bucket_denominator") 2646 field = self.sql(expression, "bucket_field") 2647 field = f" ON {field}" if field else "" 2648 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2649 seed = self.sql(expression, "seed") 2650 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2651 2652 size = self.sql(expression, "size") 2653 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2654 size = f"{size} ROWS" 2655 2656 percent = self.sql(expression, "percent") 2657 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2658 percent = f"{percent} PERCENT" 2659 2660 expr = f"{bucket}{percent}{size}" 2661 if self.TABLESAMPLE_REQUIRES_PARENS: 2662 expr = f"({expr})" 2663 2664 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2665 2666 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2667 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2668 # the stored column name differs from the target dialect's natural output. 2669 columns = expression.args.get("columns") 2670 if not columns or len(expression.fields) != 1: 2671 return None 2672 2673 args = expression.args 2674 parser_cls = self.dialect.parser_class 2675 2676 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2677 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2678 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2679 2680 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2681 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2682 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2683 2684 if ( 2685 src_identify_pivot_strings == tgt_identify_pivot_strings 2686 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2687 and src_pivot_column_naming == tgt_pivot_column_naming 2688 ): 2689 return None 2690 2691 in_exprs = expression.fields[0].expressions 2692 step = len(columns) // len(in_exprs) 2693 2694 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2695 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2696 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2697 first_stored = columns[0].name 2698 2699 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2700 if not first_stored.startswith(first_base): 2701 return None 2702 2703 suffix = first_stored[len(first_base) :] 2704 2705 # Whether the target dialect would append an agg-name suffix for this pivot. 2706 # Spark single-agg uniquely drops the agg alias entirely. 2707 target_has_suffix = ( 2708 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2709 ) and any(a.alias for a in expression.expressions) 2710 source_has_suffix = suffix != "" 2711 2712 new_exprs: list[exp.Expression] = [] 2713 modified = False 2714 for val_idx, e in enumerate(in_exprs): 2715 if isinstance(e, exp.PivotAlias): 2716 new_exprs.append(e) 2717 continue 2718 2719 i = val_idx * step 2720 stored_full = columns[i].name 2721 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2722 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2723 2724 # Source had a suffix, but target won't apply one 2725 if source_has_suffix and not target_has_suffix: 2726 new_exprs.append( 2727 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2728 ) 2729 modified = True 2730 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2731 elif stored_value != target_value: 2732 new_exprs.append( 2733 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2734 ) 2735 modified = True 2736 else: 2737 new_exprs.append(e) 2738 2739 return new_exprs if modified else None 2740 2741 def pivot_sql(self, expression: exp.Pivot) -> str: 2742 expressions = self.expressions(expression, flat=True) 2743 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2744 2745 group = self.sql(expression, "group") 2746 2747 if expression.this: 2748 this = self.sql(expression, "this") 2749 if not expressions: 2750 sql = f"UNPIVOT {this}" 2751 else: 2752 on = f"{self.seg('ON')} {expressions}" 2753 into = self.sql(expression, "into") 2754 into = f"{self.seg('INTO')} {into}" if into else "" 2755 using = self.expressions(expression, key="using", flat=True) 2756 using = f"{self.seg('USING')} {using}" if using else "" 2757 sql = f"{direction} {this}{on}{into}{using}{group}" 2758 return self.prepend_ctes(expression, sql) 2759 2760 if not expression.unpivot: 2761 # Wrap IN-list values with explicit aliases where the target dialect would differ 2762 new_field_exprs = self._pivot_in_value_aliases(expression) 2763 if new_field_exprs is not None: 2764 expression.fields[0].set("expressions", new_field_exprs) 2765 2766 alias = self.sql(expression, "alias") 2767 if alias: 2768 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2769 2770 fields = self.expressions( 2771 expression, 2772 "fields", 2773 sep=" ", 2774 dynamic=True, 2775 new_line=True, 2776 skip_first=True, 2777 skip_last=True, 2778 ) 2779 2780 include_nulls = expression.args.get("include_nulls") 2781 if include_nulls is not None: 2782 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2783 else: 2784 nulls = "" 2785 2786 default_on_null = self.sql(expression, "default_on_null") 2787 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2788 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2789 return self.prepend_ctes(expression, sql) 2790 2791 def version_sql(self, expression: exp.Version) -> str: 2792 this = f"FOR {expression.name}" 2793 kind = expression.text("kind") 2794 expr = self.sql(expression, "expression") 2795 return f"{this} {kind} {expr}" 2796 2797 def tuple_sql(self, expression: exp.Tuple) -> str: 2798 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2799 2800 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2801 """ 2802 Returns (join_sql, from_sql) for UPDATE statements. 2803 - join_sql: placed after UPDATE table, before SET 2804 - from_sql: placed after SET clause (standard position) 2805 Dialects like MySQL need to convert FROM to JOIN syntax. 2806 """ 2807 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2808 return ("", self.sql(expression, "from_")) 2809 2810 # Qualify unqualified columns in SET clause with the target table 2811 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2812 target_table = expression.this 2813 if isinstance(target_table, exp.Table): 2814 target_name = exp.to_identifier(target_table.alias_or_name) 2815 for eq in expression.expressions: 2816 col = eq.this 2817 if isinstance(col, exp.Column) and not col.table: 2818 col.set("table", target_name) 2819 2820 table = from_expr.this 2821 if nested_joins := table.args.get("joins", []): 2822 table.set("joins", None) 2823 2824 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2825 for nested in nested_joins: 2826 if not nested.args.get("on") and not nested.args.get("using"): 2827 nested.set("on", exp.true()) 2828 join_sql += self.sql(nested) 2829 2830 return (join_sql, "") 2831 2832 def update_sql(self, expression: exp.Update) -> str: 2833 hint = self.sql(expression, "hint") 2834 this = self.sql(expression, "this") 2835 join_sql, from_sql = self._update_from_joins_sql(expression) 2836 set_sql = self.expressions(expression, flat=True) 2837 where_sql = self.sql(expression, "where") 2838 returning = self.sql(expression, "returning") 2839 order = self.sql(expression, "order") 2840 limit = self.sql(expression, "limit") 2841 if self.RETURNING_END: 2842 expression_sql = f"{from_sql}{where_sql}{returning}" 2843 else: 2844 expression_sql = f"{returning}{from_sql}{where_sql}" 2845 options = self.expressions(expression, key="options") 2846 options = f" OPTION({options})" if options else "" 2847 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2848 return self.prepend_ctes(expression, sql) 2849 2850 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2851 values_as_table = values_as_table and self.VALUES_AS_TABLE 2852 2853 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2854 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2855 args = self.expressions(expression) 2856 alias = self.sql(expression, "alias") 2857 values = f"VALUES{self.seg('')}{args}" 2858 values = ( 2859 f"({values})" 2860 if self.WRAP_DERIVED_VALUES 2861 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2862 else values 2863 ) 2864 values = self.query_modifiers(expression, values) 2865 return f"{values} AS {alias}" if alias else values 2866 2867 # Converts `VALUES...` expression into a series of select unions. 2868 alias_node = expression.args.get("alias") 2869 column_names = alias_node and alias_node.columns 2870 2871 selects: list[exp.Query] = [] 2872 2873 for i, tup in enumerate(expression.expressions): 2874 row = tup.expressions 2875 2876 if i == 0 and column_names: 2877 row = [ 2878 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2879 ] 2880 2881 selects.append(exp.Select(expressions=row)) 2882 2883 if self.pretty: 2884 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2885 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2886 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2887 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2888 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2889 2890 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2891 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2892 return f"({unions}){alias}" 2893 2894 def var_sql(self, expression: exp.Var) -> str: 2895 return self.sql(expression, "this") 2896 2897 @unsupported_args("expressions") 2898 def into_sql(self, expression: exp.Into) -> str: 2899 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2900 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2901 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2902 2903 def from_sql(self, expression: exp.From) -> str: 2904 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2905 2906 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2907 grouping_sets = self.expressions(expression, indent=False) 2908 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2909 2910 def rollup_sql(self, expression: exp.Rollup) -> str: 2911 expressions = self.expressions(expression, indent=False) 2912 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2913 2914 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2915 this = self.sql(expression, "this") 2916 2917 columns = self.expressions(expression, flat=True) 2918 2919 from_sql = self.sql(expression, "from_index") 2920 from_sql = f" FROM {from_sql}" if from_sql else "" 2921 2922 properties = expression.args.get("properties") 2923 properties_sql = ( 2924 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2925 ) 2926 2927 return f"{this}({columns}){from_sql}{properties_sql}" 2928 2929 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2930 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2931 2932 def cube_sql(self, expression: exp.Cube) -> str: 2933 expressions = self.expressions(expression, indent=False) 2934 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2935 2936 def group_sql(self, expression: exp.Group) -> str: 2937 group_by_all = expression.args.get("all") 2938 if group_by_all is True: 2939 modifier = " ALL" 2940 elif group_by_all is False: 2941 modifier = " DISTINCT" 2942 else: 2943 modifier = "" 2944 2945 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2946 2947 grouping_sets = self.expressions(expression, key="grouping_sets") 2948 cube = self.expressions(expression, key="cube") 2949 rollup = self.expressions(expression, key="rollup") 2950 2951 groupings = csv( 2952 self.seg(grouping_sets) if grouping_sets else "", 2953 self.seg(cube) if cube else "", 2954 self.seg(rollup) if rollup else "", 2955 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2956 sep=self.GROUPINGS_SEP, 2957 ) 2958 2959 if ( 2960 expression.expressions 2961 and groupings 2962 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2963 ): 2964 add_separator = True 2965 2966 if grouping_sets: 2967 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2968 add_separator = False 2969 else: 2970 self.unsupported( 2971 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2972 ) 2973 2974 if add_separator: 2975 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2976 2977 return f"{group_by}{groupings}" 2978 2979 def having_sql(self, expression: exp.Having) -> str: 2980 this = self.indent(self.sql(expression, "this")) 2981 return f"{self.seg('HAVING')}{self.sep()}{this}" 2982 2983 def connectto_sql(self, expression: exp.ConnectTo) -> str: 2984 return f"CONNECT {self.sql(expression, 'this')}" 2985 2986 def disconnect_sql(self, expression: exp.Disconnect) -> str: 2987 return "DISCONNECT" 2988 2989 def connect_sql(self, expression: exp.Connect) -> str: 2990 start = self.sql(expression, "start") 2991 start = self.seg(f"START WITH {start}") if start else "" 2992 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2993 connect = self.sql(expression, "connect") 2994 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2995 return start + connect 2996 2997 def prior_sql(self, expression: exp.Prior) -> str: 2998 return f"PRIOR {self.sql(expression, 'this')}" 2999 3000 def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str: 3001 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 3002 side = None 3003 else: 3004 side = expression.side 3005 3006 op_sql = " ".join( 3007 op 3008 for op in ( 3009 expression.method, 3010 "GLOBAL" if expression.args.get("global_") else None, 3011 side, 3012 expression.kind, 3013 expression.hint if self.JOIN_HINTS else None, 3014 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 3015 ) 3016 if op 3017 ) 3018 match_cond = self.sql(expression, "match_condition") 3019 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 3020 nearest = self.sql(expression, "nearest") 3021 nearest = f" {nearest}" if nearest else "" 3022 on_sql = self.sql(expression, "on") 3023 using = expression.args.get("using") 3024 3025 if not on_sql and using: 3026 on_sql = csv(*(self.sql(column) for column in using)) 3027 3028 this = expression.this 3029 this_sql = self.sql(this) 3030 3031 exprs = self.expressions(expression) 3032 if exprs: 3033 this_sql = f"{this_sql},{self.seg(exprs)}" 3034 3035 if on_sql: 3036 on_sql = self.indent(on_sql, skip_first=True) 3037 space = self.seg(" " * self.pad) if self.pretty else " " 3038 if using: 3039 on_sql = f"{space}USING ({on_sql})" 3040 else: 3041 on_sql = f"{space}ON {on_sql}" 3042 elif not op_sql and not nearest: 3043 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 3044 return f" {this_sql}" 3045 3046 return f", {this_sql}" 3047 3048 if join_op: 3049 op_sql = join_op 3050 elif op_sql != "STRAIGHT_JOIN": 3051 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 3052 3053 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3054 return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}" 3055 3056 def nearest_sql(self, expression: exp.Nearest) -> str: 3057 if not self.SUPPORTS_NEAREST_JOIN: 3058 self.unsupported("NEAREST joins are not supported") 3059 3060 approximate = "APPROX " if expression.args.get("approximate") else "" 3061 metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE" 3062 return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}" 3063 3064 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 3065 args = self.expressions(expression, flat=True) 3066 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 3067 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 3068 3069 def lateral_op(self, expression: exp.Lateral) -> str: 3070 cross_apply = expression.args.get("cross_apply") 3071 3072 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 3073 if cross_apply is True: 3074 op = "INNER JOIN " 3075 elif cross_apply is False: 3076 op = "LEFT JOIN " 3077 else: 3078 op = "" 3079 3080 return f"{op}LATERAL" 3081 3082 def lateral_sql(self, expression: exp.Lateral) -> str: 3083 this = self.sql(expression, "this") 3084 3085 if expression.args.get("view"): 3086 alias = expression.args["alias"] 3087 columns = self.expressions(alias, key="columns", flat=True) 3088 table = f" {alias.name}" if alias.name else "" 3089 columns = f" AS {columns}" if columns else "" 3090 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 3091 return f"{op_sql}{self.sep()}{this}{table}{columns}" 3092 3093 table_alias = expression.args.get("alias") 3094 offset = expression.this.args.get("offset") 3095 3096 if ( 3097 self.UNNEST_WITH_ORDINALITY 3098 and table_alias 3099 and isinstance(expression.this, exp.Unnest) 3100 and isinstance(offset, exp.Identifier) 3101 ): 3102 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3103 table_alias = table_alias.copy() 3104 table_alias.append("columns", offset.copy()) 3105 3106 alias = self.sql(table_alias) 3107 alias = f" AS {alias}" if alias else "" 3108 3109 ordinality = expression.args.get("ordinality") or "" 3110 if ordinality: 3111 ordinality = f" WITH ORDINALITY{alias}" 3112 alias = "" 3113 3114 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3115 3116 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3117 this = self.sql(expression, "this") 3118 3119 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3120 return this 3121 3122 args = [ 3123 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3124 for e in (expression.args.get(k) for k in ("offset", "expression")) 3125 if e 3126 ] 3127 3128 args_sql = ", ".join(self.sql(e) for e in args) 3129 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3130 expressions = self.expressions(expression, flat=True) 3131 limit_options = self.sql(expression, "limit_options") 3132 expressions = f" BY {expressions}" if expressions else "" 3133 3134 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3135 3136 def offset_sql(self, expression: exp.Offset) -> str: 3137 this = self.sql(expression, "this") 3138 value = expression.expression 3139 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3140 expressions = self.expressions(expression, flat=True) 3141 expressions = f" BY {expressions}" if expressions else "" 3142 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3143 3144 def setitem_sql(self, expression: exp.SetItem) -> str: 3145 kind = self.sql(expression, "kind") 3146 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3147 kind = "" 3148 else: 3149 kind = f"{kind} " if kind else "" 3150 this = self.sql(expression, "this") 3151 expressions = self.expressions(expression) 3152 collate = self.sql(expression, "collate") 3153 collate = f" COLLATE {collate}" if collate else "" 3154 global_ = "GLOBAL " if expression.args.get("global_") else "" 3155 return f"{global_}{kind}{this}{expressions}{collate}" 3156 3157 def set_sql(self, expression: exp.Set) -> str: 3158 expressions = f" {self.expressions(expression, flat=True)}" 3159 tag = " TAG" if expression.args.get("tag") else "" 3160 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3161 3162 def queryband_sql(self, expression: exp.QueryBand) -> str: 3163 this = self.sql(expression, "this") 3164 update = " UPDATE" if expression.args.get("update") else "" 3165 scope = self.sql(expression, "scope") 3166 scope = f" FOR {scope}" if scope else "" 3167 3168 return f"QUERY_BAND = {this}{update}{scope}" 3169 3170 def pragma_sql(self, expression: exp.Pragma) -> str: 3171 return f"PRAGMA {self.sql(expression, 'this')}" 3172 3173 def lock_sql(self, expression: exp.Lock) -> str: 3174 if not self.LOCKING_READS_SUPPORTED: 3175 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3176 return "" 3177 3178 update = expression.args["update"] 3179 key = expression.args.get("key") 3180 if update: 3181 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3182 else: 3183 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3184 expressions = self.expressions(expression, flat=True) 3185 expressions = f" OF {expressions}" if expressions else "" 3186 wait = expression.args.get("wait") 3187 3188 if wait is not None: 3189 if isinstance(wait, exp.Literal): 3190 wait = f" WAIT {self.sql(wait)}" 3191 else: 3192 wait = " NOWAIT" if wait else " SKIP LOCKED" 3193 3194 return f"{lock_type}{expressions}{wait or ''}" 3195 3196 def literal_sql(self, expression: exp.Literal) -> str: 3197 text = expression.this or "" 3198 if expression.is_string: 3199 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3200 return text 3201 3202 def escape_str( 3203 self, 3204 text: str, 3205 escape_backslash: bool = True, 3206 delimiter: str | None = None, 3207 escaped_delimiter: str | None = None, 3208 is_byte_string: bool = False, 3209 is_bytes: bool = False, 3210 ) -> str: 3211 if is_byte_string: 3212 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3213 else: 3214 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3215 3216 if supports_escape_sequences: 3217 text = "".join( 3218 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3219 for ch in text 3220 ) 3221 3222 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3223 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3224 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3225 3226 delimiter = delimiter or self.dialect.QUOTE_END 3227 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3228 3229 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3230 3231 def loaddata_sql(self, expression: exp.LoadData) -> str: 3232 is_overwrite = expression.args.get("overwrite") 3233 overwrite = " OVERWRITE" if is_overwrite else "" 3234 this = self.sql(expression, "this") 3235 3236 files = expression.args.get("files") 3237 if files: 3238 files_sql = self.expressions(files, flat=True) 3239 files_sql = f"FILES{self.wrap(files_sql)}" 3240 if is_overwrite: 3241 this = f" {this}" 3242 elif expression.args.get("temp"): 3243 this = f" INTO TEMP TABLE {this}" 3244 else: 3245 this = f" INTO TABLE {this}" 3246 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3247 3248 local = " LOCAL" if expression.args.get("local") else "" 3249 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3250 this = f" INTO TABLE {this}" 3251 partition = self.sql(expression, "partition") 3252 partition = f" {partition}" if partition else "" 3253 input_format = self.sql(expression, "input_format") 3254 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3255 serde = self.sql(expression, "serde") 3256 serde = f" SERDE {serde}" if serde else "" 3257 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3258 3259 def null_sql(self, *_) -> str: 3260 return "NULL" 3261 3262 def boolean_sql(self, expression: exp.Boolean) -> str: 3263 return "TRUE" if expression.this else "FALSE" 3264 3265 def booland_sql(self, expression: exp.Booland) -> str: 3266 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3267 3268 def boolor_sql(self, expression: exp.Boolor) -> str: 3269 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3270 3271 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3272 this = self.sql(expression, "this") 3273 this = f"{this} " if this else this 3274 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3275 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3276 3277 def withfill_sql(self, expression: exp.WithFill) -> str: 3278 from_sql = self.sql(expression, "from_") 3279 from_sql = f" FROM {from_sql}" if from_sql else "" 3280 to_sql = self.sql(expression, "to") 3281 to_sql = f" TO {to_sql}" if to_sql else "" 3282 step_sql = self.sql(expression, "step") 3283 step_sql = f" STEP {step_sql}" if step_sql else "" 3284 interpolated_values = [ 3285 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3286 if isinstance(e, exp.Alias) 3287 else self.sql(e, "this") 3288 for e in expression.args.get("interpolate") or [] 3289 ] 3290 interpolate = ( 3291 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3292 ) 3293 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3294 3295 def cluster_sql(self, expression: exp.Cluster) -> str: 3296 return self.op_expressions("CLUSTER BY", expression) 3297 3298 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3299 if expression.this: 3300 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3301 return "" 3302 expressions = self.expressions(expression, flat=True) 3303 return f"CLUSTER BY ({expressions})" 3304 3305 def distribute_sql(self, expression: exp.Distribute) -> str: 3306 return self.op_expressions("DISTRIBUTE BY", expression) 3307 3308 def sort_sql(self, expression: exp.Sort) -> str: 3309 return self.op_expressions("SORT BY", expression) 3310 3311 def _resolve_ordered_for_null_ordering_simulation( 3312 self, expression: exp.Ordered 3313 ) -> exp.Expr | None: 3314 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3315 3316 Returns the underlying expression of the uniquely-matching projection 3317 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3318 simulation, since the CASE is evaluated in FROM-clause scope rather 3319 than alias scope (MySQL error 1052). Returns None if no safe 3320 substitution applies, leaving the original behaviour unchanged. 3321 """ 3322 this = expression.this 3323 if not (isinstance(this, exp.Column) and not this.table): 3324 return None 3325 3326 # Only a query's own ORDER BY can reference its projection aliases 3327 order = expression.parent 3328 ancestor = order.parent if isinstance(order, exp.Order) else None 3329 if not isinstance(ancestor, exp.Select): 3330 return None 3331 3332 column_name = this.name 3333 matched: list[exp.Expr] = [ 3334 p.this if isinstance(p, exp.Alias) else p 3335 for p in ancestor.selects 3336 if p.output_name == column_name 3337 ] 3338 match = matched[0] if len(matched) == 1 else None 3339 3340 # Skip the substitution when it would be identical to the existing 3341 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3342 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3343 return None 3344 3345 return match 3346 3347 def ordered_sql(self, expression: exp.Ordered) -> str: 3348 desc = expression.args.get("desc") 3349 asc = not desc 3350 3351 nulls_first = expression.args.get("nulls_first") 3352 nulls_last = not nulls_first 3353 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3354 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3355 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3356 3357 this = self.sql(expression, "this") 3358 3359 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3360 nulls_sort_change = "" 3361 if nulls_first and ( 3362 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3363 ): 3364 nulls_sort_change = " NULLS FIRST" 3365 elif ( 3366 nulls_last 3367 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3368 and not nulls_are_last 3369 ): 3370 nulls_sort_change = " NULLS LAST" 3371 3372 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3373 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3374 window = expression.find_ancestor(exp.Window, exp.Select) 3375 3376 if isinstance(window, exp.Window): 3377 window_this = window.this 3378 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3379 window_this = window_this.this 3380 spec = window.args.get("spec") 3381 else: 3382 window_this = None 3383 spec = None 3384 3385 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3386 # without a spec or with a ROWS spec, but not with RANGE 3387 if not ( 3388 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3389 and (not spec or spec.text("kind").upper() == "ROWS") 3390 ): 3391 if window_this and spec: 3392 self.unsupported( 3393 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3394 ) 3395 nulls_sort_change = "" 3396 elif self.NULL_ORDERING_SUPPORTED is False and ( 3397 (asc and nulls_sort_change == " NULLS LAST") 3398 or (desc and nulls_sort_change == " NULLS FIRST") 3399 ): 3400 # BigQuery does not allow these ordering/nulls combinations when used under 3401 # an aggregation func or under a window containing one 3402 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3403 3404 if isinstance(ancestor, exp.Window): 3405 ancestor = ancestor.this 3406 if isinstance(ancestor, exp.AggFunc): 3407 self.unsupported( 3408 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3409 ) 3410 nulls_sort_change = "" 3411 elif self.NULL_ORDERING_SUPPORTED is None: 3412 if expression.this.is_int: 3413 self.unsupported( 3414 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3415 ) 3416 elif not isinstance(expression.this, exp.Rand): 3417 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3418 target = self.sql(resolved) if resolved is not None else this 3419 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3420 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3421 nulls_sort_change = "" 3422 3423 with_fill = self.sql(expression, "with_fill") 3424 with_fill = f" {with_fill}" if with_fill else "" 3425 3426 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3427 3428 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3429 window_frame = self.sql(expression, "window_frame") 3430 window_frame = f"{window_frame} " if window_frame else "" 3431 3432 this = self.sql(expression, "this") 3433 3434 return f"{window_frame}{this}" 3435 3436 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3437 partition = self.partition_by_sql(expression) 3438 order = self.sql(expression, "order") 3439 measures = self.expressions(expression, key="measures") 3440 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3441 rows = self.sql(expression, "rows") 3442 rows = self.seg(rows) if rows else "" 3443 after = self.sql(expression, "after") 3444 after = self.seg(after) if after else "" 3445 pattern = self.sql(expression, "pattern") 3446 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3447 definition_sqls = [ 3448 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3449 for definition in expression.args.get("define", []) 3450 ] 3451 definitions = self.expressions(sqls=definition_sqls) 3452 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3453 body = "".join( 3454 ( 3455 partition, 3456 order, 3457 measures, 3458 rows, 3459 after, 3460 pattern, 3461 define, 3462 ) 3463 ) 3464 alias = self.sql(expression, "alias") 3465 alias = f" {alias}" if alias else "" 3466 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3467 3468 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3469 limit = expression.args.get("limit") 3470 3471 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3472 count = limit.args.get("count") 3473 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3474 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3475 limit = exp.Limit( 3476 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3477 ) 3478 elif ( 3479 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3480 ): 3481 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3482 3483 return csv( 3484 *sqls, 3485 *[self.sql(join) for join in expression.args.get("joins") or []], 3486 self.sql(expression, "match"), 3487 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3488 self.sql(expression, "prewhere"), 3489 self.sql(expression, "where"), 3490 self.sql(expression, "connect"), 3491 self.sql(expression, "group"), 3492 self.sql(expression, "having"), 3493 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3494 self.sql(expression, "order"), 3495 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3496 *self.after_limit_modifiers(expression), 3497 self.sql(expression, "for_"), 3498 self.options_modifier(expression), 3499 sep="", 3500 ) 3501 3502 def options_modifier(self, expression: exp.Expr) -> str: 3503 options = self.expressions(expression, key="options") 3504 return f" {options}" if options else "" 3505 3506 def forclause_sql(self, expression: exp.ForClause) -> str: 3507 kind = expression.args["kind"] 3508 if kind == "BROWSE": 3509 return f"{self.sep()}FOR BROWSE" 3510 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3511 # the target dialect doesn't support QueryOption, so we drop the clause. 3512 options = self.expressions(expression, key="expressions") 3513 if not options: 3514 return "" 3515 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3516 3517 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3518 self.unsupported("Unsupported query option.") 3519 return "" 3520 3521 def offset_limit_modifiers( 3522 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3523 ) -> list[str]: 3524 return [ 3525 self.sql(expression, "offset") if fetch else self.sql(limit), 3526 self.sql(limit) if fetch else self.sql(expression, "offset"), 3527 ] 3528 3529 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3530 locks = self.expressions(expression, key="locks", sep=" ") 3531 locks = f" {locks}" if locks else "" 3532 return [locks, self.sql(expression, "sample")] 3533 3534 def select_sql(self, expression: exp.Select) -> str: 3535 into = expression.args.get("into") 3536 if not self.SUPPORTS_SELECT_INTO and into: 3537 into.pop() 3538 3539 hint = self.sql(expression, "hint") 3540 distinct = self.sql(expression, "distinct") 3541 distinct = f" {distinct}" if distinct else "" 3542 kind = self.sql(expression, "kind") 3543 3544 limit = expression.args.get("limit") 3545 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3546 top = self.limit_sql(limit, top=True) 3547 limit.pop() 3548 else: 3549 top = "" 3550 3551 expressions = self.expressions(expression) 3552 3553 if kind: 3554 if kind in self.SELECT_KINDS: 3555 kind = f" AS {kind}" 3556 else: 3557 if kind == "STRUCT": 3558 expressions = self.expressions( 3559 sqls=[ 3560 self.sql( 3561 exp.Struct( 3562 expressions=[ 3563 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3564 if isinstance(e, exp.Alias) 3565 else e 3566 for e in expression.expressions 3567 ] 3568 ) 3569 ) 3570 ] 3571 ) 3572 kind = "" 3573 3574 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3575 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3576 3577 exclude = expression.args.get("exclude") 3578 3579 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3580 exclude_sql = self.expressions(sqls=exclude, flat=True) 3581 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3582 3583 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3584 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3585 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3586 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3587 sql = self.query_modifiers( 3588 expression, 3589 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3590 self.sql(expression, "into", comment=False), 3591 self.sql(expression, "from_", comment=False), 3592 ) 3593 3594 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3595 if expression.args.get("with_"): 3596 sql = self.maybe_comment(sql, expression) 3597 expression.pop_comments() 3598 3599 sql = self.prepend_ctes(expression, sql) 3600 3601 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3602 expression.set("exclude", None) 3603 subquery = expression.subquery(copy=False) 3604 star = exp.Star(except_=exclude) 3605 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3606 3607 if not self.SUPPORTS_SELECT_INTO and into: 3608 if into.args.get("temporary"): 3609 table_kind = " TEMPORARY" 3610 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3611 table_kind = " UNLOGGED" 3612 else: 3613 table_kind = "" 3614 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3615 3616 return sql 3617 3618 def schema_sql(self, expression: exp.Schema) -> str: 3619 this = self.sql(expression, "this") 3620 sql = self.schema_columns_sql(expression) 3621 return f"{this} {sql}" if this and sql else this or sql 3622 3623 def schema_columns_sql(self, expression: exp.Expr) -> str: 3624 if expression.expressions: 3625 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3626 return "" 3627 3628 def star_sql(self, expression: exp.Star) -> str: 3629 except_ = self.expressions(expression, key="except_", flat=True) 3630 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3631 replace = self.expressions(expression, key="replace", flat=True) 3632 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3633 rename = self.expressions(expression, key="rename", flat=True) 3634 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3635 ilike = self.sql(expression, "ilike") 3636 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3637 return f"*{ilike}{except_}{replace}{rename}" 3638 3639 def parameter_sql(self, expression: exp.Parameter) -> str: 3640 this = self.sql(expression, "this") 3641 return f"{self.PARAMETER_TOKEN}{this}" 3642 3643 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3644 this = self.sql(expression, "this") 3645 kind = expression.text("kind") 3646 if kind: 3647 kind = f"{kind}." 3648 return f"@@{kind}{this}" 3649 3650 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3651 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3652 3653 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3654 alias = self.sql(expression, "alias") 3655 alias = f"{sep}{alias}" if alias else "" 3656 sample = self.sql(expression, "sample") 3657 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3658 alias = f"{sample}{alias}" 3659 3660 # Set to None so it's not generated again by self.query_modifiers() 3661 expression.set("sample", None) 3662 3663 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3664 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3665 return self.prepend_ctes(expression, sql) 3666 3667 def qualify_sql(self, expression: exp.Qualify) -> str: 3668 this = self.indent(self.sql(expression, "this")) 3669 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3670 3671 def unnest_sql(self, expression: exp.Unnest) -> str: 3672 args = self.expressions(expression, flat=True) 3673 3674 alias = expression.args.get("alias") 3675 offset = expression.args.get("offset") 3676 3677 if self.UNNEST_WITH_ORDINALITY: 3678 if alias and isinstance(offset, exp.Expr): 3679 alias.append("columns", offset) 3680 expression.set("offset", None) 3681 3682 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3683 columns = alias.columns 3684 alias = self.sql(columns[0]) if columns else "" 3685 else: 3686 alias = self.sql(alias) 3687 3688 alias = f" AS {alias}" if alias else alias 3689 if self.UNNEST_WITH_ORDINALITY: 3690 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3691 else: 3692 if isinstance(offset, exp.Expr): 3693 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3694 elif offset: 3695 suffix = f"{alias} WITH OFFSET" 3696 else: 3697 suffix = alias 3698 3699 return f"UNNEST({args}){suffix}" 3700 3701 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3702 return "" 3703 3704 def where_sql(self, expression: exp.Where) -> str: 3705 this = self.indent(self.sql(expression, "this")) 3706 return f"{self.seg('WHERE')}{self.sep()}{this}" 3707 3708 def window_sql(self, expression: exp.Window) -> str: 3709 this = self.sql(expression, "this") 3710 partition = self.partition_by_sql(expression) 3711 order = expression.args.get("order") 3712 order = self.order_sql(order, flat=True) if order else "" 3713 spec = self.sql(expression, "spec") 3714 alias = self.sql(expression, "alias") 3715 over = self.sql(expression, "over") or "OVER" 3716 3717 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3718 3719 first = expression.args.get("first") 3720 if first is None: 3721 first = "" 3722 else: 3723 first = "FIRST" if first else "LAST" 3724 3725 if not partition and not order and not spec and alias: 3726 # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required. 3727 if expression.arg_key == "windows": 3728 return f"{this} ({alias})" 3729 return f"{this} {alias}" 3730 3731 args = self.format_args( 3732 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3733 ) 3734 return f"{this} ({args})" 3735 3736 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3737 partition = self.expressions(expression, key="partition_by", flat=True) 3738 return f"PARTITION BY {partition}" if partition else "" 3739 3740 def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str: 3741 return f"EXCLUDING {self.expressions(expression, flat=True)}" 3742 3743 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3744 kind = self.sql(expression, "kind") 3745 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3746 end = ( 3747 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3748 or "CURRENT ROW" 3749 ) 3750 3751 window_spec = f"{kind} BETWEEN {start} AND {end}" 3752 3753 exclude = self.sql(expression, "exclude") 3754 if exclude: 3755 if self.SUPPORTS_WINDOW_EXCLUDE: 3756 window_spec += f" EXCLUDE {exclude}" 3757 else: 3758 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3759 3760 return window_spec 3761 3762 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3763 this = self.sql(expression, "this") 3764 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3765 return f"{this} WITHIN GROUP ({expression_sql})" 3766 3767 def between_sql(self, expression: exp.Between) -> str: 3768 this = self.sql(expression, "this") 3769 low = self.sql(expression, "low") 3770 high = self.sql(expression, "high") 3771 symmetric = expression.args.get("symmetric") 3772 3773 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3774 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3775 3776 flag = ( 3777 " SYMMETRIC" 3778 if symmetric 3779 else " ASYMMETRIC" 3780 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3781 else "" # silently drop ASYMMETRIC – semantics identical 3782 ) 3783 return f"{this} BETWEEN{flag} {low} AND {high}" 3784 3785 def bracket_offset_expressions( 3786 self, expression: exp.Bracket, index_offset: int | None = None 3787 ) -> list[exp.Expr]: 3788 if expression.args.get("json_access"): 3789 return expression.expressions 3790 3791 return apply_index_offset( 3792 expression.this, 3793 expression.expressions, 3794 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3795 dialect=self.dialect, 3796 ) 3797 3798 def bracket_sql(self, expression: exp.Bracket) -> str: 3799 expressions = self.bracket_offset_expressions(expression) 3800 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3801 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3802 3803 def all_sql(self, expression: exp.All) -> str: 3804 this = self.sql(expression, "this") 3805 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3806 this = self.wrap(this) 3807 return f"ALL {this}" 3808 3809 def any_sql(self, expression: exp.Any) -> str: 3810 this = self.sql(expression, "this") 3811 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3812 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3813 this = self.wrap(this) 3814 return f"ANY{this}" 3815 return f"ANY {this}" 3816 3817 def exists_sql(self, expression: exp.Exists) -> str: 3818 return f"EXISTS{self.wrap(expression)}" 3819 3820 def case_sql(self, expression: exp.Case) -> str: 3821 this = self.sql(expression, "this") 3822 statements = [f"CASE {this}" if this else "CASE"] 3823 3824 for e in expression.args["ifs"]: 3825 statements.append(f"WHEN {self.sql(e, 'this')}") 3826 statements.append(f"THEN {self.sql(e, 'true')}") 3827 3828 default = self.sql(expression, "default") 3829 3830 if default: 3831 statements.append(f"ELSE {default}") 3832 3833 statements.append("END") 3834 3835 if self.pretty and self.too_wide(statements): 3836 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3837 3838 return " ".join(statements) 3839 3840 def constraint_sql(self, expression: exp.Constraint) -> str: 3841 this = self.sql(expression, "this") 3842 expressions = self.expressions(expression, flat=True) 3843 return f"CONSTRAINT {this} {expressions}" 3844 3845 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3846 order = expression.args.get("order") 3847 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3848 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3849 3850 def extract_sql(self, expression: exp.Extract) -> str: 3851 import sqlglot.dialects.dialect 3852 3853 this = ( 3854 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3855 if self.NORMALIZE_EXTRACT_DATE_PARTS 3856 else expression.this 3857 ) 3858 if self.EXTRACT_ALLOWS_QUOTES: 3859 this_sql = self.sql(this) 3860 elif isinstance(this, exp.WeekStart): 3861 this_sql = self.weekstart_name(this) 3862 else: 3863 this_sql = this.name 3864 expression_sql = self.sql(expression, "expression") 3865 3866 return f"EXTRACT({this_sql} FROM {expression_sql})" 3867 3868 def trim_sql(self, expression: exp.Trim) -> str: 3869 trim_type = self.sql(expression, "position") 3870 3871 if trim_type == "LEADING": 3872 func_name = "LTRIM" 3873 elif trim_type == "TRAILING": 3874 func_name = "RTRIM" 3875 else: 3876 func_name = "TRIM" 3877 3878 return self.func(func_name, expression.this, expression.expression) 3879 3880 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3881 args = expression.expressions 3882 if isinstance(expression, exp.ConcatWs): 3883 args = args[1:] # Skip the delimiter 3884 3885 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3886 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3887 3888 concat_coalesce = ( 3889 self.dialect.CONCAT_WS_COALESCE 3890 if isinstance(expression, exp.ConcatWs) 3891 else self.dialect.CONCAT_COALESCE 3892 ) 3893 3894 if not concat_coalesce and expression.args.get("coalesce"): 3895 3896 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3897 if not e.type: 3898 import sqlglot.optimizer.annotate_types 3899 3900 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3901 3902 if e.is_string or e.is_type(exp.DType.ARRAY): 3903 return e 3904 3905 return exp.func("coalesce", e, exp.Literal.string("")) 3906 3907 args = [_wrap_with_coalesce(e) for e in args] 3908 3909 return args 3910 3911 def concat_sql(self, expression: exp.Concat) -> str: 3912 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3913 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3914 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3915 # instead of coalescing them to empty string. 3916 import sqlglot.dialects.dialect 3917 3918 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3919 3920 expressions = self.convert_concat_args(expression) 3921 3922 # Some dialects don't allow a single-argument CONCAT call 3923 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3924 return self.sql(expressions[0]) 3925 3926 return self.func("CONCAT", *expressions) 3927 3928 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3929 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3930 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3931 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3932 all_args = expression.expressions 3933 expression.set("coalesce", True) 3934 return self.sql( 3935 exp.case() 3936 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3937 .else_(expression) 3938 ) 3939 3940 return self.func( 3941 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3942 ) 3943 3944 def check_sql(self, expression: exp.Check) -> str: 3945 this = self.sql(expression, key="this") 3946 return f"CHECK ({this})" 3947 3948 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3949 this = self.sql(expression, "this") 3950 this = f" {this}" if this else "" 3951 expressions = self.expressions(expression, flat=True) 3952 expressions = f" ({expressions})" if expressions else "" 3953 reference = self.sql(expression, "reference") 3954 reference = f" {reference}" if reference else "" 3955 delete = self.sql(expression, "delete") 3956 delete = f" ON DELETE {delete}" if delete else "" 3957 update = self.sql(expression, "update") 3958 update = f" ON UPDATE {update}" if update else "" 3959 options = self.expressions(expression, key="options", flat=True, sep=" ") 3960 options = f" {options}" if options else "" 3961 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}" 3962 3963 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3964 this = self.sql(expression, "this") 3965 this = f" {this}" if this else "" 3966 expressions = self.expressions(expression, flat=True) 3967 include = self.sql(expression, "include") 3968 options = self.expressions(expression, key="options", flat=True, sep=" ") 3969 options = f" {options}" if options else "" 3970 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3971 3972 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3973 self.unsupported("TIMESERIES primary key columns are not supported") 3974 return self.sql(expression, "this") 3975 3976 def if_sql(self, expression: exp.If) -> str: 3977 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3978 3979 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3980 if self.MATCH_AGAINST_TABLE_PREFIX: 3981 expressions = [] 3982 for expr in expression.expressions: 3983 if isinstance(expr, exp.Table): 3984 expressions.append(f"TABLE {self.sql(expr)}") 3985 else: 3986 expressions.append(expr) 3987 else: 3988 expressions = expression.expressions 3989 3990 modifier = expression.args.get("modifier") 3991 modifier = f" {modifier}" if modifier else "" 3992 return ( 3993 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3994 ) 3995 3996 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3997 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3998 3999 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 4000 path = self.expressions(expression, sep="", flat=True).lstrip(".") 4001 4002 if self.QUOTE_JSON_PATH: 4003 path = self.escape_str(path) 4004 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 4005 4006 return path 4007 4008 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 4009 if isinstance(expression, exp.JSONPathPart): 4010 transform = self.TRANSFORMS.get(expression.__class__) 4011 if not callable(transform): 4012 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 4013 return "" 4014 4015 return transform(self, expression) 4016 4017 if isinstance(expression, int): 4018 return str(expression) 4019 4020 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 4021 escaped = expression.replace("'", "\\'") 4022 escaped = f"'{escaped}'" 4023 else: 4024 escaped = expression.replace('"', '\\"') 4025 escaped = f'"{escaped}"' 4026 4027 return escaped 4028 4029 def formatjson_sql(self, expression: exp.FormatJson) -> str: 4030 return f"{self.sql(expression, 'this')} FORMAT JSON" 4031 4032 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 4033 # Output the Teradata column FORMAT override. 4034 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 4035 this = self.sql(expression, "this") 4036 fmt = self.sql(expression, "format") 4037 return f"{this} (FORMAT {fmt})" 4038 4039 def _jsonobject_sql( 4040 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 4041 ) -> str: 4042 null_handling = expression.args.get("null_handling") 4043 null_handling = f" {null_handling}" if null_handling else "" 4044 4045 unique_keys = expression.args.get("unique_keys") 4046 if unique_keys is not None: 4047 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 4048 else: 4049 unique_keys = "" 4050 4051 return_type = self.sql(expression, "return_type") 4052 return_type = f" RETURNING {return_type}" if return_type else "" 4053 encoding = self.sql(expression, "encoding") 4054 encoding = f" ENCODING {encoding}" if encoding else "" 4055 4056 if not name: 4057 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 4058 4059 return self.func( 4060 name, 4061 *expression.expressions, 4062 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 4063 ) 4064 4065 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 4066 null_handling = expression.args.get("null_handling") 4067 null_handling = f" {null_handling}" if null_handling else "" 4068 return_type = self.sql(expression, "return_type") 4069 return_type = f" RETURNING {return_type}" if return_type else "" 4070 strict = " STRICT" if expression.args.get("strict") else "" 4071 return self.func( 4072 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 4073 ) 4074 4075 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 4076 this = self.sql(expression, "this") 4077 order = self.sql(expression, "order") 4078 null_handling = expression.args.get("null_handling") 4079 null_handling = f" {null_handling}" if null_handling else "" 4080 return_type = self.sql(expression, "return_type") 4081 return_type = f" RETURNING {return_type}" if return_type else "" 4082 strict = " STRICT" if expression.args.get("strict") else "" 4083 return self.func( 4084 "JSON_ARRAYAGG", 4085 this, 4086 suffix=f"{order}{null_handling}{return_type}{strict})", 4087 ) 4088 4089 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 4090 path = self.sql(expression, "path") 4091 path = f" PATH {path}" if path else "" 4092 nested_schema = self.sql(expression, "nested_schema") 4093 4094 if nested_schema: 4095 return f"NESTED{path} {nested_schema}" 4096 4097 this = self.sql(expression, "this") 4098 kind = self.sql(expression, "kind") 4099 kind = f" {kind}" if kind else "" 4100 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4101 4102 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4103 return f"{this}{kind}{format_json}{path}{ordinality}" 4104 4105 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 4106 return self.func("COLUMNS", *expression.expressions) 4107 4108 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4109 this = self.sql(expression, "this") 4110 path = self.sql(expression, "path") 4111 path = f", {path}" if path else "" 4112 error_handling = expression.args.get("error_handling") 4113 error_handling = f" {error_handling}" if error_handling else "" 4114 empty_handling = expression.args.get("empty_handling") 4115 empty_handling = f" {empty_handling}" if empty_handling else "" 4116 schema = self.sql(expression, "schema") 4117 return self.func( 4118 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4119 ) 4120 4121 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4122 this = self.sql(expression, "this") 4123 kind = self.sql(expression, "kind") 4124 path = self.sql(expression, "path") 4125 path = f" {path}" if path else "" 4126 as_json = " AS JSON" if expression.args.get("as_json") else "" 4127 return f"{this} {kind}{path}{as_json}" 4128 4129 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4130 this = self.sql(expression, "this") 4131 path = self.sql(expression, "path") 4132 path = f", {path}" if path else "" 4133 expressions = self.expressions(expression) 4134 with_ = ( 4135 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4136 if expressions 4137 else "" 4138 ) 4139 return f"OPENJSON({this}{path}){with_}" 4140 4141 def in_sql(self, expression: exp.In) -> str: 4142 query = expression.args.get("query") 4143 unnest = expression.args.get("unnest") 4144 field = expression.args.get("field") 4145 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4146 4147 if query: 4148 in_sql = self.sql(query) 4149 elif unnest: 4150 in_sql = self.in_unnest_op(unnest) 4151 elif field: 4152 in_sql = self.sql(field) 4153 else: 4154 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4155 4156 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4157 4158 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4159 return f"(SELECT {self.sql(unnest)})" 4160 4161 def interval_sql(self, expression: exp.Interval) -> str: 4162 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4163 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4164 exp.AutoRefreshProperty, 4165 ) 4166 interval_keyword = "INTERVAL" if include_keyword else "" 4167 unit_expression = expression.args.get("unit") 4168 unit = self.sql(unit_expression) if unit_expression else "" 4169 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4170 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4171 unit = f" {unit}" if unit else "" 4172 4173 if self.SINGLE_STRING_INTERVAL: 4174 this = expression.this.name if expression.this else "" 4175 if this: 4176 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4177 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4178 return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}" 4179 return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}" 4180 return f"{interval_keyword}{unit}" 4181 4182 this = self.sql(expression, "this") 4183 if this: 4184 if not include_keyword and expression.this.is_string: 4185 this = expression.this.name 4186 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4187 this = f"({this})" 4188 if include_keyword: 4189 this = f" {this}" 4190 4191 return f"{interval_keyword}{this}{unit}" 4192 4193 def return_sql(self, expression: exp.Return) -> str: 4194 return f"RETURN {self.sql(expression, 'this')}" 4195 4196 def reference_sql(self, expression: exp.Reference) -> str: 4197 this = self.sql(expression, "this") 4198 expressions = self.expressions(expression, flat=True) 4199 expressions = f"({expressions})" if expressions else "" 4200 options = self.expressions(expression, key="options", flat=True, sep=" ") 4201 options = f" {options}" if options else "" 4202 return f"REFERENCES {this}{expressions}{options}" 4203 4204 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4205 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4206 parent = expression.parent 4207 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4208 4209 return self.func( 4210 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4211 ) 4212 4213 def paren_sql(self, expression: exp.Paren) -> str: 4214 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4215 return f"({sql}{self.seg(')', sep='')}" 4216 4217 def neg_sql(self, expression: exp.Neg) -> str: 4218 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4219 this_sql = self.sql(expression, "this") 4220 sep = " " if this_sql[0] == "-" else "" 4221 return f"-{sep}{this_sql}" 4222 4223 def not_sql(self, expression: exp.Not) -> str: 4224 return f"NOT {self.sql(expression, 'this')}" 4225 4226 def alias_sql(self, expression: exp.Alias) -> str: 4227 alias = self.sql(expression, "alias") 4228 alias = f" AS {alias}" if alias else "" 4229 return f"{self.sql(expression, 'this')}{alias}" 4230 4231 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4232 alias = expression.args["alias"] 4233 4234 parent = expression.parent 4235 pivot = parent and parent.parent 4236 4237 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4238 identifier_alias = isinstance(alias, exp.Identifier) 4239 literal_alias = isinstance(alias, exp.Literal) 4240 4241 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4242 alias.replace(exp.Literal.string(alias.output_name)) 4243 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4244 alias.replace(exp.to_identifier(alias.output_name)) 4245 4246 return self.alias_sql(expression) 4247 4248 def aliases_sql(self, expression: exp.Aliases) -> str: 4249 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4250 4251 def atindex_sql(self, expression: exp.AtIndex) -> str: 4252 this = self.sql(expression, "this") 4253 index = self.sql(expression, "expression") 4254 return f"{this} AT {index}" 4255 4256 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4257 this = self.sql(expression, "this") 4258 zone = self.sql(expression, "zone") 4259 return f"{this} AT TIME ZONE {zone}" 4260 4261 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4262 this = self.sql(expression, "this") 4263 zone = self.sql(expression, "zone") 4264 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4265 4266 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4267 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4268 4269 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4270 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4271 4272 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4273 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4274 4275 def add_sql(self, expression: exp.Add) -> str: 4276 return self.binary(expression, "+") 4277 4278 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4279 return self.connector_sql(expression, "AND", stack) 4280 4281 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4282 return self.connector_sql(expression, "OR", stack) 4283 4284 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4285 return self.connector_sql(expression, "XOR", stack) 4286 4287 def connector_sql( 4288 self, 4289 expression: exp.Connector, 4290 op: str, 4291 stack: list[str | exp.Expr] | None = None, 4292 ) -> str: 4293 if stack is not None: 4294 stack.append(expression.right) 4295 if expression.comments and self.comments: 4296 op = self.maybe_comment(op, comments=expression.comments) 4297 4298 stack.extend((op, expression.left)) 4299 return op 4300 4301 stack = [expression] 4302 sqls: list[str] = [] 4303 ops = set() 4304 4305 while stack: 4306 node = stack.pop() 4307 if isinstance(node, exp.Connector): 4308 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4309 else: 4310 sql = self.sql(node) 4311 if sqls and sqls[-1] in ops: 4312 sqls[-1] += f" {sql}" 4313 else: 4314 sqls.append(sql) 4315 4316 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4317 return sep.join(sqls) 4318 4319 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4320 return self.binary(expression, "&") 4321 4322 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4323 return self.binary(expression, "<<") 4324 4325 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4326 return f"~{self.sql(expression, 'this')}" 4327 4328 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4329 return self.binary(expression, "|") 4330 4331 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4332 return self.binary(expression, ">>") 4333 4334 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4335 return self.binary(expression, "^") 4336 4337 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4338 format_sql = self.sql(expression, "format") 4339 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4340 to_sql = self.sql(expression, "to") 4341 to_sql = f" {to_sql}" if to_sql else "" 4342 action = self.sql(expression, "action") 4343 action = f" {action}" if action else "" 4344 default = self.sql(expression, "default") 4345 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4346 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4347 4348 # Base implementation that excludes safe, zone, and target_type metadata args 4349 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4350 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4351 4352 # Base implementation that excludes the safe and default_year metadata args 4353 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4354 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4355 4356 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4357 return self.func( 4358 "PARSE_DATETIME", 4359 expression.this, 4360 expression.args.get("format"), 4361 expression.args.get("zone"), 4362 ) 4363 4364 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4365 zone = self.sql(expression, "this") 4366 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4367 4368 def collate_sql(self, expression: exp.Collate) -> str: 4369 if self.COLLATE_IS_FUNC: 4370 return self.function_fallback_sql(expression) 4371 return self.binary(expression, "COLLATE") 4372 4373 def command_sql(self, expression: exp.Command) -> str: 4374 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4375 4376 def comment_sql(self, expression: exp.Comment) -> str: 4377 this = self.sql(expression, "this") 4378 kind = expression.args["kind"] 4379 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4380 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4381 expression_sql = self.sql(expression, "expression") 4382 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4383 4384 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4385 this = self.sql(expression, "this") 4386 delete = " DELETE" if expression.args.get("delete") else "" 4387 recompress = self.sql(expression, "recompress") 4388 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4389 to_disk = self.sql(expression, "to_disk") 4390 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4391 to_volume = self.sql(expression, "to_volume") 4392 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4393 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4394 4395 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4396 where = self.sql(expression, "where") 4397 group = self.sql(expression, "group") 4398 aggregates = self.expressions(expression, key="aggregates") 4399 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4400 4401 if not (where or group or aggregates) and len(expression.expressions) == 1: 4402 return f"TTL {self.expressions(expression, flat=True)}" 4403 4404 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4405 4406 def transaction_sql(self, expression: exp.Transaction) -> str: 4407 modes = self.expressions(expression, key="modes") 4408 modes = f" {modes}" if modes else "" 4409 return f"BEGIN{modes}" 4410 4411 def commit_sql(self, expression: exp.Commit) -> str: 4412 chain = expression.args.get("chain") 4413 if chain is not None: 4414 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4415 4416 return f"COMMIT{chain or ''}" 4417 4418 def rollback_sql(self, expression: exp.Rollback) -> str: 4419 savepoint = expression.args.get("savepoint") 4420 savepoint = f" TO {savepoint}" if savepoint else "" 4421 return f"ROLLBACK{savepoint}" 4422 4423 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4424 this = self.sql(expression, "this") 4425 4426 exists = "" 4427 if expression.args.get("exists"): 4428 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4429 exists = " IF EXISTS" 4430 else: 4431 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4432 4433 dtype = self.sql(expression, "dtype") 4434 if dtype: 4435 collate = self.sql(expression, "collate") 4436 collate = f" COLLATE {collate}" if collate else "" 4437 using = self.sql(expression, "using") 4438 using = f" USING {using}" if using else "" 4439 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4440 null_constraint = self._alter_column_null_constraint_sql(expression) 4441 4442 return ( 4443 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4444 f"{collate}{using}{null_constraint}" 4445 ) 4446 4447 default = self.sql(expression, "default") 4448 if default: 4449 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4450 4451 comment = self.sql(expression, "comment") 4452 if comment: 4453 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4454 4455 visible = expression.args.get("visible") 4456 if visible: 4457 return f"ALTER COLUMN{exists} {this} SET {visible}" 4458 4459 allow_null = expression.args.get("allow_null") 4460 drop = expression.args.get("drop") 4461 4462 if not drop and not allow_null: 4463 self.unsupported("Unsupported ALTER COLUMN syntax") 4464 4465 if allow_null is not None: 4466 keyword = "DROP" if drop else "SET" 4467 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4468 4469 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4470 4471 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4472 allow_null = expression.args.get("allow_null") 4473 if allow_null is None: 4474 return "" 4475 4476 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4477 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4478 return "" 4479 4480 return " NULL" if allow_null else " NOT NULL" 4481 4482 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4483 this = self.sql(expression, "this") 4484 rename_from = self.sql(expression, "rename_from") 4485 if rename_from: 4486 if not self.SUPPORTS_CHANGE_COLUMN: 4487 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4488 return f"CHANGE COLUMN {rename_from} {this}" 4489 if not self.SUPPORTS_MODIFY_COLUMN: 4490 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4491 return f"MODIFY COLUMN {this}" 4492 4493 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4494 this = self.sql(expression, "this") 4495 4496 visible = expression.args.get("visible") 4497 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4498 4499 return f"ALTER INDEX {this} {visible_sql}" 4500 4501 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4502 this = self.sql(expression, "this") 4503 if not isinstance(expression.this, exp.Var): 4504 this = f"KEY DISTKEY {this}" 4505 return f"ALTER DISTSTYLE {this}" 4506 4507 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4508 compound = " COMPOUND" if expression.args.get("compound") else "" 4509 this = self.sql(expression, "this") 4510 expressions = self.expressions(expression, flat=True) 4511 expressions = f"({expressions})" if expressions else "" 4512 return f"ALTER{compound} SORTKEY {this or expressions}" 4513 4514 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4515 if not self.RENAME_TABLE_WITH_DB: 4516 # Remove db from tables 4517 expression = expression.transform( 4518 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4519 ).assert_is(exp.AlterRename) 4520 this = self.sql(expression, "this") 4521 to_kw = " TO" if include_to else "" 4522 return f"RENAME{to_kw} {this}" 4523 4524 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4525 exists = " IF EXISTS" if expression.args.get("exists") else "" 4526 old_column = self.sql(expression, "this") 4527 new_column = self.sql(expression, "to") 4528 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4529 4530 def alterset_sql(self, expression: exp.AlterSet) -> str: 4531 exprs = self.expressions(expression, flat=True) 4532 if self.ALTER_SET_WRAPPED: 4533 exprs = f"({exprs})" 4534 4535 return f"SET {exprs}" 4536 4537 def alter_sql(self, expression: exp.Alter) -> str: 4538 actions = expression.actions 4539 4540 if ( 4541 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4542 and actions 4543 and isinstance(actions[0], exp.ColumnDef) 4544 ): 4545 actions_sql = self.expressions(expression, key="actions", flat=True) 4546 actions_sql = f"ADD {actions_sql}" 4547 else: 4548 actions_list = [] 4549 for action in actions: 4550 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4551 action_sql = self.add_column_sql(action) 4552 else: 4553 action_sql = self.sql(action) 4554 if isinstance(action, exp.Query): 4555 action_sql = f"AS {action_sql}" 4556 4557 actions_list.append(action_sql) 4558 4559 actions_sql = self.format_args(*actions_list).lstrip("\n") 4560 4561 iceberg = ( 4562 "ICEBERG " 4563 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4564 else "" 4565 ) 4566 exists = " IF EXISTS" if expression.args.get("exists") else "" 4567 on_cluster = self.sql(expression, "cluster") 4568 on_cluster = f" {on_cluster}" if on_cluster else "" 4569 only = " ONLY" if expression.args.get("only") else "" 4570 options = expression.args.get("options") or [] 4571 options_sql = self.expressions( 4572 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4573 ) 4574 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4575 partitions_sql = self.expressions( 4576 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4577 sep=" ", 4578 flat=True, 4579 ) 4580 if partitions_sql and (actions_sql or options_sql): 4581 partitions_sql = f" {partitions_sql}" 4582 kind = self.sql(expression, "kind") 4583 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4584 check = " WITH CHECK" if expression.args.get("check") else "" 4585 cascade = ( 4586 " CASCADE" 4587 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4588 else "" 4589 ) 4590 this = self.sql(expression, "this") 4591 this = f" {this}" if this else "" 4592 4593 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4594 4595 def altersession_sql(self, expression: exp.AlterSession) -> str: 4596 items_sql = self.expressions(expression, flat=True) 4597 keyword = "UNSET" if expression.args.get("unset") else "SET" 4598 return f"{keyword} {items_sql}" 4599 4600 def add_column_sql(self, expression: exp.Expr) -> str: 4601 column_text = "" 4602 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4603 if isinstance(expression, exp.ColumnDef): 4604 column_text = " COLUMN" 4605 elif isinstance(expression, exp.Schema): 4606 column_text = " COLUMNS" 4607 4608 if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED: 4609 sql = self.expressions(expression, flat=True) 4610 else: 4611 sql = self.sql(expression) 4612 4613 return f"ADD{column_text} {sql}" 4614 4615 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4616 expressions = self.expressions(expression) 4617 exists = " IF EXISTS " if expression.args.get("exists") else " " 4618 return f"DROP{exists}{expressions}" 4619 4620 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4621 return "DROP PRIMARY KEY" 4622 4623 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4624 return f"ADD {self.expressions(expression, indent=False)}" 4625 4626 def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str: 4627 return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})" 4628 4629 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4630 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4631 location = self.sql(expression, "location") 4632 location = f" {location}" if location else "" 4633 return f"ADD {exists}{self.sql(expression.this)}{location}" 4634 4635 def distinct_sql(self, expression: exp.Distinct) -> str: 4636 this = self.expressions(expression, flat=True) 4637 4638 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4639 case = exp.case() 4640 for arg in expression.expressions: 4641 case = case.when(arg.is_(exp.null()), exp.null()) 4642 this = self.sql(case.else_(f"({this})")) 4643 4644 this = f" {this}" if this else "" 4645 4646 on = self.sql(expression, "on") 4647 on = f" ON {on}" if on else "" 4648 return f"DISTINCT{this}{on}" 4649 4650 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4651 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4652 4653 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4654 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4655 4656 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4657 this_sql = self.sql(expression, "this") 4658 expression_sql = self.sql(expression, "expression") 4659 kind = "MAX" if expression.args.get("max") else "MIN" 4660 return f"{this_sql} HAVING {kind} {expression_sql}" 4661 4662 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4663 return self._truncated_div_sql(expression.this, expression.expression) 4664 4665 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4666 div: exp.Expr = exp.Div(this=this, expression=divisor) 4667 if self.CAST_TO_INT_ROUNDS: 4668 div = exp.Trunc(this=div) 4669 4670 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4671 4672 def dpipe_sql(self, expression: exp.DPipe) -> str: 4673 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4674 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4675 return self.binary(expression, "||") 4676 4677 def div_sql(self, expression: exp.Div) -> str: 4678 l, r = expression.left, expression.right 4679 4680 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4681 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4682 4683 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4684 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4685 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4686 4687 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4688 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4689 # Native integer division depends on the target's operand types, which may differ 4690 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4691 # integer literals or casts 4692 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4693 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4694 ): 4695 return self.sql(exp.IntDiv(this=l, expression=r)) 4696 return self._truncated_div_sql(l, r) 4697 4698 return self.binary(expression, "/") 4699 4700 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4701 n = exp._wrap(expression.this, exp.Binary) 4702 d = exp._wrap(expression.expression, exp.Binary) 4703 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4704 4705 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4706 return self.binary(expression, "OVERLAPS") 4707 4708 def distance_sql(self, expression: exp.Distance) -> str: 4709 return self.binary(expression, "<->") 4710 4711 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4712 return self.binary(expression, "<<->>") 4713 4714 def dot_sql(self, expression: exp.Dot) -> str: 4715 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4716 4717 def eq_sql(self, expression: exp.EQ) -> str: 4718 return self.binary(expression, "=") 4719 4720 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4721 return self.binary(expression, ":=") 4722 4723 def escape_sql(self, expression: exp.Escape) -> str: 4724 this = expression.this 4725 if ( 4726 isinstance(this, (exp.Like, exp.ILike)) 4727 and isinstance(this.expression, (exp.All, exp.Any)) 4728 and not self.SUPPORTS_LIKE_QUANTIFIERS 4729 ): 4730 return self._like_sql(this, escape=expression) 4731 return self.binary(expression, "ESCAPE") 4732 4733 def glob_sql(self, expression: exp.Glob) -> str: 4734 return self.binary(expression, "GLOB") 4735 4736 def gt_sql(self, expression: exp.GT) -> str: 4737 return self.binary(expression, ">") 4738 4739 def gte_sql(self, expression: exp.GTE) -> str: 4740 return self.binary(expression, ">=") 4741 4742 def is_sql(self, expression: exp.Is) -> str: 4743 negate = expression.args.get("negate") 4744 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4745 positive = bool(expression.expression.this) != bool(negate) 4746 return self.sql(expression.this if positive else exp.not_(expression.this)) 4747 return self.binary(expression, "IS NOT" if negate else "IS") 4748 4749 def _like_sql( 4750 self, 4751 expression: exp.Like | exp.ILike, 4752 escape: exp.Escape | None = None, 4753 ) -> str: 4754 this = expression.this 4755 rhs = expression.expression 4756 4757 if isinstance(expression, exp.Like): 4758 exp_class: type[exp.Like | exp.ILike] = exp.Like 4759 op = "LIKE" 4760 else: 4761 exp_class = exp.ILike 4762 op = "ILIKE" 4763 4764 if expression.args.get("negate"): 4765 op = f"NOT {op}" 4766 4767 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4768 exprs = rhs.this.unnest() 4769 4770 if isinstance(exprs, exp.Tuple): 4771 exprs = exprs.expressions 4772 else: 4773 exprs = [exprs] 4774 4775 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4776 4777 def _make_like(expr: exp.Expression) -> exp.Expression: 4778 like: exp.Expression = exp_class( 4779 this=this, expression=expr, negate=expression.args.get("negate") 4780 ) 4781 if escape: 4782 like = exp.Escape(this=like, expression=escape.expression.copy()) 4783 return like 4784 4785 like_expr: exp.Expr = _make_like(exprs[0]) 4786 for expr in exprs[1:]: 4787 like_expr = connective(like_expr, _make_like(expr), copy=False) 4788 4789 parent = escape.parent if escape else expression.parent 4790 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4791 parent, exp.Condition 4792 ): 4793 like_expr = exp.paren(like_expr, copy=False) 4794 4795 return self.sql(like_expr) 4796 4797 return self.binary(expression, op) 4798 4799 def like_sql(self, expression: exp.Like) -> str: 4800 return self._like_sql(expression) 4801 4802 def ilike_sql(self, expression: exp.ILike) -> str: 4803 return self._like_sql(expression) 4804 4805 def match_sql(self, expression: exp.Match) -> str: 4806 if expression.args.get("kind") or expression.args.get("analyzer"): 4807 self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect") 4808 return self.binary(expression, "MATCH") 4809 4810 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4811 return self.binary(expression, "SIMILAR TO") 4812 4813 def lt_sql(self, expression: exp.LT) -> str: 4814 return self.binary(expression, "<") 4815 4816 def lte_sql(self, expression: exp.LTE) -> str: 4817 return self.binary(expression, "<=") 4818 4819 def mod_sql(self, expression: exp.Mod) -> str: 4820 this = self.sql(expression, "this") 4821 expr = self.sql(expression, "expression") 4822 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4823 4824 parent = expression.parent 4825 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4826 return f"({sql})" 4827 4828 return sql 4829 4830 def mul_sql(self, expression: exp.Mul) -> str: 4831 return self.binary(expression, "*") 4832 4833 def neq_sql(self, expression: exp.NEQ) -> str: 4834 return self.binary(expression, "<>") 4835 4836 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4837 return self.binary(expression, "IS NOT DISTINCT FROM") 4838 4839 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4840 return self.binary(expression, "IS DISTINCT FROM") 4841 4842 def sub_sql(self, expression: exp.Sub) -> str: 4843 return self.binary(expression, "-") 4844 4845 def trycast_sql(self, expression: exp.TryCast) -> str: 4846 return self.cast_sql(expression, safe_prefix="TRY_") 4847 4848 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4849 return self.cast_sql(expression) 4850 4851 def try_sql(self, expression: exp.Try) -> str: 4852 if not self.TRY_SUPPORTED: 4853 self.unsupported("Unsupported TRY function") 4854 return self.sql(expression, "this") 4855 4856 return self.func("TRY", expression.this) 4857 4858 def log_sql(self, expression: exp.Log) -> str: 4859 this = expression.this 4860 expr = expression.expression 4861 4862 if self.dialect.LOG_BASE_FIRST is False: 4863 this, expr = expr, this 4864 elif self.dialect.LOG_BASE_FIRST is None and expr: 4865 if this.name in ("2", "10"): 4866 return self.func(f"LOG{this.name}", expr) 4867 4868 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4869 4870 return self.func("LOG", this, expr) 4871 4872 def use_sql(self, expression: exp.Use) -> str: 4873 kind = self.sql(expression, "kind") 4874 kind = f" {kind}" if kind else "" 4875 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4876 this = f" {this}" if this else "" 4877 return f"USE{kind}{this}" 4878 4879 def binary(self, expression: exp.Binary, op: str) -> str: 4880 sqls: list[str] = [] 4881 stack: list[None | str | exp.Expr] = [expression] 4882 binary_type = type(expression) 4883 is_is = binary_type is exp.Is and op in ("IS", "IS NOT") 4884 negate = expression.args.get("negate") 4885 4886 while stack: 4887 node = stack.pop() 4888 4889 if type(node) is binary_type and ( 4890 node is expression 4891 or (is_is and not isinstance(node.expression, exp.Boolean)) 4892 or node.args.get("negate") == negate 4893 ): 4894 op_func = node.args.get("operator") 4895 if op_func: 4896 op = f"OPERATOR({self.sql(op_func)})" 4897 elif is_is: 4898 op = "IS NOT" if node.args.get("negate") else "IS" 4899 4900 stack.append(node.args.get("expression")) 4901 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4902 stack.append(node.args.get("this")) 4903 else: 4904 sqls.append(self.sql(node)) 4905 4906 return "".join(sqls) 4907 4908 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4909 to_clause = self.sql(expression, "to") 4910 if to_clause: 4911 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4912 4913 return self.function_fallback_sql(expression) 4914 4915 def function_fallback_sql(self, expression: exp.Func) -> str: 4916 args = [] 4917 4918 for key in expression.arg_types: 4919 arg_value = expression.args.get(key) 4920 4921 if isinstance(arg_value, list): 4922 for value in arg_value: 4923 args.append(value) 4924 elif arg_value is not None: 4925 args.append(arg_value) 4926 4927 return self.func(self.function_name(expression), *args) 4928 4929 def function_name(self, expression: exp.Func) -> str: 4930 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4931 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4932 4933 def func( 4934 self, 4935 name: str, 4936 *args: t.Any, 4937 prefix: str = "(", 4938 suffix: str = ")", 4939 normalize: bool = True, 4940 ) -> str: 4941 name = self.normalize_func(name) if normalize else name 4942 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4943 4944 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4945 arg_sqls = tuple( 4946 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4947 ) 4948 if self.pretty and self.too_wide(arg_sqls): 4949 return self.indent( 4950 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4951 ) 4952 return sep.join(arg_sqls) 4953 4954 def too_wide(self, args: t.Iterable) -> bool: 4955 return sum(len(arg) for arg in args) > self.max_text_width 4956 4957 def format_time( 4958 self, 4959 expression: exp.Expr, 4960 inverse_time_mapping: dict[str, str] | None = None, 4961 inverse_time_trie: dict | None = None, 4962 ) -> str | None: 4963 return format_time( 4964 self.sql(expression, "format"), 4965 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4966 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4967 ) 4968 4969 def expressions( 4970 self, 4971 expression: exp.Expr | None = None, 4972 key: str | None = None, 4973 sqls: t.Collection[str | exp.Expr] | None = None, 4974 flat: bool = False, 4975 indent: bool = True, 4976 skip_first: bool = False, 4977 skip_last: bool = False, 4978 sep: str = ", ", 4979 prefix: str = "", 4980 dynamic: bool = False, 4981 new_line: bool = False, 4982 ) -> str: 4983 expressions = expression.args.get(key or "expressions") if expression else sqls 4984 4985 if not expressions: 4986 return "" 4987 4988 if flat: 4989 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4990 4991 num_sqls = len(expressions) 4992 result_sqls = [] 4993 4994 for i, e in enumerate(expressions): 4995 sql = self.sql(e, comment=False) 4996 if not sql: 4997 continue 4998 4999 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 5000 5001 if self.pretty: 5002 if self.leading_comma: 5003 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 5004 else: 5005 result_sqls.append( 5006 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 5007 ) 5008 else: 5009 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 5010 5011 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 5012 if new_line: 5013 result_sqls.insert(0, "") 5014 result_sqls.append("") 5015 result_sql = "\n".join(s.rstrip() for s in result_sqls) 5016 else: 5017 result_sql = "".join(result_sqls) 5018 5019 return ( 5020 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 5021 if indent 5022 else result_sql 5023 ) 5024 5025 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 5026 flat = flat or isinstance(expression.parent, exp.Properties) 5027 expressions_sql = self.expressions(expression, flat=flat) 5028 if flat: 5029 return f"{op} {expressions_sql}" 5030 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 5031 5032 def naked_property(self, expression: exp.Property) -> str: 5033 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 5034 if not property_name: 5035 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 5036 return f"{property_name} {self.sql(expression, 'this')}" 5037 5038 def tag_sql(self, expression: exp.Tag) -> str: 5039 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 5040 5041 def token_sql(self, token_type: TokenType) -> str: 5042 return self.TOKEN_MAPPING.get(token_type, token_type.name) 5043 5044 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 5045 this = self.sql(expression, "this") 5046 expressions = self.no_identify(self.expressions, expression) 5047 expressions = ( 5048 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 5049 ) 5050 return f"{this}{expressions}" if expressions.strip() != "" else this 5051 5052 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 5053 return self.expressions(expression, flat=True) 5054 5055 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 5056 params = self.no_identify(self.expressions, expression, flat=True) 5057 body = self.sql(expression, "this") 5058 prefix = "TABLE " if expression.args.get("is_table") else "" 5059 return f"({params}) AS {prefix}{body}" 5060 5061 def joinhint_sql(self, expression: exp.JoinHint) -> str: 5062 this = self.sql(expression, "this") 5063 expressions = self.expressions(expression, flat=True) 5064 return f"{this}({expressions})" 5065 5066 def kwarg_sql(self, expression: exp.Kwarg) -> str: 5067 return self.binary(expression, "=>") 5068 5069 def when_sql(self, expression: exp.When) -> str: 5070 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 5071 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 5072 condition = self.sql(expression, "condition") 5073 condition = f" AND {condition}" if condition else "" 5074 5075 then_expression = expression.args.get("then") 5076 if isinstance(then_expression, exp.Insert): 5077 this = ( 5078 "ALL BY NAME" 5079 if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star) 5080 else self.sql(then_expression, "this") 5081 ) 5082 this = f"INSERT {this}" if this else "INSERT" 5083 then = self.sql(then_expression, "expression") 5084 then = f"{this} VALUES {then}" if then else this 5085 elif isinstance(then_expression, exp.Update): 5086 if isinstance(then_expression.args.get("expressions"), exp.Star): 5087 then = ( 5088 "UPDATE ALL BY NAME" 5089 if self.MERGE_ALL_BY_NAME 5090 else f"UPDATE {self.sql(then_expression, 'expressions')}" 5091 ) 5092 elif ( 5093 self.MERGE_ALL_BY_NAME 5094 and len(then_expression.expressions) == 1 5095 and isinstance(then_expression.expressions[0], exp.Star) 5096 ): 5097 then = "UPDATE ALL BY NAME" 5098 else: 5099 expressions_sql = self.expressions(then_expression) 5100 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 5101 else: 5102 then = self.sql(then_expression) 5103 5104 if isinstance(then_expression, (exp.Insert, exp.Update)): 5105 where = self.sql(then_expression, "where") 5106 if where and not self.SUPPORTS_MERGE_WHERE: 5107 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 5108 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 5109 where = "" 5110 then = f"{then}{where}" 5111 return f"WHEN {matched}{source}{condition} THEN {then}" 5112 5113 def whens_sql(self, expression: exp.Whens) -> str: 5114 return self.expressions(expression, sep=" ", indent=False) 5115 5116 def merge_sql(self, expression: exp.Merge) -> str: 5117 table = expression.this 5118 table_alias = "" 5119 5120 hints = table.args.get("hints") 5121 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 5122 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 5123 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 5124 5125 this = self.sql(table) 5126 using = f"USING {self.sql(expression, 'using')}" 5127 whens = self.sql(expression, "whens") 5128 5129 on = self.sql(expression, "on") 5130 on = f"ON {on}" if on else "" 5131 5132 if not on: 5133 on = self.expressions(expression, key="using_cond") 5134 on = f"USING ({on})" if on else "" 5135 5136 returning = self.sql(expression, "returning") 5137 if returning: 5138 whens = f"{whens}{returning}" 5139 5140 sep = self.sep() 5141 5142 return self.prepend_ctes( 5143 expression, 5144 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5145 ) 5146 5147 @unsupported_args("format") 5148 def tochar_sql(self, expression: exp.ToChar) -> str: 5149 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5150 5151 @unsupported_args("default") 5152 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5153 if not self.SUPPORTS_TO_NUMBER: 5154 self.unsupported("Unsupported TO_NUMBER function") 5155 return self._tonumber_cast_sql(expression) 5156 5157 fmt = expression.args.get("format") 5158 if not fmt: 5159 self.unsupported("Conversion format is required for TO_NUMBER") 5160 return self._tonumber_cast_sql(expression) 5161 5162 return self.func("TO_NUMBER", expression.this, fmt) 5163 5164 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5165 if expression.args.get("safe"): 5166 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5167 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5168 5169 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5170 this = self.sql(expression, "this") 5171 kind = self.sql(expression, "kind") 5172 settings_sql = self.expressions(expression, key="settings", sep=" ") 5173 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5174 return f"{this}({kind}{args})" 5175 5176 def dictrange_sql(self, expression: exp.DictRange) -> str: 5177 this = self.sql(expression, "this") 5178 max = self.sql(expression, "max") 5179 min = self.sql(expression, "min") 5180 return f"{this}(MIN {min} MAX {max})" 5181 5182 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5183 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5184 5185 def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str: 5186 return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})" 5187 5188 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5189 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5190 5191 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5192 def uniquekeyproperty_sql( 5193 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5194 ) -> str: 5195 return f"{prefix} ({self.expressions(expression, flat=True)})" 5196 5197 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5198 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5199 expressions = self.expressions(expression, flat=True) 5200 expressions = f" {self.wrap(expressions)}" if expressions else "" 5201 buckets = self.sql(expression, "buckets") 5202 kind = self.sql(expression, "kind") 5203 buckets = f" BUCKETS {buckets}" if buckets else "" 5204 order = self.sql(expression, "order") 5205 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5206 5207 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5208 return "" 5209 5210 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5211 expressions = self.expressions(expression, key="expressions", flat=True) 5212 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5213 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5214 buckets = self.sql(expression, "buckets") 5215 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5216 5217 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5218 this = self.sql(expression, "this") 5219 having = self.sql(expression, "having") 5220 5221 if having: 5222 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5223 5224 return self.func("ANY_VALUE", this) 5225 5226 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5227 transform = self.func("TRANSFORM", *expression.expressions) 5228 row_format_before = self.sql(expression, "row_format_before") 5229 row_format_before = f" {row_format_before}" if row_format_before else "" 5230 record_writer = self.sql(expression, "record_writer") 5231 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5232 using = f" USING {self.sql(expression, 'command_script')}" 5233 schema = self.sql(expression, "schema") 5234 schema = f" AS {schema}" if schema else "" 5235 row_format_after = self.sql(expression, "row_format_after") 5236 row_format_after = f" {row_format_after}" if row_format_after else "" 5237 record_reader = self.sql(expression, "record_reader") 5238 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5239 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5240 5241 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5242 key_block_size = self.sql(expression, "key_block_size") 5243 if key_block_size: 5244 return f"KEY_BLOCK_SIZE = {key_block_size}" 5245 5246 using = self.sql(expression, "using") 5247 if using: 5248 return f"USING {using}" 5249 5250 parser = self.sql(expression, "parser") 5251 if parser: 5252 return f"WITH PARSER {parser}" 5253 5254 comment = self.sql(expression, "comment") 5255 if comment: 5256 return f"COMMENT {comment}" 5257 5258 visible = expression.args.get("visible") 5259 if visible is not None: 5260 return "VISIBLE" if visible else "INVISIBLE" 5261 5262 engine_attr = self.sql(expression, "engine_attr") 5263 if engine_attr: 5264 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5265 5266 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5267 if secondary_engine_attr: 5268 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5269 5270 self.unsupported("Unsupported index constraint option.") 5271 return "" 5272 5273 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5274 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5275 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5276 5277 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5278 kind = self.sql(expression, "kind") 5279 kind = f"{kind} INDEX" if kind else "INDEX" 5280 this = self.sql(expression, "this") 5281 this = f" {this}" if this else "" 5282 index_type = self.sql(expression, "index_type") 5283 index_type = f" USING {index_type}" if index_type else "" 5284 expressions = self.expressions(expression, flat=True) 5285 expressions = f" ({expressions})" if expressions else "" 5286 options = self.expressions(expression, key="options", sep=" ") 5287 options = f" {options}" if options else "" 5288 return f"{kind}{this}{index_type}{expressions}{options}" 5289 5290 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5291 if self.NVL2_SUPPORTED: 5292 return self.function_fallback_sql(expression) 5293 5294 case = exp.Case().when( 5295 expression.this.is_(exp.null()).not_(copy=False), 5296 expression.args["true"], 5297 copy=False, 5298 ) 5299 else_cond = expression.args.get("false") 5300 if else_cond: 5301 case.else_(else_cond, copy=False) 5302 5303 return self.sql(case) 5304 5305 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5306 if expression.args.get("from_first") is False: 5307 self.unsupported("NTH_VALUE FROM LAST is not supported") 5308 5309 return self.function_fallback_sql(expression) 5310 5311 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5312 this = self.sql(expression, "this") 5313 expr = self.sql(expression, "expression") 5314 position = self.sql(expression, "position") 5315 position = f", {position}" if position else "" 5316 iterator = self.sql(expression, "iterator") 5317 condition = self.sql(expression, "condition") 5318 condition = f" IF {condition}" if condition else "" 5319 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5320 5321 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5322 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5323 5324 def opclass_sql(self, expression: exp.Opclass) -> str: 5325 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5326 5327 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5328 model = self.sql(expression, "this") 5329 model = f"MODEL {model}" 5330 expr = expression.expression 5331 if expr: 5332 expr_sql = self.sql(expression, "expression") 5333 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5334 else: 5335 expr_sql = None 5336 5337 parameters = self.sql(expression, "params_struct") or None 5338 5339 return self.func(name, model, expr_sql, parameters) 5340 5341 def predict_sql(self, expression: exp.Predict) -> str: 5342 return self._ml_sql(expression, "PREDICT") 5343 5344 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5345 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5346 return self._ml_sql(expression, name) 5347 5348 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5349 return self._ml_sql(expression, "GENERATE_TEXT") 5350 5351 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5352 return self._ml_sql(expression, "GENERATE_TABLE") 5353 5354 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5355 return self._ml_sql(expression, "GENERATE_BOOL") 5356 5357 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5358 return self._ml_sql(expression, "GENERATE_INT") 5359 5360 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5361 return self._ml_sql(expression, "GENERATE_DOUBLE") 5362 5363 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5364 return self._ml_sql(expression, "TRANSLATE") 5365 5366 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5367 return self._ml_sql(expression, "FORECAST") 5368 5369 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5370 this_sql = self.sql(expression, "this") 5371 if isinstance(expression.this, exp.Table): 5372 this_sql = f"TABLE {this_sql}" 5373 5374 return self.func( 5375 "FORECAST", 5376 this_sql, 5377 expression.args.get("data_col"), 5378 expression.args.get("timestamp_col"), 5379 expression.args.get("model"), 5380 expression.args.get("id_cols"), 5381 expression.args.get("horizon"), 5382 expression.args.get("forecast_end_timestamp"), 5383 expression.args.get("confidence_level"), 5384 expression.args.get("output_historical_time_series"), 5385 expression.args.get("context_window"), 5386 ) 5387 5388 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5389 this_sql = self.sql(expression, "this") 5390 if isinstance(expression.this, exp.Table): 5391 this_sql = f"TABLE {this_sql}" 5392 5393 return self.func( 5394 "FEATURES_AT_TIME", 5395 this_sql, 5396 expression.args.get("time"), 5397 expression.args.get("num_rows"), 5398 expression.args.get("ignore_feature_nulls"), 5399 ) 5400 5401 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5402 this_sql = self.sql(expression, "this") 5403 if isinstance(expression.this, exp.Table): 5404 this_sql = f"TABLE {this_sql}" 5405 5406 query_table = self.sql(expression, "query_table") 5407 if isinstance(expression.args["query_table"], exp.Table): 5408 query_table = f"TABLE {query_table}" 5409 5410 return self.func( 5411 "VECTOR_SEARCH", 5412 this_sql, 5413 expression.args.get("column_to_search"), 5414 query_table, 5415 expression.args.get("query_column_to_search"), 5416 expression.args.get("top_k"), 5417 expression.args.get("distance_type"), 5418 expression.args.get("options"), 5419 ) 5420 5421 def forin_sql(self, expression: exp.ForIn) -> str: 5422 this = self.sql(expression, "this") 5423 expression_sql = self.sql(expression, "expression") 5424 return f"FOR {this} DO {expression_sql}" 5425 5426 @unsupported_args("partition_start", "partition_end", "force", "mode") 5427 def refresh_sql(self, expression: exp.Refresh) -> str: 5428 this = self.sql(expression, "this") 5429 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5430 return f"REFRESH {kind}{this}" 5431 5432 def toarray_sql(self, expression: exp.ToArray) -> str: 5433 arg = expression.this 5434 if not arg.type: 5435 import sqlglot.optimizer.annotate_types 5436 5437 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5438 5439 if arg.is_type(exp.DType.ARRAY): 5440 return self.sql(arg) 5441 5442 cond_for_null = arg.is_(exp.null()) 5443 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5444 5445 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5446 this = expression.this 5447 time_format = self.format_time(expression) 5448 5449 if time_format: 5450 return self.sql( 5451 exp.cast( 5452 exp.StrToTime(this=this, format=expression.args["format"]), 5453 exp.DType.TIME, 5454 ) 5455 ) 5456 5457 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5458 return self.sql(this) 5459 5460 return self.sql(exp.cast(this, exp.DType.TIME)) 5461 5462 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5463 this = expression.this 5464 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5465 return self.sql(this) 5466 5467 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5468 5469 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5470 this = expression.this 5471 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5472 return self.sql(this) 5473 5474 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5475 5476 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5477 this = expression.this 5478 time_format = self.format_time(expression) 5479 safe = expression.args.get("safe") 5480 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5481 return self.sql( 5482 exp.cast( 5483 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5484 exp.DType.DATE, 5485 ) 5486 ) 5487 5488 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5489 return self.sql(this) 5490 5491 if safe: 5492 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5493 5494 return self.sql(exp.cast(this, exp.DType.DATE)) 5495 5496 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5497 return self.sql( 5498 exp.func( 5499 "DATEDIFF", 5500 expression.this, 5501 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5502 "day", 5503 ) 5504 ) 5505 5506 def lastday_sql(self, expression: exp.LastDay) -> str: 5507 if self.LAST_DAY_SUPPORTS_DATE_PART: 5508 return self.function_fallback_sql(expression) 5509 5510 unit = expression.args.get("unit") 5511 if unit and unit.name.upper() != "MONTH": 5512 self.unsupported("Date parts are not supported in LAST_DAY.") 5513 5514 return self.func("LAST_DAY", expression.this) 5515 5516 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5517 import sqlglot.dialects.dialect 5518 5519 return self.func( 5520 "DATE_ADD", 5521 expression.this, 5522 expression.expression, 5523 sqlglot.dialects.dialect.unit_to_str(expression), 5524 ) 5525 5526 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5527 this = expression.this 5528 position = expression.args["position"] 5529 offset = index_offset - (expression.args.get("offset") or 0) 5530 5531 if offset: 5532 if position.is_int: 5533 value = position.to_py() 5534 if value >= 0: 5535 position = exp.Literal.number(value + offset) 5536 elif offset < 0 and value == -1: 5537 # 1-based -1 appends, which a 0-based position can only express as the size 5538 position = exp.ArraySize(this=this.copy()) 5539 else: 5540 # Negative positions count from the end, so they shift in the opposite 5541 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5542 position = exp.Literal.number(value - offset) 5543 else: 5544 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5545 5546 return self.func("ARRAY_INSERT", this, position, expression.expression) 5547 5548 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5549 if self.CAN_IMPLEMENT_ARRAY_ANY: 5550 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5551 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5552 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5553 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5554 5555 import sqlglot.dialects.dialect 5556 5557 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5558 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5559 self.unsupported("ARRAY_ANY is unsupported") 5560 5561 return self.function_fallback_sql(expression) 5562 5563 def struct_sql(self, expression: exp.Struct) -> str: 5564 expression.set( 5565 "expressions", 5566 [ 5567 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5568 if isinstance(e, exp.PropertyEQ) 5569 else e 5570 for e in expression.expressions 5571 ], 5572 ) 5573 5574 return self.function_fallback_sql(expression) 5575 5576 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5577 low = self.sql(expression, "this") 5578 high = self.sql(expression, "expression") 5579 5580 return f"{low} TO {high}" 5581 5582 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5583 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5584 tables = f" {self.expressions(expression)}" 5585 5586 exists = " IF EXISTS" if expression.args.get("exists") else "" 5587 5588 on_cluster = self.sql(expression, "cluster") 5589 on_cluster = f" {on_cluster}" if on_cluster else "" 5590 5591 identity = self.sql(expression, "identity") 5592 identity = f" {identity} IDENTITY" if identity else "" 5593 5594 option = self.sql(expression, "option") 5595 option = f" {option}" if option else "" 5596 5597 partition = self.sql(expression, "partition") 5598 partition = f" {partition}" if partition else "" 5599 5600 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5601 5602 # This transpiles T-SQL's CONVERT function 5603 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5604 def convert_sql(self, expression: exp.Convert) -> str: 5605 to = expression.this 5606 value = expression.expression 5607 style = expression.args.get("style") 5608 safe = expression.args.get("safe") 5609 strict = expression.args.get("strict") 5610 5611 if not to or not value: 5612 return "" 5613 5614 # Retrieve length of datatype and override to default if not specified 5615 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5616 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5617 5618 transformed: exp.Expr | None = None 5619 cast = exp.Cast if strict else exp.TryCast 5620 5621 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5622 if isinstance(style, exp.Literal) and style.is_int: 5623 import sqlglot.dialects.tsql 5624 5625 style_value = style.name 5626 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5627 if not converted_style: 5628 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5629 5630 fmt = exp.Literal.string(converted_style) 5631 5632 if to.this == exp.DType.DATE: 5633 transformed = exp.StrToDate(this=value, format=fmt) 5634 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5635 transformed = exp.StrToTime(this=value, format=fmt) 5636 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5637 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5638 elif to.this == exp.DType.TEXT: 5639 transformed = exp.TimeToStr(this=value, format=fmt) 5640 5641 if not transformed: 5642 transformed = cast(this=value, to=to, safe=safe) 5643 5644 return self.sql(transformed) 5645 5646 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5647 this = expression.this 5648 if isinstance(this, exp.JSONPathWildcard): 5649 this = self.json_path_part(this) 5650 return f".{this}" if this else "" 5651 5652 quoted = expression.args.get("quoted") 5653 if not ( 5654 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5655 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5656 return f".{this}" 5657 5658 this = self.json_path_part(this) 5659 5660 return ( 5661 f"[{this}]" 5662 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5663 else f".{this}" 5664 ) 5665 5666 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5667 this = self.json_path_part(expression.this) 5668 return f"[{this}]" if this else "" 5669 5670 def _simplify_unless_literal(self, expression: E) -> E: 5671 if not isinstance(expression, exp.Literal): 5672 import sqlglot.optimizer.simplify 5673 5674 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5675 5676 return expression 5677 5678 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5679 this = expression.this 5680 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5681 self.unsupported( 5682 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5683 ) 5684 return self.sql(this) 5685 5686 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5687 if self.IGNORE_NULLS_BEFORE_ORDER: 5688 from sqlglot.optimizer.scope import find_all_in_scope 5689 5690 # The first modifier here will be the one closest to the AggFunc's arg 5691 mods = sorted( 5692 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5693 key=lambda x: ( 5694 0 5695 if isinstance(x, exp.HavingMax) 5696 else (1 if isinstance(x, exp.Order) else 2) 5697 ), 5698 ) 5699 5700 if mods: 5701 mod = mods[0] 5702 this = expression.__class__(this=mod.this.copy()) 5703 this.meta["inline"] = True 5704 mod.this.replace(this) 5705 return self.sql(expression.this) 5706 5707 agg_func = expression.find(exp.AggFunc) 5708 5709 if agg_func: 5710 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5711 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5712 5713 return f"{self.sql(expression, 'this')} {text}" 5714 5715 def _replace_line_breaks(self, string: str) -> str: 5716 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5717 if self.pretty: 5718 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5719 return string 5720 5721 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5722 option = self.sql(expression, "this") 5723 5724 if expression.expressions: 5725 upper = option.upper() 5726 5727 # Snowflake FILE_FORMAT options are separated by whitespace 5728 sep = " " if upper == "FILE_FORMAT" else ", " 5729 5730 # Databricks copy/format options do not set their list of values with EQ 5731 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5732 values = self.expressions(expression, flat=True, sep=sep) 5733 return f"{option}{op}({values})" 5734 5735 value = self.sql(expression, "expression") 5736 5737 if not value: 5738 return option 5739 5740 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5741 5742 return f"{option}{op}{value}" 5743 5744 def credentials_sql(self, expression: exp.Credentials) -> str: 5745 cred_expr = expression.args.get("credentials") 5746 if isinstance(cred_expr, exp.Literal): 5747 # Redshift case: CREDENTIALS <string> 5748 credentials = self.sql(expression, "credentials") 5749 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5750 else: 5751 # Snowflake case: CREDENTIALS = (...) 5752 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5753 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5754 5755 storage = self.sql(expression, "storage") 5756 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5757 5758 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5759 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5760 5761 iam_role = self.sql(expression, "iam_role") 5762 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5763 5764 region = self.sql(expression, "region") 5765 region = f" REGION {region}" if region else "" 5766 5767 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5768 5769 def copy_sql(self, expression: exp.Copy) -> str: 5770 this = self.sql(expression, "this") 5771 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5772 5773 credentials = self.sql(expression, "credentials") 5774 credentials = self.seg(credentials) if credentials else "" 5775 files = self.expressions(expression, key="files", flat=True) 5776 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5777 5778 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5779 params = self.expressions( 5780 expression, 5781 key="params", 5782 sep=sep, 5783 new_line=True, 5784 skip_last=True, 5785 skip_first=True, 5786 indent=self.COPY_PARAMS_ARE_WRAPPED, 5787 ) 5788 5789 if params: 5790 if self.COPY_PARAMS_ARE_WRAPPED: 5791 params = f" WITH ({params})" 5792 elif not self.pretty and (files or credentials): 5793 params = f" {params}" 5794 5795 return f"COPY{this}{kind} {files}{credentials}{params}" 5796 5797 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5798 return "" 5799 5800 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5801 on_sql = "ON" if expression.args.get("on") else "OFF" 5802 filter_col: str | None = self.sql(expression, "filter_column") 5803 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5804 retention_period: str | None = self.sql(expression, "retention_period") 5805 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5806 5807 if filter_col or retention_period: 5808 on_sql = self.func("ON", filter_col, retention_period) 5809 5810 return f"DATA_DELETION={on_sql}" 5811 5812 def maskingpolicycolumnconstraint_sql( 5813 self, expression: exp.MaskingPolicyColumnConstraint 5814 ) -> str: 5815 this = self.sql(expression, "this") 5816 expressions = self.expressions(expression, flat=True) 5817 expressions = f" USING ({expressions})" if expressions else "" 5818 return f"MASKING POLICY {this}{expressions}" 5819 5820 def gapfill_sql(self, expression: exp.GapFill) -> str: 5821 this = self.sql(expression, "this") 5822 this = f"TABLE {this}" 5823 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5824 5825 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5826 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5827 5828 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5829 this = self.sql(expression, "this") 5830 expr = expression.expression 5831 5832 if isinstance(expr, exp.Func): 5833 # T-SQL's CLR functions are case sensitive 5834 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5835 else: 5836 expr = self.sql(expression, "expression") 5837 5838 return self.scope_resolution(expr, this) 5839 5840 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5841 if self.PARSE_JSON_NAME is None: 5842 return self.sql(expression.this) 5843 5844 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5845 5846 def rand_sql(self, expression: exp.Rand) -> str: 5847 lower = self.sql(expression, "lower") 5848 upper = self.sql(expression, "upper") 5849 5850 if lower and upper: 5851 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5852 return self.func("RAND", expression.this) 5853 5854 def changes_sql(self, expression: exp.Changes) -> str: 5855 information = self.sql(expression, "information") 5856 information = f"INFORMATION => {information}" 5857 at_before = self.sql(expression, "at_before") 5858 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5859 end = self.sql(expression, "end") 5860 end = f"{self.seg('')}{end}" if end else "" 5861 5862 return f"CHANGES ({information}){at_before}{end}" 5863 5864 def pad_sql(self, expression: exp.Pad) -> str: 5865 prefix = "L" if expression.args.get("is_left") else "R" 5866 5867 fill_pattern = self.sql(expression, "fill_pattern") or None 5868 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5869 fill_pattern = "' '" 5870 5871 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5872 5873 def summarize_sql(self, expression: exp.Summarize) -> str: 5874 table = " TABLE" if expression.args.get("table") else "" 5875 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5876 5877 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5878 generate_series = exp.GenerateSeries(**expression.args) 5879 5880 parent = expression.parent 5881 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5882 parent = parent.parent 5883 5884 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5885 return self.sql(exp.Unnest(expressions=[generate_series])) 5886 5887 if isinstance(parent, exp.Select): 5888 self.unsupported("GenerateSeries projection unnesting is not supported.") 5889 5890 return self.sql(generate_series) 5891 5892 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5893 if self.SUPPORTS_CONVERT_TIMEZONE: 5894 return self.function_fallback_sql(expression) 5895 5896 source_tz = expression.args.get("source_tz") 5897 target_tz = expression.args.get("target_tz") 5898 timestamp = expression.args.get("timestamp") 5899 5900 if source_tz and timestamp: 5901 timestamp = exp.AtTimeZone( 5902 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5903 ) 5904 5905 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5906 5907 return self.sql(expr) 5908 5909 def json_sql(self, expression: exp.JSON) -> str: 5910 this = self.sql(expression, "this") 5911 this = f" {this}" if this else "" 5912 5913 _with = expression.args.get("with_") 5914 5915 if _with is None: 5916 with_sql = "" 5917 elif not _with: 5918 with_sql = " WITHOUT" 5919 else: 5920 with_sql = " WITH" 5921 5922 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5923 5924 return f"JSON{this}{with_sql}{unique_sql}" 5925 5926 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5927 path = self.sql(expression, "path") 5928 returning = self.sql(expression, "returning") 5929 returning = f" RETURNING {returning}" if returning else "" 5930 5931 on_condition = self.sql(expression, "on_condition") 5932 on_condition = f" {on_condition}" if on_condition else "" 5933 5934 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5935 5936 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5937 regexp = " REGEXP" if expression.args.get("regexp") else "" 5938 return f"SKIP{regexp} {self.sql(expression.expression)}" 5939 5940 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5941 else_ = "ELSE " if expression.args.get("else_") else "" 5942 condition = self.sql(expression, "expression") 5943 condition = f"WHEN {condition} THEN " if condition else else_ 5944 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5945 return f"{condition}{insert}" 5946 5947 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5948 kind = self.sql(expression, "kind") 5949 source = self.sql(expression, "source") 5950 5951 if kind: 5952 expressions = self.seg(self.expressions(expression, sep=" ")) 5953 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5954 else: 5955 # If kind isn't present, we have Hive's FROM-first form: 5956 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5957 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5958 sql = f"FROM {source}{self.seg(inserts)}" 5959 5960 return self.prepend_ctes(expression, sql) 5961 5962 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5963 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5964 empty = expression.args.get("empty") 5965 empty = ( 5966 f"DEFAULT {empty} ON EMPTY" 5967 if isinstance(empty, exp.Expr) 5968 else self.sql(expression, "empty") 5969 ) 5970 5971 error = expression.args.get("error") 5972 error = ( 5973 f"DEFAULT {error} ON ERROR" 5974 if isinstance(error, exp.Expr) 5975 else self.sql(expression, "error") 5976 ) 5977 5978 if error and empty: 5979 error = ( 5980 f"{empty} {error}" 5981 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5982 else f"{error} {empty}" 5983 ) 5984 empty = "" 5985 5986 null = self.sql(expression, "null") 5987 5988 return f"{empty}{error}{null}" 5989 5990 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5991 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5992 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5993 5994 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5995 this = self.sql(expression, "this") 5996 path = self.sql(expression, "path") 5997 5998 passing = self.expressions(expression, "passing") 5999 passing = f" PASSING {passing}" if passing else "" 6000 6001 on_condition = self.sql(expression, "on_condition") 6002 on_condition = f" {on_condition}" if on_condition else "" 6003 6004 path = f"{path}{passing}{on_condition}" 6005 6006 return self.func("JSON_EXISTS", this, path) 6007 6008 def _add_arrayagg_null_filter( 6009 self, 6010 array_agg_sql: str, 6011 array_agg_expr: exp.ArrayAgg, 6012 column_expr: exp.Expr, 6013 ) -> str: 6014 """ 6015 Add NULL filter to ARRAY_AGG if dialect requires it. 6016 6017 Args: 6018 array_agg_sql: The generated ARRAY_AGG SQL string 6019 array_agg_expr: The ArrayAgg expression node 6020 column_expr: The column/expression to filter (before ORDER BY wrapping) 6021 6022 Returns: 6023 SQL string with FILTER clause added if needed 6024 """ 6025 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 6026 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 6027 if not ( 6028 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 6029 ): 6030 return array_agg_sql 6031 6032 parent = array_agg_expr.parent 6033 if isinstance(parent, exp.Filter): 6034 parent_cond = parent.expression.this 6035 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 6036 elif column_expr.find(exp.Column): 6037 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 6038 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 6039 this_sql = ( 6040 self.expressions(column_expr) 6041 if isinstance(column_expr, exp.Distinct) 6042 else self.sql(column_expr) 6043 ) 6044 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 6045 6046 return array_agg_sql 6047 6048 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 6049 array_agg = self.function_fallback_sql(expression) 6050 column_expr = expression.this 6051 if isinstance(column_expr, exp.Order): 6052 column_expr = column_expr.this 6053 6054 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 6055 6056 def slice_sql(self, expression: exp.Slice) -> str: 6057 step = self.sql(expression, "step") 6058 # An omitted end is written as `-` before a step, since `::` is the cast operator 6059 end = self.sql(expression.expression) or ("-" if step else "") 6060 begin = self.sql(expression.this) 6061 6062 sql = f"{end}:{step}" if step else end 6063 return f"{begin}:{sql}" if sql else f"{begin}:" 6064 6065 def apply_sql(self, expression: exp.Apply) -> str: 6066 this = self.sql(expression, "this") 6067 expr = self.sql(expression, "expression") 6068 6069 return f"{this} APPLY({expr})" 6070 6071 def _grant_or_revoke_sql( 6072 self, 6073 expression: exp.Grant | exp.Revoke, 6074 keyword: str, 6075 preposition: str, 6076 grant_option_prefix: str = "", 6077 grant_option_suffix: str = "", 6078 ) -> str: 6079 privileges_sql = self.expressions(expression, key="privileges", flat=True) 6080 6081 kind = self.sql(expression, "kind") 6082 kind = f" {kind}" if kind else "" 6083 6084 securable = self.sql(expression, "securable") 6085 securable = f" {securable}" if securable else "" 6086 6087 principals = self.expressions(expression, key="principals", flat=True) 6088 6089 if not expression.args.get("grant_option"): 6090 grant_option_prefix = grant_option_suffix = "" 6091 6092 # cascade for revoke only 6093 cascade = self.sql(expression, "cascade") 6094 cascade = f" {cascade}" if cascade else "" 6095 6096 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 6097 6098 def grant_sql(self, expression: exp.Grant) -> str: 6099 return self._grant_or_revoke_sql( 6100 expression, 6101 keyword="GRANT", 6102 preposition="TO", 6103 grant_option_suffix=" WITH GRANT OPTION", 6104 ) 6105 6106 def revoke_sql(self, expression: exp.Revoke) -> str: 6107 return self._grant_or_revoke_sql( 6108 expression, 6109 keyword="REVOKE", 6110 preposition="FROM", 6111 grant_option_prefix="GRANT OPTION FOR ", 6112 ) 6113 6114 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 6115 this = self.sql(expression, "this") 6116 columns = self.expressions(expression, flat=True) 6117 columns = f"({columns})" if columns else "" 6118 6119 return f"{this}{columns}" 6120 6121 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 6122 this = self.sql(expression, "this") 6123 6124 kind = self.sql(expression, "kind") 6125 kind = f"{kind} " if kind else "" 6126 6127 return f"{kind}{this}" 6128 6129 def columns_sql(self, expression: exp.Columns) -> str: 6130 func = self.function_fallback_sql(expression) 6131 if expression.args.get("unpack"): 6132 func = f"*{func}" 6133 6134 return func 6135 6136 def overlay_sql(self, expression: exp.Overlay) -> str: 6137 this = self.sql(expression, "this") 6138 expr = self.sql(expression, "expression") 6139 from_sql = self.sql(expression, "from_") 6140 for_sql = self.sql(expression, "for_") 6141 for_sql = f" FOR {for_sql}" if for_sql else "" 6142 6143 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6144 6145 @unsupported_args("format") 6146 def todouble_sql(self, expression: exp.ToDouble) -> str: 6147 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6148 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6149 6150 def string_sql(self, expression: exp.String) -> str: 6151 this = expression.this 6152 zone = expression.args.get("zone") 6153 6154 if zone: 6155 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6156 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6157 # set for source_tz to transpile the time conversion before the STRING cast 6158 this = exp.ConvertTimezone( 6159 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6160 ) 6161 6162 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6163 6164 def median_sql(self, expression: exp.Median) -> str: 6165 if not self.SUPPORTS_MEDIAN: 6166 return self.sql( 6167 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6168 ) 6169 6170 return self.function_fallback_sql(expression) 6171 6172 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6173 filler = self.sql(expression, "this") 6174 filler = f" {filler}" if filler else "" 6175 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6176 return f"TRUNCATE{filler} {with_count}" 6177 6178 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6179 if self.SUPPORTS_UNIX_SECONDS: 6180 return self.function_fallback_sql(expression) 6181 6182 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6183 6184 return self.sql( 6185 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6186 ) 6187 6188 def arraysort_sql(self, expression: exp.ArraySort) -> str: 6189 asc = expression.args.get("asc") 6190 nulls_first = expression.args.get("nulls_first") 6191 if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()): 6192 self.unsupported("ARRAY_SORT only sorts ascending with NULLs last") 6193 return self.func("ARRAY_SORT", expression.this, expression.expression) 6194 6195 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6196 dim = expression.expression 6197 6198 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6199 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6200 if not (dim.is_int and dim.name == "1"): 6201 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6202 dim = None 6203 6204 # If dimension is required but not specified, default initialize it 6205 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6206 dim = exp.Literal.number(1) 6207 6208 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6209 6210 def attach_sql(self, expression: exp.Attach) -> str: 6211 this = self.sql(expression, "this") 6212 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6213 expressions = self.expressions(expression) 6214 expressions = f" ({expressions})" if expressions else "" 6215 6216 return f"ATTACH{exists_sql} {this}{expressions}" 6217 6218 def detach_sql(self, expression: exp.Detach) -> str: 6219 kind = self.sql(expression, "kind") 6220 kind = f" {kind}" if kind else "" 6221 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6222 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6223 exists = " IF EXISTS" if expression.args.get("exists") else "" 6224 if exists: 6225 kind = kind or " DATABASE" 6226 6227 this = self.sql(expression, "this") 6228 this = f" {this}" if this else "" 6229 cluster = self.sql(expression, "cluster") 6230 cluster = f" {cluster}" if cluster else "" 6231 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6232 sync = " SYNC" if expression.args.get("sync") else "" 6233 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6234 6235 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6236 this = self.sql(expression, "this") 6237 value = self.sql(expression, "expression") 6238 value = f" {value}" if value else "" 6239 return f"{this}{value}" 6240 6241 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6242 return ( 6243 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6244 ) 6245 6246 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6247 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6248 encode = f"{encode} {self.sql(expression, 'this')}" 6249 6250 properties = expression.args.get("properties") 6251 if properties: 6252 encode = f"{encode} {self.properties(properties)}" 6253 6254 return encode 6255 6256 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6257 this = self.sql(expression, "this") 6258 include = f"INCLUDE {this}" 6259 6260 column_def = self.sql(expression, "column_def") 6261 if column_def: 6262 include = f"{include} {column_def}" 6263 6264 alias = self.sql(expression, "alias") 6265 if alias: 6266 include = f"{include} AS {alias}" 6267 6268 return include 6269 6270 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6271 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6272 name = f"{prefix} {self.sql(expression, 'this')}" 6273 return self.func("XMLELEMENT", name, *expression.expressions) 6274 6275 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6276 this = self.sql(expression, "this") 6277 expr = self.sql(expression, "expression") 6278 expr = f"({expr})" if expr else "" 6279 return f"{this}{expr}" 6280 6281 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6282 partitions = self.expressions(expression, "partition_expressions") 6283 create = self.expressions(expression, "create_expressions") 6284 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6285 6286 def partitionbyrangepropertydynamic_sql( 6287 self, expression: exp.PartitionByRangePropertyDynamic 6288 ) -> str: 6289 start = self.sql(expression, "start") 6290 end = self.sql(expression, "end") 6291 6292 every = expression.args["every"] 6293 if isinstance(every, exp.Interval) and every.this.is_string: 6294 every.this.replace(exp.Literal.number(every.name)) 6295 6296 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6297 6298 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6299 name = self.sql(expression, "this") 6300 values = self.expressions(expression, flat=True) 6301 6302 return f"NAME {name} VALUE {values}" 6303 6304 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6305 kind = self.sql(expression, "kind") 6306 sample = self.sql(expression, "sample") 6307 return f"SAMPLE {sample} {kind}" 6308 6309 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6310 kind = self.sql(expression, "kind") 6311 option = self.sql(expression, "option") 6312 option = f" {option}" if option else "" 6313 this = self.sql(expression, "this") 6314 this = f" {this}" if this else "" 6315 columns = self.expressions(expression) 6316 columns = f" {columns}" if columns else "" 6317 return f"{kind}{option} STATISTICS{this}{columns}" 6318 6319 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6320 this = self.sql(expression, "this") 6321 columns = self.expressions(expression) 6322 inner_expression = self.sql(expression, "expression") 6323 inner_expression = f" {inner_expression}" if inner_expression else "" 6324 update_options = self.sql(expression, "update_options") 6325 update_options = f" {update_options} UPDATE" if update_options else "" 6326 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6327 6328 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6329 kind = self.sql(expression, "kind") 6330 kind = f" {kind}" if kind else "" 6331 return f"DELETE{kind} STATISTICS" 6332 6333 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6334 inner_expression = self.sql(expression, "expression") 6335 return f"LIST CHAINED ROWS{inner_expression}" 6336 6337 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6338 kind = self.sql(expression, "kind") 6339 this = self.sql(expression, "this") 6340 this = f" {this}" if this else "" 6341 inner_expression = self.sql(expression, "expression") 6342 return f"VALIDATE {kind}{this}{inner_expression}" 6343 6344 def analyze_sql(self, expression: exp.Analyze) -> str: 6345 options = self.expressions(expression, key="options", sep=" ") 6346 options = f" {options}" if options else "" 6347 kind = self.sql(expression, "kind") 6348 kind = f" {kind}" if kind else "" 6349 tables = self.expressions(expression, key="tables", flat=True) 6350 tables = f" {tables}" if tables else "" 6351 mode = self.sql(expression, "mode") 6352 mode = f" {mode}" if mode else "" 6353 properties = self.sql(expression, "properties") 6354 properties = f" {properties}" if properties else "" 6355 partition = self.sql(expression, "partition") 6356 partition = f" {partition}" if partition else "" 6357 inner_expression = self.sql(expression, "expression") 6358 inner_expression = f" {inner_expression}" if inner_expression else "" 6359 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6360 6361 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6362 this = self.sql(expression, "this") 6363 namespaces = self.expressions(expression, key="namespaces") 6364 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6365 passing = self.expressions(expression, key="passing") 6366 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6367 columns = self.expressions(expression, key="columns") 6368 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6369 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6370 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6371 6372 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6373 this = self.sql(expression, "this") 6374 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6375 6376 def export_sql(self, expression: exp.Export) -> str: 6377 this = self.sql(expression, "this") 6378 connection = self.sql(expression, "connection") 6379 connection = f"WITH CONNECTION {connection} " if connection else "" 6380 options = self.sql(expression, "options") 6381 return f"EXPORT DATA {connection}{options} AS {this}" 6382 6383 def declare_sql(self, expression: exp.Declare) -> str: 6384 replace = "OR REPLACE " if expression.args.get("replace") else "" 6385 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6386 6387 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6388 variables = self.expressions(expression, "this") 6389 default = self.sql(expression, "default") 6390 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6391 6392 kind = self.sql(expression, "kind") 6393 if isinstance(expression.args.get("kind"), exp.Schema): 6394 kind = f"TABLE {kind}" 6395 6396 kind = f" {kind}" if kind else "" 6397 6398 return f"{variables}{kind}{default}" 6399 6400 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6401 kind = self.sql(expression, "kind") 6402 this = self.sql(expression, "this") 6403 set = self.sql(expression, "expression") 6404 to = self.sql(expression, "to") 6405 to = f" TO {to}" if to else "" 6406 default = self.sql(expression, "default") 6407 default = f" DEFAULT {default}" if default else "" 6408 using = self.sql(expression, "using") 6409 using = f" USING {using}" if using else "" 6410 6411 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6412 6413 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6414 6415 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6416 params = self.expressions(expression, key="params", flat=True) 6417 return self.func(expression.name, *expression.expressions) + f"({params})" 6418 6419 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6420 return self.func(expression.name, *expression.expressions) 6421 6422 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6423 return self.anonymousaggfunc_sql(expression) 6424 6425 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6426 return self.parameterizedagg_sql(expression) 6427 6428 def show_sql(self, expression: exp.Show) -> str: 6429 self.unsupported("Unsupported SHOW statement") 6430 return "" 6431 6432 def install_sql(self, expression: exp.Install) -> str: 6433 self.unsupported("Unsupported INSTALL statement") 6434 return "" 6435 6436 def liststage_sql(self, expression: exp.ListStage) -> str: 6437 pattern = self.sql(expression, "pattern") 6438 pattern = f" PATTERN = {pattern}" if pattern else "" 6439 return f"LIST {self.sql(expression, 'this')}{pattern}" 6440 6441 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6442 # Snowflake GET/PUT statements: 6443 # PUT <file> <internalStage> <properties> 6444 # GET <internalStage> <file> <properties> 6445 props = expression.args.get("properties") 6446 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6447 this = self.sql(expression, "this") 6448 target = self.sql(expression, "target") 6449 6450 if isinstance(expression, exp.Put): 6451 return f"PUT {this} {target}{props_sql}" 6452 else: 6453 return f"GET {target} {this}{props_sql}" 6454 6455 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6456 this = self.sql(expression, "this") 6457 expr = self.sql(expression, "expression") 6458 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6459 return f"TRANSLATE({this} USING {expr}{with_error})" 6460 6461 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6462 if self.SUPPORTS_DECODE_CASE: 6463 return self.func("DECODE", *expression.expressions) 6464 6465 decode_expr, *expressions = expression.expressions 6466 6467 ifs = [] 6468 for search, result in zip(expressions[::2], expressions[1::2]): 6469 if isinstance(search, exp.Literal): 6470 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6471 elif isinstance(search, exp.Null): 6472 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6473 else: 6474 if isinstance(search, exp.Binary): 6475 search = exp.paren(search) 6476 6477 cond = exp.or_( 6478 decode_expr.eq(search), 6479 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6480 copy=False, 6481 ) 6482 ifs.append(exp.If(this=cond, true=result)) 6483 6484 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6485 return self.sql(case) 6486 6487 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6488 this = self.sql(expression, "this") 6489 this = self.seg(this, sep="") 6490 dimensions = self.expressions( 6491 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6492 ) 6493 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6494 metrics = self.expressions( 6495 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6496 ) 6497 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6498 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6499 facts = self.seg(f"FACTS {facts}") if facts else "" 6500 where = self.sql(expression, "where") 6501 where = self.seg(f"WHERE {where}") if where else "" 6502 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6503 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6504 6505 def getextract_sql(self, expression: exp.GetExtract) -> str: 6506 this = expression.this 6507 expr = expression.expression 6508 6509 if not this.type or not expression.type: 6510 import sqlglot.optimizer.annotate_types 6511 6512 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6513 6514 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6515 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6516 6517 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6518 6519 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6520 return self.sql( 6521 exp.DateAdd( 6522 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6523 expression=expression.this, 6524 unit=exp.var("DAY"), 6525 ) 6526 ) 6527 6528 def space_sql(self: Generator, expression: exp.Space) -> str: 6529 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6530 6531 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6532 return f"BUILD {self.sql(expression, 'this')}" 6533 6534 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6535 method = self.sql(expression, "method") 6536 kind = expression.args.get("kind") 6537 if not kind: 6538 return f"REFRESH {method}" 6539 6540 every = self.sql(expression, "every") 6541 unit = self.sql(expression, "unit") 6542 every = f" EVERY {every} {unit}" if every else "" 6543 starts = self.sql(expression, "starts") 6544 starts = f" STARTS {starts}" if starts else "" 6545 6546 return f"REFRESH {method} ON {kind}{every}{starts}" 6547 6548 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6549 self.unsupported("The model!attribute syntax is not supported") 6550 return "" 6551 6552 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6553 return self.func("DIRECTORY", expression.this) 6554 6555 def uuid_sql(self, expression: exp.Uuid) -> str: 6556 is_string = expression.args.get("is_string", False) 6557 uuid_func_sql = self.func("UUID") 6558 6559 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6560 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6561 6562 return uuid_func_sql 6563 6564 def initcap_sql(self, expression: exp.Initcap) -> str: 6565 delimiters = expression.expression 6566 6567 if delimiters: 6568 # do not generate delimiters arg if we are round-tripping from default delimiters 6569 if ( 6570 delimiters.is_string 6571 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6572 ): 6573 delimiters = None 6574 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6575 self.unsupported("INITCAP does not support custom delimiters") 6576 delimiters = None 6577 6578 return self.func("INITCAP", expression.this, delimiters) 6579 6580 def localtime_sql(self, expression: exp.Localtime) -> str: 6581 this = expression.this 6582 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6583 6584 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6585 this = expression.this 6586 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6587 6588 def weekstart_name(self, expression: exp.WeekStart) -> str: 6589 import sqlglot.dialects.dialect 6590 6591 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6592 this = expression.this.name.upper() 6593 6594 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6595 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6596 6597 if dow_from_week_start_day != dow_from_week_offset: 6598 self.unsupported( 6599 f"WEEK({this}) is not supported; falling back to the default week start day" 6600 ) 6601 6602 return "WEEK" 6603 6604 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6605 name = self.weekstart_name(expression) 6606 6607 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6608 if isinstance(expression.parent, exp.DateTrunc): 6609 return self.sql(exp.Literal.string(name)) 6610 6611 return name 6612 6613 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6614 this = self.expressions(expression) 6615 charset = self.sql(expression, "charset") 6616 using = f" USING {charset}" if charset else "" 6617 return self.func(name, this + using) 6618 6619 def weightstring_sql(self, expression: exp.WeightString) -> str: 6620 to = self.sql(expression, "to") 6621 to = f" AS {to}" if to else "" 6622 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6623 6624 def block_sql(self, expression: exp.Block) -> str: 6625 expressions = self.expressions(expression, sep="; ", flat=True) 6626 begin = "BEGIN " if expression.args.get("begin") else "" 6627 return f"{begin}{expressions}" if expressions else "" 6628 6629 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6630 self.unsupported("Unsupported Inline UDFs syntax") 6631 return "" 6632 6633 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6634 self.unsupported("Unsupported Stored Procedure syntax") 6635 return "" 6636 6637 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6638 self.unsupported("Unsupported If block syntax") 6639 return "" 6640 6641 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6642 self.unsupported("Unsupported Case statement syntax") 6643 return "" 6644 6645 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6646 self.unsupported("Unsupported While block syntax") 6647 return "" 6648 6649 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6650 self.unsupported("Unsupported Loop block syntax") 6651 return "" 6652 6653 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6654 self.unsupported("Unsupported Repeat block syntax") 6655 return "" 6656 6657 def leave_sql(self, expression: exp.Leave) -> str: 6658 self.unsupported("Unsupported Leave syntax") 6659 return "" 6660 6661 def iterate_sql(self, expression: exp.Iterate) -> str: 6662 self.unsupported("Unsupported Iterate syntax") 6663 return "" 6664 6665 def execute_sql(self, expression: exp.Execute) -> str: 6666 self.unsupported("Unsupported Execute syntax") 6667 return "" 6668 6669 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6670 self.unsupported("Unsupported Execute syntax") 6671 return "" 6672 6673 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6674 props = self.expressions(expression, sep=" ") 6675 return f"MODIFY {props}" 6676 6677 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6678 kind = expression.args.get("kind") 6679 return f"USING {kind} {self.sql(expression, 'this')}" 6680 6681 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6682 this = self.sql(expression, "this") 6683 to = self.sql(expression, "to") 6684 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]]:
35def unsupported_args( 36 *args: str | tuple[str, str], 37) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 38 """ 39 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 40 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 41 """ 42 diagnostic_by_arg: dict[str, str | None] = {} 43 for arg in args: 44 if isinstance(arg, str): 45 diagnostic_by_arg[arg] = None 46 else: 47 diagnostic_by_arg[arg[0]] = arg[1] 48 49 def decorator(func: GeneratorMethod) -> GeneratorMethod: 50 @wraps(func) 51 def _func(generator: G, expression: E) -> str: 52 expression_name = expression.__class__.__name__ 53 dialect_name = generator.dialect.__class__.__name__ 54 55 for arg_name, diagnostic in diagnostic_by_arg.items(): 56 if expression.args.get(arg_name): 57 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 58 arg_name, expression_name, dialect_name 59 ) 60 generator.unsupported(diagnostic) 61 62 return func(generator, expression) 63 64 return _func 65 66 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:
100class Generator: 101 """ 102 Generator converts a given syntax tree to the corresponding SQL string. 103 104 Args: 105 pretty: Whether to format the produced SQL string. 106 Default: False. 107 identify: Determines when an identifier should be quoted. Possible values are: 108 False (default): Never quote, except in cases where it's mandatory by the dialect. 109 True: Always quote except for specials cases. 110 'safe': Only quote identifiers that are case insensitive. 111 normalize: Whether to normalize identifiers to lowercase. 112 Default: False. 113 pad: The pad size in a formatted string. For example, this affects the indentation of 114 a projection in a query, relative to its nesting level. 115 Default: 2. 116 indent: The indentation size in a formatted string. For example, this affects the 117 indentation of subqueries and filters under a `WHERE` clause. 118 Default: 2. 119 normalize_functions: How to normalize function names. Possible values are: 120 "upper" or True (default): Convert names to uppercase. 121 "lower": Convert names to lowercase. 122 False: Disables function name normalization. 123 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 124 Default ErrorLevel.WARN. 125 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 126 This is only relevant if unsupported_level is ErrorLevel.RAISE. 127 Default: 3 128 leading_comma: Whether the comma is leading or trailing in select expressions. 129 This is only relevant when generating in pretty mode. 130 Default: False 131 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 132 The default is on the smaller end because the length only represents a segment and not the true 133 line length. 134 Default: 80 135 comments: Whether to preserve comments in the output SQL code. 136 Default: True 137 """ 138 139 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 140 **JSON_PATH_PART_TRANSFORMS, 141 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 142 exp.AllowedValuesProperty: lambda self, e: ( 143 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 144 ), 145 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 146 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 147 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 148 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 149 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 150 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 151 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 152 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 153 exp.BinaryColumnConstraint: lambda *_: "BINARY", 154 exp.CaseSpecificColumnConstraint: lambda _, e: ( 155 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 156 ), 157 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 158 exp.Ceil: lambda self, e: self.ceil_floor(e), 159 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 160 exp.CharacterSetProperty: lambda self, e: ( 161 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 162 ), 163 exp.ClusteredColumnConstraint: lambda self, e: ( 164 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 165 ), 166 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 167 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 168 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 169 exp.ConvertToCharset: lambda self, e: self.func( 170 "CONVERT", e.this, e.args["dest"], e.args.get("source") 171 ), 172 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 173 exp.CopyTagsProperty: lambda *_: "COPY TAGS", 174 exp.CredentialsProperty: lambda self, e: ( 175 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 176 ), 177 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 178 exp.SessionUser: lambda *_: "SESSION_USER", 179 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 180 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 181 exp.ApiProperty: lambda *_: "API", 182 exp.ApplicationProperty: lambda *_: "APPLICATION", 183 exp.CatalogProperty: lambda *_: "CATALOG", 184 exp.ComputeProperty: lambda *_: "COMPUTE", 185 exp.DatabaseProperty: lambda *_: "DATABASE", 186 exp.DynamicProperty: lambda *_: "DYNAMIC", 187 exp.EmptyProperty: lambda *_: "EMPTY", 188 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 189 exp.EndStatement: lambda *_: "END", 190 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 191 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 192 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 193 exp.EphemeralColumnConstraint: lambda self, e: ( 194 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 195 ), 196 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 197 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 198 exp.Except: lambda self, e: self.set_operations(e), 199 exp.ExternalProperty: lambda *_: "EXTERNAL", 200 exp.Floor: lambda self, e: self.ceil_floor(e), 201 exp.Get: lambda self, e: self.get_put_sql(e), 202 exp.GlobalProperty: lambda *_: "GLOBAL", 203 exp.HeapProperty: lambda *_: "HEAP", 204 exp.HybridProperty: lambda *_: "HYBRID", 205 exp.IcebergProperty: lambda *_: "ICEBERG", 206 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 207 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 208 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 209 exp.Intersect: lambda self, e: self.set_operations(e), 210 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 211 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 212 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 213 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 214 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 215 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 216 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 217 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 218 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 219 exp.LanguageProperty: lambda self, e: self.naked_property(e), 220 exp.LocationProperty: lambda self, e: self.naked_property(e), 221 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 222 exp.MaskingProperty: lambda *_: "MASKING", 223 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 224 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 225 exp.NetworkProperty: lambda *_: "NETWORK", 226 exp.NonClusteredColumnConstraint: lambda self, e: ( 227 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 228 ), 229 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 230 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 231 exp.OnCommitProperty: lambda _, e: ( 232 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 233 ), 234 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 235 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 236 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 237 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 238 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 239 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 240 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 241 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 242 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 243 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 244 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 245 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 246 f"PROJECTION POLICY {self.sql(e, 'this')}" 247 ), 248 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 249 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 250 exp.Put: lambda self, e: self.get_put_sql(e), 251 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 252 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 253 ), 254 exp.ReturnsProperty: lambda self, e: ( 255 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 256 ), 257 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 258 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 259 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 260 exp.SecureProperty: lambda *_: "SECURE", 261 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 262 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 263 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 264 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 265 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 266 exp.SqlReadWriteProperty: lambda _, e: e.name, 267 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 268 exp.StabilityProperty: lambda _, e: e.name, 269 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 270 exp.StreamingTableProperty: lambda *_: "STREAMING", 271 exp.StrictProperty: lambda *_: "STRICT", 272 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 273 exp.TableColumn: lambda self, e: self.sql(e.this), 274 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 275 exp.TemporaryProperty: lambda *_: "TEMPORARY", 276 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 277 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 278 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 279 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 280 exp.TransientProperty: lambda *_: "TRANSIENT", 281 exp.VirtualProperty: lambda *_: "VIRTUAL", 282 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 283 exp.Union: lambda self, e: self.set_operations(e), 284 exp.UnloggedProperty: lambda *_: "UNLOGGED", 285 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 286 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 287 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 288 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 289 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 290 exp.UtcTimestamp: lambda self, e: self.sql( 291 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 292 ), 293 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 294 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 295 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 296 exp.VolatileProperty: lambda *_: "VOLATILE", 297 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 298 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 299 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 300 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 301 exp.ForceProperty: lambda *_: "FORCE", 302 } 303 304 # Whether null ordering is supported in order by 305 # True: Full Support, None: No support, False: No support for certain cases 306 # such as window specifications, aggregate functions etc 307 NULL_ORDERING_SUPPORTED: bool | None = True 308 309 # Window functions that support NULLS FIRST/LAST 310 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 311 312 # Whether ignore nulls is inside the agg or outside. 313 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 314 IGNORE_NULLS_IN_FUNC = False 315 316 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 317 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 318 IGNORE_NULLS_BEFORE_ORDER = True 319 320 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 321 LOCKING_READS_SUPPORTED = False 322 323 # Whether the EXCEPT and INTERSECT operations can return duplicates 324 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 325 326 # Wrap derived values in parens, usually standard but spark doesn't support it 327 WRAP_DERIVED_VALUES = True 328 329 # Whether create function uses an AS before the RETURN 330 CREATE_FUNCTION_RETURN_AS = True 331 332 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 333 MATCHED_BY_SOURCE = True 334 335 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 336 SUPPORTS_MERGE_WHERE = False 337 338 # Whether MERGE uses ALL BY NAME instead of a star to update or insert all columns 339 MERGE_ALL_BY_NAME = False 340 341 # Whether the INTERVAL expression works only with values like '1 day' 342 SINGLE_STRING_INTERVAL = False 343 344 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 345 INTERVAL_ALLOWS_PLURAL_FORM = True 346 347 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 348 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 349 AUTO_REFRESH_BARE_INTERVALS = False 350 351 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 352 LIMIT_FETCH = "ALL" 353 354 # Whether limit and fetch allows expresions or just limits 355 LIMIT_ONLY_LITERALS = False 356 357 # Whether a table is allowed to be renamed with a db 358 RENAME_TABLE_WITH_DB = True 359 360 # The separator for grouping sets and rollups 361 GROUPINGS_SEP = "," 362 363 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 364 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 365 366 # The string used for creating an index on a table 367 INDEX_ON = "ON" 368 369 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 370 INOUT_SEPARATOR = " " 371 372 # Whether join hints should be generated 373 JOIN_HINTS = True 374 375 # Whether directed joins are supported 376 DIRECTED_JOINS = False 377 378 # Whether top-k distance/similarity joins are supported 379 SUPPORTS_NEAREST_JOIN = False 380 381 # Whether table hints should be generated 382 TABLE_HINTS = True 383 384 # Whether query hints should be generated 385 QUERY_HINTS = True 386 387 # What kind of separator to use for query hints 388 QUERY_HINT_SEP = ", " 389 390 # Whether comparing against booleans (e.g. x IS TRUE) is supported 391 IS_BOOL_ALLOWED = True 392 393 # Whether casting a fractional number to an integer rounds it instead of truncating it 394 CAST_TO_INT_ROUNDS = False 395 396 # Whether the native integer division operator returns NULL for a zero divisor instead of raising 397 SAFE_INT_DIVISION = False 398 399 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 400 DUPLICATE_KEY_UPDATE_WITH_SET = True 401 402 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 403 LIMIT_IS_TOP = False 404 405 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 406 RETURNING_END = True 407 408 # Whether to generate an unquoted value for EXTRACT's date part argument 409 EXTRACT_ALLOWS_QUOTES = True 410 411 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 412 TZ_TO_WITH_TIME_ZONE = False 413 414 # Whether the NVL2 function is supported 415 NVL2_SUPPORTED = True 416 417 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 418 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 419 420 # Whether VALUES statements can be used as derived tables. 421 # MySQL 5 and Redshift do not allow this, so when False, it will convert 422 # SELECT * VALUES into SELECT UNION 423 VALUES_AS_TABLE = True 424 425 # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE. 426 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 427 428 # Whether ALTER TABLE mutli-column additions are wrapped in parentheses. 429 ALTER_TABLE_ADD_COLUMNS_WRAPPED = True 430 431 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 432 UNNEST_WITH_ORDINALITY = True 433 434 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 435 SEMI_ANTI_JOIN_WITH_SIDE = True 436 437 # Whether to include the type of a computed column in the CREATE DDL 438 COMPUTED_COLUMN_WITH_TYPE = True 439 440 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 441 SUPPORTS_TABLE_COPY = True 442 443 # Whether parentheses are required around the table sample's expression 444 TABLESAMPLE_REQUIRES_PARENS = True 445 446 # Whether a table sample clause's size needs to be followed by the ROWS keyword 447 TABLESAMPLE_SIZE_IS_ROWS = True 448 449 # The keyword(s) to use when generating a sample clause 450 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 451 452 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 453 TABLESAMPLE_WITH_METHOD = True 454 455 # The keyword to use when specifying the seed of a sample clause 456 TABLESAMPLE_SEED_KEYWORD = "SEED" 457 458 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 459 HISTORICAL_DATA_POST_ALIAS = False 460 461 # Whether COLLATE is a function instead of a binary operator 462 COLLATE_IS_FUNC = False 463 464 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 465 DATA_TYPE_SPECIFIERS_ALLOWED = False 466 467 # Whether conditions require booleans WHERE x = 0 vs WHERE x 468 ENSURE_BOOLS = False 469 470 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 471 CTE_RECURSIVE_KEYWORD_REQUIRED = True 472 473 # Whether CONCAT requires >1 arguments 474 SUPPORTS_SINGLE_ARG_CONCAT = True 475 476 # Whether LAST_DAY function supports a date part argument 477 LAST_DAY_SUPPORTS_DATE_PART = True 478 479 # Whether named columns are allowed in table aliases 480 SUPPORTS_TABLE_ALIAS_COLUMNS = True 481 482 # Whether named columns are allowed in CTE definitions 483 SUPPORTS_NAMED_CTE_COLUMNS = True 484 485 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 486 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 487 488 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 489 PIVOT_ALIAS_WITH_AS = True 490 491 # What delimiter to use for separating JSON key/value pairs 492 JSON_KEY_VALUE_PAIR_SEP = ":" 493 494 # INSERT OVERWRITE TABLE x override 495 INSERT_OVERWRITE = " OVERWRITE TABLE" 496 497 # Whether the SELECT .. INTO syntax is used instead of CTAS 498 SUPPORTS_SELECT_INTO = False 499 500 # Whether UNLOGGED tables can be created 501 SUPPORTS_UNLOGGED_TABLES = False 502 503 # Whether the CREATE TABLE LIKE statement is supported 504 SUPPORTS_CREATE_TABLE_LIKE = True 505 506 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 507 SUPPORTS_MODIFY_COLUMN = False 508 509 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 510 SUPPORTS_CHANGE_COLUMN = False 511 512 # Whether ALTER COLUMN can set a column's nullability together with its type 513 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 514 515 # Whether ALTER COLUMN IF EXISTS is supported 516 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 517 518 # Whether the LikeProperty needs to be specified inside of the schema clause 519 LIKE_PROPERTY_INSIDE_SCHEMA = False 520 521 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 522 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 523 MULTI_ARG_DISTINCT = True 524 525 # Whether the JSON extraction operators expect a value of type JSON 526 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 527 528 # Whether bracketed keys like ["foo"] are supported in JSON paths 529 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 530 531 # Whether to escape keys using single quotes in JSON paths 532 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 533 534 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 535 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 536 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 537 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 538 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 539 540 # The JSONPathPart expressions supported by this dialect 541 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 542 543 # Whether any(f(x) for x in array) can be implemented by this dialect 544 CAN_IMPLEMENT_ARRAY_ANY = False 545 546 # Whether the function TO_NUMBER is supported 547 SUPPORTS_TO_NUMBER = True 548 549 # Whether EXCLUDE in window specification is supported 550 SUPPORTS_WINDOW_EXCLUDE = False 551 552 # Whether or not set op modifiers apply to the outer set op or select. 553 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 554 # True means limit 1 happens after the set op, False means it it happens on y. 555 SET_OP_MODIFIERS = True 556 557 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 558 SET_OP_LIMITS = False 559 560 # Whether set operation operands can be parenthesized without a SELECT wrapper. 561 SET_OP_PARENTHESIZED_OPERANDS = True 562 563 # Whether parameters from COPY statement are wrapped in parentheses 564 COPY_PARAMS_ARE_WRAPPED = True 565 566 # Whether values of params are set with "=" token or empty space 567 COPY_PARAMS_EQ_REQUIRED = False 568 569 # Whether COPY statement has INTO keyword 570 COPY_HAS_INTO_KEYWORD = True 571 572 # Whether the conditional TRY(expression) function is supported 573 TRY_SUPPORTED = True 574 575 # Whether the UESCAPE syntax in unicode strings is supported 576 SUPPORTS_UESCAPE = True 577 578 # Function used to replace escaped unicode codes in unicode strings 579 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 580 581 # The keyword to use when generating a star projection with excluded columns 582 STAR_EXCEPT = "EXCEPT" 583 584 # The HEX function name 585 HEX_FUNC = "HEX" 586 587 # Function that constructs a binary value from a hex-encoded byte string. 588 BYTE_STRING_FUNCTION = "" 589 590 # The keywords to use when prefixing & separating WITH based properties 591 WITH_PROPERTIES_PREFIX = "WITH" 592 593 # Whether to quote the generated expression of exp.JsonPath 594 QUOTE_JSON_PATH = True 595 596 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 597 PAD_FILL_PATTERN_IS_REQUIRED = False 598 599 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 600 SUPPORTS_EXPLODING_PROJECTIONS = True 601 602 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 603 ARRAY_CONCAT_IS_VAR_LEN = True 604 605 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 606 SUPPORTS_CONVERT_TIMEZONE = False 607 608 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 609 SUPPORTS_MEDIAN = True 610 611 # Whether UNIX_SECONDS(timestamp) is supported 612 SUPPORTS_UNIX_SECONDS = False 613 614 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 615 ALTER_SET_WRAPPED = False 616 617 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 618 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 619 # TODO: The normalization should be done by default once we've tested it across all dialects. 620 NORMALIZE_EXTRACT_DATE_PARTS = False 621 622 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 623 PARSE_JSON_NAME: str | None = "PARSE_JSON" 624 625 # The function name of the exp.ArraySize expression 626 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 627 628 # The syntax to use when altering the type of a column 629 ALTER_SET_TYPE = "SET DATA TYPE" 630 631 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 632 # None -> Doesn't support it at all 633 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 634 # True (Postgres) -> Explicitly requires it 635 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 636 637 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 638 SUPPORTS_DECODE_CASE = True 639 640 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 641 SUPPORTS_BETWEEN_FLAGS = False 642 643 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 644 SUPPORTS_LIKE_QUANTIFIERS = True 645 646 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 647 MATCH_AGAINST_TABLE_PREFIX: str | None = None 648 649 # Whether to include the VARIABLE keyword for SET assignments 650 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 651 652 # The keyword to use for default value assignment in DECLARE statements 653 DECLARE_DEFAULT_ASSIGNMENT = "=" 654 655 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 656 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 657 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 658 UPDATE_STATEMENT_SUPPORTS_FROM = True 659 660 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 661 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 662 663 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 664 # - Snowflake: DROP ICEBERG TABLE a.b; 665 # - DuckDB: DROP TABLE a.b; 666 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 667 668 TYPE_MAPPING: t.ClassVar = { 669 exp.DType.DATETIME2: "TIMESTAMP", 670 exp.DType.NCHAR: "CHAR", 671 exp.DType.NVARCHAR: "VARCHAR", 672 exp.DType.MEDIUMTEXT: "TEXT", 673 exp.DType.LONGTEXT: "TEXT", 674 exp.DType.TINYTEXT: "TEXT", 675 exp.DType.BLOB: "VARBINARY", 676 exp.DType.MEDIUMBLOB: "BLOB", 677 exp.DType.LONGBLOB: "BLOB", 678 exp.DType.TINYBLOB: "BLOB", 679 exp.DType.INET: "INET", 680 exp.DType.ROWVERSION: "VARBINARY", 681 exp.DType.SMALLDATETIME: "TIMESTAMP", 682 } 683 684 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 685 686 # mapping of DType to its default parameters, bounds 687 TYPE_PARAM_SETTINGS: t.ClassVar[ 688 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 689 ] = {} 690 691 TIME_PART_SINGULARS: t.ClassVar = { 692 "MICROSECONDS": "MICROSECOND", 693 "SECONDS": "SECOND", 694 "MINUTES": "MINUTE", 695 "HOURS": "HOUR", 696 "DAYS": "DAY", 697 "WEEKS": "WEEK", 698 "MONTHS": "MONTH", 699 "QUARTERS": "QUARTER", 700 "YEARS": "YEAR", 701 } 702 703 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 704 "cluster": lambda self, e: self.sql(e, "cluster"), 705 "distribute": lambda self, e: self.sql(e, "distribute"), 706 "sort": lambda self, e: self.sql(e, "sort"), 707 **AFTER_HAVING_MODIFIER_TRANSFORMS, 708 } 709 710 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 711 712 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 713 714 PARAMETER_TOKEN = "@" 715 NAMED_PLACEHOLDER_TOKEN = ":" 716 717 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 718 719 PROPERTIES_LOCATION: t.ClassVar = { 720 exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 723 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 724 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 725 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 729 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 731 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 733 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 735 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 738 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 743 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 744 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 746 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 747 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 748 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 749 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 753 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 759 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 760 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 761 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 762 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 763 exp.HeapProperty: exp.Properties.Location.POST_WITH, 764 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 765 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 767 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 770 exp.JournalProperty: exp.Properties.Location.POST_NAME, 771 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 776 exp.LogProperty: exp.Properties.Location.POST_NAME, 777 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 778 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 779 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 780 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 782 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 783 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 784 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 785 exp.Order: exp.Properties.Location.POST_SCHEMA, 786 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 788 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED, 790 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 791 exp.Property: exp.Properties.Location.POST_WITH, 792 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 795 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 796 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 797 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 798 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 801 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 802 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 803 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 804 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 805 exp.Set: exp.Properties.Location.POST_SCHEMA, 806 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 807 exp.SetProperty: exp.Properties.Location.POST_CREATE, 808 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 809 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 810 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 811 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 812 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 813 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 814 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 815 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 816 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 817 exp.StorageProperty: exp.Properties.Location.UNSUPPORTED, 818 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 819 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 820 exp.Tags: exp.Properties.Location.POST_WITH, 821 exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED, 822 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 823 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 824 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 825 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 826 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 827 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 828 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 829 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 830 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 831 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 832 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 833 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 834 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 835 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 836 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 837 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 838 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 839 } 840 841 # Keywords that can't be used as unquoted identifier names 842 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 843 844 # Exprs whose comments are separated from them for better formatting 845 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 846 exp.Command, 847 exp.Create, 848 exp.Describe, 849 exp.Delete, 850 exp.Drop, 851 exp.From, 852 exp.Insert, 853 exp.Join, 854 exp.MultitableInserts, 855 exp.Order, 856 exp.Group, 857 exp.Having, 858 exp.Select, 859 exp.SetOperation, 860 exp.Update, 861 exp.Where, 862 exp.With, 863 ) 864 865 # Exprs that should not have their comments generated in maybe_comment 866 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 867 exp.Binary, 868 exp.SetOperation, 869 ) 870 871 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 872 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 873 exp.Column, 874 exp.Literal, 875 exp.Neg, 876 exp.Paren, 877 ) 878 879 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 880 exp.DType.NVARCHAR, 881 exp.DType.VARCHAR, 882 exp.DType.CHAR, 883 exp.DType.NCHAR, 884 } 885 886 # Exprs that need to have all CTEs under them bubbled up to them 887 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 888 889 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 890 891 MOD_OPERATOR = "%" 892 893 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 894 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 895 exp.Mul, 896 exp.Div, 897 exp.IntDiv, 898 exp.Mod, 899 ) 900 901 # ALTER options that follow the others without a comma, e.g. MySQL's 902 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 903 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 904 905 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 906 907 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 908 909 __slots__ = ( 910 "pretty", 911 "identify", 912 "normalize", 913 "pad", 914 "_indent", 915 "normalize_functions", 916 "unsupported_level", 917 "max_unsupported", 918 "leading_comma", 919 "max_text_width", 920 "comments", 921 "dialect", 922 "unsupported_messages", 923 "_escaped_quote_end", 924 "_escaped_byte_quote_end", 925 "_escaped_identifier_end", 926 "_identifier_escaped_sequences", 927 "_next_name", 928 "_identifier_start", 929 "_identifier_end", 930 "_quote_json_path_key_using_brackets", 931 "_dispatch", 932 ) 933 934 def __init__( 935 self, 936 pretty: bool | int | None = None, 937 identify: str | bool = False, 938 normalize: bool = False, 939 pad: int = 2, 940 indent: int = 2, 941 normalize_functions: str | bool | None = None, 942 unsupported_level: ErrorLevel = ErrorLevel.WARN, 943 max_unsupported: int = 3, 944 leading_comma: bool = False, 945 max_text_width: int = 80, 946 comments: bool = True, 947 dialect: DialectType = None, 948 ): 949 import sqlglot 950 import sqlglot.dialects.dialect 951 952 self.pretty = pretty if pretty is not None else sqlglot.pretty 953 self.identify = identify 954 self.normalize = normalize 955 self.pad = pad 956 self._indent = indent 957 self.unsupported_level = unsupported_level 958 self.max_unsupported = max_unsupported 959 self.leading_comma = leading_comma 960 self.max_text_width = max_text_width 961 self.comments = comments 962 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 963 964 # This is both a Dialect property and a Generator argument, so we prioritize the latter 965 self.normalize_functions = ( 966 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 967 ) 968 969 self.unsupported_messages: list[str] = [] 970 self._escaped_quote_end: str = ( 971 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 972 ) 973 self._escaped_byte_quote_end: str = ( 974 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 975 if self.dialect.BYTE_END 976 else "" 977 ) 978 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 979 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 980 981 self._next_name = name_sequence("_t") 982 983 self._identifier_start = self.dialect.IDENTIFIER_START 984 self._identifier_end = self.dialect.IDENTIFIER_END 985 986 self._quote_json_path_key_using_brackets = True 987 988 cls = type(self) 989 dispatch = _DISPATCH_CACHE.get(cls) 990 if dispatch is None: 991 dispatch = _build_dispatch(cls) 992 _DISPATCH_CACHE[cls] = dispatch 993 self._dispatch = dispatch 994 995 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 996 """ 997 Generates the SQL string corresponding to the given syntax tree. 998 999 Args: 1000 expression: The syntax tree. 1001 copy: Whether to copy the expression. The generator performs mutations so 1002 it is safer to copy. 1003 1004 Returns: 1005 The SQL string corresponding to `expression`. 1006 """ 1007 if copy: 1008 expression = expression.copy() 1009 1010 expression = self.preprocess(expression) 1011 1012 self.unsupported_messages = [] 1013 sql = self.sql(expression).strip() 1014 1015 if self.pretty: 1016 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1017 1018 if self.unsupported_level == ErrorLevel.IGNORE: 1019 return sql 1020 1021 if self.unsupported_level == ErrorLevel.WARN: 1022 for msg in self.unsupported_messages: 1023 logger.warning(msg) 1024 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1025 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1026 1027 return sql 1028 1029 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1030 """Apply generic preprocessing transformations to a given expression.""" 1031 expression = self._move_ctes_to_top_level(expression) 1032 1033 if self.ENSURE_BOOLS: 1034 import sqlglot.transforms 1035 1036 expression = sqlglot.transforms.ensure_bools(expression) 1037 1038 return expression 1039 1040 def _move_ctes_to_top_level(self, expression: E) -> E: 1041 if ( 1042 not expression.parent 1043 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1044 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1045 ): 1046 import sqlglot.transforms 1047 1048 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1049 return expression 1050 1051 def unsupported(self, message: str) -> None: 1052 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1053 raise UnsupportedError(message) 1054 self.unsupported_messages.append(message) 1055 1056 def sep(self, sep: str = " ") -> str: 1057 return f"{sep.strip()}\n" if self.pretty else sep 1058 1059 def seg(self, sql: str, sep: str = " ") -> str: 1060 return f"{self.sep(sep)}{sql}" 1061 1062 def sanitize_comment(self, comment: str) -> str: 1063 comment = " " + comment if comment[0].strip() else comment 1064 comment = comment + " " if comment[-1].strip() else comment 1065 1066 # Escape block comment markers to prevent premature closure or unintended nesting. 1067 # This is necessary because single-line comments (--) are converted to block comments 1068 # (/* */) on output, and any */ in the original text would close the comment early. 1069 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1070 1071 return comment 1072 1073 def maybe_comment( 1074 self, 1075 sql: str, 1076 expression: exp.Expr | None = None, 1077 comments: list[str] | None = None, 1078 separated: bool = False, 1079 ) -> str: 1080 comments = ( 1081 ((expression and expression.comments) if comments is None else comments) # type: ignore 1082 if self.comments 1083 else None 1084 ) 1085 1086 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1087 return sql 1088 1089 comments_list = [ 1090 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1091 for comment in comments 1092 if comment 1093 ] 1094 1095 if not comments_list: 1096 return sql 1097 1098 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1099 comments_sql = self.sep().join(comments_list) 1100 return ( 1101 f"{self.sep()}{comments_sql}{sql}" 1102 if not sql or sql[0].isspace() 1103 else f"{comments_sql}{self.sep()}{sql}" 1104 ) 1105 1106 return f"{sql} {' '.join(comments_list)}" 1107 1108 def wrap(self, expression: exp.Expr | str) -> str: 1109 this_sql = ( 1110 self.sql(expression) 1111 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1112 else self.sql(expression, "this") 1113 ) 1114 if not this_sql: 1115 return "()" 1116 1117 this_sql = self.indent(this_sql, level=1, pad=0) 1118 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1119 1120 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1121 original = self.identify 1122 self.identify = False 1123 result = func(*args, **kwargs) 1124 self.identify = original 1125 return result 1126 1127 def normalize_func(self, name: str) -> str: 1128 if self.normalize_functions == "upper" or self.normalize_functions is True: 1129 return name.upper() 1130 if self.normalize_functions == "lower": 1131 return name.lower() 1132 return name 1133 1134 def indent( 1135 self, 1136 sql: str, 1137 level: int = 0, 1138 pad: int | None = None, 1139 skip_first: bool = False, 1140 skip_last: bool = False, 1141 ) -> str: 1142 if not self.pretty or not sql: 1143 return sql 1144 1145 pad = self.pad if pad is None else pad 1146 lines = sql.split("\n") 1147 1148 return "\n".join( 1149 ( 1150 line 1151 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1152 else f"{' ' * (level * self._indent + pad)}{line}" 1153 ) 1154 for i, line in enumerate(lines) 1155 ) 1156 1157 def sql( 1158 self, 1159 expression: str | exp.Expr | None, 1160 key: str | None = None, 1161 comment: bool = True, 1162 ) -> str: 1163 if not expression: 1164 return "" 1165 1166 if isinstance(expression, str): 1167 return expression 1168 1169 if key: 1170 value = expression.args.get(key) 1171 if value: 1172 return self.sql(value) 1173 return "" 1174 1175 handler = self._dispatch.get(expression.__class__) 1176 1177 if handler: 1178 sql = handler(self, expression) 1179 elif isinstance(expression, exp.Func): 1180 sql = self.function_fallback_sql(expression) 1181 elif isinstance(expression, exp.Property): 1182 sql = self.property_sql(expression) 1183 else: 1184 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1185 1186 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1187 1188 def uncache_sql(self, expression: exp.Uncache) -> str: 1189 table = self.sql(expression, "this") 1190 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1191 return f"UNCACHE TABLE{exists_sql} {table}" 1192 1193 def cache_sql(self, expression: exp.Cache) -> str: 1194 lazy = " LAZY" if expression.args.get("lazy") else "" 1195 table = self.sql(expression, "this") 1196 options = expression.args.get("options") 1197 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1198 sql = self.sql(expression, "expression") 1199 sql = f" AS{self.sep()}{sql}" if sql else "" 1200 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1201 return self.prepend_ctes(expression, sql) 1202 1203 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1204 default = "DEFAULT " if expression.args.get("default") else "" 1205 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1206 1207 def column_parts(self, expression: exp.Column) -> str: 1208 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1209 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1210 return self.sql(expression, "this") 1211 1212 return ".".join( 1213 self.sql(part) 1214 for part in ( 1215 expression.args.get("catalog"), 1216 expression.args.get("db"), 1217 expression.args.get("table"), 1218 expression.args.get("this"), 1219 ) 1220 if part 1221 ) 1222 1223 def column_sql(self, expression: exp.Column) -> str: 1224 join_mark = " (+)" if expression.args.get("join_mark") else "" 1225 1226 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1227 join_mark = "" 1228 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1229 1230 return f"{self.column_parts(expression)}{join_mark}" 1231 1232 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1233 return self.column_sql(expression) 1234 1235 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1236 this = self.sql(expression, "this") 1237 this = f" {this}" if this else "" 1238 position = self.sql(expression, "position") 1239 return f"{position}{this}" 1240 1241 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1242 column = self.sql(expression, "this") 1243 kind = self.sql(expression, "kind") 1244 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1245 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1246 kind = f"{sep}{kind}" if kind else "" 1247 constraints = f" {constraints}" if constraints else "" 1248 position = self.sql(expression, "position") 1249 position = f" {position}" if position else "" 1250 1251 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1252 kind = "" 1253 1254 return f"{exists}{column}{kind}{constraints}{position}" 1255 1256 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1257 this = self.sql(expression, "this") 1258 kind_sql = self.sql(expression, "kind").strip() 1259 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1260 1261 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1262 this = self.sql(expression, "this") 1263 if expression.args.get("not_null"): 1264 persisted = " PERSISTED NOT NULL" 1265 elif expression.args.get("persisted"): 1266 persisted = " PERSISTED" 1267 else: 1268 persisted = "" 1269 1270 return f"AS {this}{persisted}" 1271 1272 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1273 return self.token_sql(TokenType.AUTO_INCREMENT) 1274 1275 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1276 if isinstance(expression.this, list): 1277 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1278 else: 1279 this = self.sql(expression, "this") 1280 1281 return f"COMPRESS {this}" 1282 1283 def generatedasidentitycolumnconstraint_sql( 1284 self, expression: exp.GeneratedAsIdentityColumnConstraint 1285 ) -> str: 1286 this = "" 1287 if expression.this is not None: 1288 on_null = " ON NULL" if expression.args.get("on_null") else "" 1289 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1290 1291 start = expression.args.get("start") 1292 start = f"START WITH {start}" if start else "" 1293 increment = expression.args.get("increment") 1294 increment = f" INCREMENT BY {increment}" if increment else "" 1295 minvalue = expression.args.get("minvalue") 1296 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1297 maxvalue = expression.args.get("maxvalue") 1298 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1299 cycle = expression.args.get("cycle") 1300 cycle_sql = "" 1301 1302 if cycle is not None: 1303 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1304 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1305 1306 sequence_opts = "" 1307 if start or increment or cycle_sql: 1308 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1309 sequence_opts = f" ({sequence_opts.strip()})" 1310 1311 expr = self.sql(expression, "expression") 1312 expr = f"({expr})" if expr else "IDENTITY" 1313 1314 return f"GENERATED{this} AS {expr}{sequence_opts}" 1315 1316 def generatedasrowcolumnconstraint_sql( 1317 self, expression: exp.GeneratedAsRowColumnConstraint 1318 ) -> str: 1319 start = "START" if expression.args.get("start") else "END" 1320 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1321 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1322 1323 def periodforsystemtimeconstraint_sql( 1324 self, expression: exp.PeriodForSystemTimeConstraint 1325 ) -> str: 1326 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1327 1328 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1329 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1330 1331 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1332 desc = expression.args.get("desc") 1333 if desc is not None: 1334 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1335 options = self.expressions(expression, key="options", flat=True, sep=" ") 1336 options = f" {options}" if options else "" 1337 include = self.sql(expression, "include") 1338 return f"PRIMARY KEY{include}{options}" 1339 1340 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1341 this = self.sql(expression, "this") 1342 this = f" {this}" if this else "" 1343 index_type = expression.args.get("index_type") 1344 index_type = f" USING {index_type}" if index_type else "" 1345 on_conflict = self.sql(expression, "on_conflict") 1346 on_conflict = f" {on_conflict}" if on_conflict else "" 1347 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1348 options = self.expressions(expression, key="options", flat=True, sep=" ") 1349 options = f" {options}" if options else "" 1350 include = self.sql(expression, "include") 1351 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}" 1352 1353 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1354 input_ = expression.args.get("input_") 1355 output = expression.args.get("output") 1356 variadic = expression.args.get("variadic") 1357 1358 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1359 if variadic: 1360 return "VARIADIC" 1361 1362 if input_ and output: 1363 return f"IN{self.INOUT_SEPARATOR}OUT" 1364 if input_: 1365 return "IN" 1366 if output: 1367 return "OUT" 1368 1369 return "" 1370 1371 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1372 return self.sql(expression, "this") 1373 1374 def create_sql(self, expression: exp.Create) -> str: 1375 kind = self.sql(expression, "kind") 1376 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1377 1378 properties = expression.args.get("properties") 1379 1380 if ( 1381 kind == "TRIGGER" 1382 and properties 1383 and properties.expressions 1384 and isinstance(properties.expressions[0], exp.TriggerProperties) 1385 and properties.expressions[0].args.get("constraint") 1386 ): 1387 kind = f"CONSTRAINT {kind}" 1388 1389 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1390 1391 this = self.createable_sql(expression, properties_locs) 1392 1393 properties_sql = "" 1394 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1395 exp.Properties.Location.POST_WITH 1396 ): 1397 props_ast = exp.Properties( 1398 expressions=[ 1399 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1400 *properties_locs[exp.Properties.Location.POST_WITH], 1401 ] 1402 ) 1403 props_ast.parent = expression 1404 properties_sql = self.sql(props_ast) 1405 1406 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1407 properties_sql = self.sep() + properties_sql 1408 elif not self.pretty: 1409 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1410 properties_sql = f" {properties_sql}" 1411 1412 begin = " BEGIN" if expression.args.get("begin") else "" 1413 1414 expression_sql = self.sql(expression, "expression") 1415 if expression_sql: 1416 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1417 1418 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1419 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1420 ): 1421 postalias_props_sql = "" 1422 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1423 postalias_props_sql = self.properties( 1424 exp.Properties( 1425 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1426 ), 1427 wrapped=False, 1428 ) 1429 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1430 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1431 1432 postindex_props_sql = "" 1433 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1434 postindex_props_sql = self.properties( 1435 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1436 wrapped=False, 1437 prefix=" ", 1438 ) 1439 1440 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1441 indexes = f" {indexes}" if indexes else "" 1442 index_sql = indexes + postindex_props_sql 1443 1444 replace = " OR REPLACE" if expression.args.get("replace") else "" 1445 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1446 unique = " UNIQUE" if expression.args.get("unique") else "" 1447 1448 clustered = expression.args.get("clustered") 1449 if clustered is None: 1450 clustered_sql = "" 1451 elif clustered: 1452 clustered_sql = " CLUSTERED COLUMNSTORE" 1453 else: 1454 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1455 1456 postcreate_props_sql = "" 1457 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1458 postcreate_props_sql = self.properties( 1459 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1460 sep=" ", 1461 prefix=" ", 1462 wrapped=False, 1463 ) 1464 1465 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1466 1467 postexpression_props_sql = "" 1468 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1469 postexpression_props_sql = self.properties( 1470 exp.Properties( 1471 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1472 ), 1473 sep=" ", 1474 prefix=" ", 1475 wrapped=False, 1476 ) 1477 1478 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1479 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1480 no_schema_binding = ( 1481 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1482 ) 1483 1484 clone = self.sql(expression, "clone") 1485 clone = f" {clone}" if clone else "" 1486 1487 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1488 properties_expression = f"{expression_sql}{properties_sql}" 1489 else: 1490 properties_expression = f"{properties_sql}{expression_sql}" 1491 1492 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1493 return self.prepend_ctes(expression, expression_sql) 1494 1495 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1496 start = self.sql(expression, "start") 1497 start = f"START WITH {start}" if start else "" 1498 increment = self.sql(expression, "increment") 1499 increment = f" INCREMENT BY {increment}" if increment else "" 1500 minvalue = self.sql(expression, "minvalue") 1501 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1502 maxvalue = self.sql(expression, "maxvalue") 1503 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1504 owned = self.sql(expression, "owned") 1505 owned = f" OWNED BY {owned}" if owned else "" 1506 1507 cache = expression.args.get("cache") 1508 if cache is None: 1509 cache_str = "" 1510 elif cache is True: 1511 cache_str = " CACHE" 1512 else: 1513 cache_str = f" CACHE {cache}" 1514 1515 options = self.expressions(expression, key="options", flat=True, sep=" ") 1516 options = f" {options}" if options else "" 1517 1518 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1519 1520 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1521 timing = expression.args.get("timing", "") 1522 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1523 timing_events = f"{timing} {events}".strip() if timing or events else "" 1524 1525 parts = [timing_events, "ON", self.sql(expression, "table")] 1526 1527 if referenced_table := expression.args.get("referenced_table"): 1528 parts.extend(["FROM", self.sql(referenced_table)]) 1529 1530 if deferrable := expression.args.get("deferrable"): 1531 parts.append(deferrable) 1532 1533 if initially := expression.args.get("initially"): 1534 parts.append(f"INITIALLY {initially}") 1535 1536 if referencing := expression.args.get("referencing"): 1537 parts.append(self.sql(referencing)) 1538 1539 if for_each := expression.args.get("for_each"): 1540 parts.append(f"FOR EACH {for_each}") 1541 1542 if when := expression.args.get("when"): 1543 parts.append(f"WHEN ({self.sql(when)})") 1544 1545 parts.append(self.sql(expression, "execute")) 1546 1547 return self.sep().join(parts) 1548 1549 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1550 parts = [] 1551 1552 if old_alias := expression.args.get("old"): 1553 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1554 1555 if new_alias := expression.args.get("new"): 1556 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1557 1558 return f"REFERENCING {' '.join(parts)}" 1559 1560 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1561 columns = expression.args.get("columns") 1562 if columns: 1563 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1564 1565 return self.sql(expression, "this") 1566 1567 def clone_sql(self, expression: exp.Clone) -> str: 1568 this = self.sql(expression, "this") 1569 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1570 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1571 return f"{shallow}{keyword} {this}" 1572 1573 def describe_sql(self, expression: exp.Describe) -> str: 1574 style = expression.args.get("style") 1575 style = f" {style}" if style else "" 1576 partition = self.sql(expression, "partition") 1577 partition = f" {partition}" if partition else "" 1578 format = self.sql(expression, "format") 1579 format = f" {format}" if format else "" 1580 as_json = " AS JSON" if expression.args.get("as_json") else "" 1581 1582 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1583 1584 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1585 tag = self.sql(expression, "tag") 1586 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1587 1588 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1589 with_ = self.sql(expression, "with_") 1590 if with_: 1591 sql = f"{with_}{self.sep()}{sql}" 1592 return sql 1593 1594 def with_sql(self, expression: exp.With) -> str: 1595 udfs = self.expressions(expression, key="udfs", flat=True) 1596 udfs = f"WITH {udfs}" if udfs else "" 1597 1598 sql = self.expressions(expression, flat=True) 1599 1600 recursive = ( 1601 "RECURSIVE " 1602 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1603 else "" 1604 ) 1605 search = self.sql(expression, "search") 1606 search = f" {search}" if search else "" 1607 cycle = self.sql(expression, "cycle") 1608 cycle = f" {cycle}" if cycle else "" 1609 1610 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1611 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1612 1613 def cte_sql(self, expression: exp.CTE) -> str: 1614 alias = expression.args.get("alias") 1615 if alias: 1616 alias.add_comments(expression.pop_comments()) 1617 1618 alias_sql = self.sql(expression, "alias") 1619 1620 materialized = expression.args.get("materialized") 1621 if materialized is False: 1622 materialized = "NOT MATERIALIZED " 1623 elif materialized: 1624 materialized = "MATERIALIZED " 1625 1626 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1627 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1628 1629 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1630 1631 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1632 alias = self.sql(expression, "this") 1633 columns = self.expressions(expression, key="columns", flat=True) 1634 columns = f"({columns})" if columns else "" 1635 1636 if ( 1637 columns 1638 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1639 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1640 ): 1641 columns = "" 1642 self.unsupported("Named columns are not supported in table alias.") 1643 1644 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1645 alias = self._next_name() 1646 1647 return f"{alias}{columns}" 1648 1649 def bitstring_sql(self, expression: exp.BitString) -> str: 1650 this = self.sql(expression, "this") 1651 if self.dialect.BIT_START: 1652 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1653 return f"{int(this, 2)}" 1654 1655 def hexstring_sql( 1656 self, expression: exp.HexString, binary_function_repr: str | None = None 1657 ) -> str: 1658 this = self.sql(expression, "this") 1659 is_integer_type = expression.args.get("is_integer") 1660 1661 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1662 not self.dialect.HEX_START and not binary_function_repr 1663 ): 1664 # Integer representation will be returned if: 1665 # - The read dialect treats the hex value as integer literal but not the write 1666 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1667 return f"{int(this, 16)}" 1668 1669 if not is_integer_type: 1670 # Read dialect treats the hex value as BINARY/BLOB 1671 if binary_function_repr: 1672 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1673 return self.func(binary_function_repr, exp.Literal.string(this)) 1674 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1675 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1676 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1677 1678 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1679 1680 def bytestring_sql(self, expression: exp.ByteString) -> str: 1681 this = self.sql(expression, "this") 1682 is_bytes = bool(expression.args.get("is_bytes")) 1683 if is_bytes and self.BYTE_STRING_FUNCTION: 1684 # Hex digits can be quoted directly without escaping. 1685 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1686 1687 if self.dialect.BYTE_START: 1688 escaped_byte_string = self.escape_str( 1689 this, 1690 escape_backslash=bool( 1691 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1692 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1693 ), 1694 delimiter=self.dialect.BYTE_END, 1695 escaped_delimiter=self._escaped_byte_quote_end, 1696 is_byte_string=True, 1697 is_bytes=is_bytes, 1698 ) 1699 delimited_byte_string = ( 1700 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1701 ) 1702 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1703 return self.sql( 1704 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1705 ) 1706 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1707 return self.sql( 1708 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1709 ) 1710 1711 return delimited_byte_string 1712 1713 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1714 return self.sql(exp.Literal.string(this)) 1715 1716 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1717 return "" 1718 1719 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1720 this = self.sql(expression, "this") 1721 escape = expression.args.get("escape") 1722 unicode_start = self.dialect.UNICODE_START 1723 1724 if unicode_start: 1725 escape_substitute = r"\\\1" 1726 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1727 else: 1728 escape_substitute = r"\\u\1" 1729 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1730 1731 if escape: 1732 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1733 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1734 else: 1735 escape_pattern = ESCAPED_UNICODE_RE 1736 escape_sql = "" 1737 1738 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1739 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1740 1741 if unicode_start: 1742 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1743 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1744 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1745 else: 1746 this = self.escape_str(this, escape_backslash=False) 1747 1748 return f"{left_quote}{this}{right_quote}{escape_sql}" 1749 1750 def rawstring_sql(self, expression: exp.RawString) -> str: 1751 string = expression.this 1752 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1753 string = string.replace("\\", "\\\\") 1754 1755 string = self.escape_str(string, escape_backslash=False) 1756 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1757 1758 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1759 this = self.sql(expression, "this") 1760 specifier = self.sql(expression, "expression") 1761 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1762 return f"{this}{specifier}" 1763 1764 def datatype_param_bound_limiter( 1765 self, 1766 expression: exp.DataType, 1767 type_value: exp.DType, 1768 defaults: tuple[int, ...], 1769 bounds: tuple[int | None, ...], 1770 ) -> exp.DataType: 1771 params = expression.expressions 1772 1773 if not params: 1774 if defaults: 1775 expression.set( 1776 "expressions", 1777 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1778 ) 1779 return expression 1780 1781 if not bounds: 1782 return expression 1783 1784 for i, param in enumerate(params): 1785 bound = bounds[i] if i < len(bounds) else None 1786 if bound is None: 1787 continue 1788 1789 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1790 value = ( 1791 param_value.to_py() 1792 if isinstance(param_value, exp.Literal) and param_value.is_number 1793 else None 1794 ) 1795 if isinstance(value, (int, Decimal)) and value > bound: 1796 self.unsupported( 1797 f"{type_value.value} parameter {param_value.name} exceeds " 1798 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1799 ) 1800 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1801 1802 return expression 1803 1804 def datatype_sql(self, expression: exp.DataType) -> str: 1805 nested = "" 1806 values = "" 1807 1808 expr_nested = expression.args.get("nested") 1809 type_value = expression.this 1810 1811 if ( 1812 not expr_nested 1813 and isinstance(type_value, exp.DType) 1814 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1815 ): 1816 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1817 1818 interior = ( 1819 self.expressions( 1820 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1821 ) 1822 if expr_nested and self.pretty 1823 else self.expressions(expression, flat=True) 1824 ) 1825 1826 if type_value in self.UNSUPPORTED_TYPES: 1827 self.unsupported( 1828 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1829 ) 1830 1831 type_sql: t.Any = "" 1832 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1833 type_sql = self.sql(expression, "kind") 1834 elif type_value == exp.DType.CHARACTER_SET: 1835 char = f"CHAR({interior})" if interior else "CHAR" 1836 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1837 else: 1838 type_sql = ( 1839 self.TYPE_MAPPING.get(type_value, type_value.value) 1840 if isinstance(type_value, exp.DType) 1841 else type_value 1842 ) 1843 1844 if interior: 1845 if expr_nested: 1846 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1847 if expression.args.get("values") is not None: 1848 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1849 values = self.expressions(expression, key="values", flat=True) 1850 values = f"{delimiters[0]}{values}{delimiters[1]}" 1851 elif type_value == exp.DType.INTERVAL: 1852 nested = f" {interior}" 1853 else: 1854 nested = f"({interior})" 1855 1856 type_sql = f"{type_sql}{nested}{values}" 1857 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1858 exp.DType.TIMETZ, 1859 exp.DType.TIMESTAMPTZ, 1860 ): 1861 type_sql = f"{type_sql} WITH TIME ZONE" 1862 1863 collate = self.sql(expression, "collate") 1864 if collate: 1865 type_sql = f"{type_sql} COLLATE {collate}" 1866 1867 return type_sql 1868 1869 def directory_sql(self, expression: exp.Directory) -> str: 1870 local = "LOCAL " if expression.args.get("local") else "" 1871 row_format = self.sql(expression, "row_format") 1872 row_format = f" {row_format}" if row_format else "" 1873 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1874 1875 def delete_sql(self, expression: exp.Delete) -> str: 1876 hint = self.sql(expression, "hint") 1877 this = self.sql(expression, "this") 1878 this = f" FROM {this}" if this else "" 1879 using = self.expressions(expression, key="using") 1880 using = f" USING {using}" if using else "" 1881 cluster = self.sql(expression, "cluster") 1882 cluster = f" {cluster}" if cluster else "" 1883 where = self.sql(expression, "where") 1884 returning = self.sql(expression, "returning") 1885 order = self.sql(expression, "order") 1886 limit = self.sql(expression, "limit") 1887 tables = self.expressions(expression, key="tables") 1888 tables = f" {tables}" if tables else "" 1889 if self.RETURNING_END: 1890 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1891 else: 1892 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1893 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1894 1895 def drop_sql(self, expression: exp.Drop) -> str: 1896 tables = self.expressions(expression, key="tables", flat=True) 1897 expressions = self.expressions(expression, flat=True) 1898 expressions = f" ({expressions})" if expressions else "" 1899 kind = expression.args["kind"] 1900 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1901 iceberg = ( 1902 " ICEBERG" 1903 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1904 else "" 1905 ) 1906 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1907 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1908 on_cluster = self.sql(expression, "cluster") 1909 on_cluster = f" {on_cluster}" if on_cluster else "" 1910 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1911 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1912 cascade = " CASCADE" if expression.args.get("cascade") else "" 1913 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1914 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1915 purge = " PURGE" if expression.args.get("purge") else "" 1916 sync = " SYNC" if expression.args.get("sync") else "" 1917 force = " FORCE" if expression.args.get("force") else "" 1918 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1919 1920 def set_operation(self, expression: exp.SetOperation) -> str: 1921 op_type = type(expression) 1922 op_name = op_type.key.upper() 1923 1924 distinct = expression.args.get("distinct") 1925 if ( 1926 distinct is False 1927 and op_type in (exp.Except, exp.Intersect) 1928 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1929 ): 1930 self.unsupported(f"{op_name} ALL is not supported") 1931 1932 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1933 1934 if distinct is None: 1935 distinct = default_distinct 1936 if distinct is None: 1937 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1938 1939 if distinct is default_distinct: 1940 distinct_or_all = "" 1941 else: 1942 distinct_or_all = " DISTINCT" if distinct else " ALL" 1943 1944 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1945 side_kind = f"{side_kind} " if side_kind else "" 1946 1947 by_name = " BY NAME" if expression.args.get("by_name") else "" 1948 on = self.expressions(expression, key="on", flat=True) 1949 on = f" ON ({on})" if on else "" 1950 1951 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1952 1953 def set_operations(self, expression: exp.SetOperation) -> str: 1954 if not self.SET_OP_MODIFIERS: 1955 limit = expression.args.get("limit") 1956 order = expression.args.get("order") 1957 offset = expression.args.get("offset") 1958 1959 if limit or order or offset: 1960 select = self._move_ctes_to_top_level( 1961 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1962 ) 1963 1964 for arg in ("limit", "order", "offset"): 1965 if value := expression.args.get(arg): 1966 select.set(arg, value.pop()) 1967 return self.sql(select) 1968 1969 sqls: list[str] = [] 1970 stack: list[str | exp.Expr | int | None] = [expression] 1971 depth = 0 1972 open_parens = "" 1973 1974 while stack: 1975 node = stack.pop() 1976 1977 if isinstance(node, int): 1978 # Closes a plain grouping opened below, restoring its depth 1979 depth = node 1980 sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0) 1981 elif isinstance(node, exp.SetOperation): 1982 stack.append(node.expression) 1983 stack.append( 1984 self.maybe_comment( 1985 self.set_operation(node), comments=node.comments, separated=True 1986 ) 1987 ) 1988 left = node.this 1989 if ( 1990 not self.SET_OP_PARENTHESIZED_OPERANDS 1991 and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 1992 ): 1993 # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper: 1994 # it renames duplicate columns and deepens recursion. Inner CTEs and 1995 # modifiers still need the wrapper. 1996 wrappers: list[exp.Subquery] = [] 1997 inner = left 1998 while isinstance(inner, exp.Subquery) and inner.is_wrapper: 1999 wrappers.append(inner) 2000 inner = inner.this 2001 2002 if ( 2003 isinstance(inner, exp.SetOperation) 2004 and not inner.args.get("with_") 2005 and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS) 2006 ): 2007 for wrapper in wrappers: 2008 inner.add_comments(wrapper.pop_comments()) 2009 left = inner 2010 elif ( 2011 self.SET_OP_PARENTHESIZED_OPERANDS 2012 and isinstance(left, exp.Subquery) 2013 and left.is_wrapper 2014 and not left.comments 2015 and isinstance(left.this, exp.SetOperation) 2016 and not left.this.args.get("with_") 2017 and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS) 2018 ): 2019 # Write a plain grouping's parentheses here rather than recursing through 2020 # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and 2021 # long ones would exhaust the stack. The depth pushed here marks the close. 2022 open_parens += self.indent("(", level=depth, pad=0) + self.sep("") 2023 stack.append(depth) 2024 depth += 1 2025 left = left.this 2026 stack.append(left) 2027 else: 2028 if ( 2029 not self.SET_OP_LIMITS 2030 and isinstance(node, exp.Select) 2031 and node.args.get("limit") 2032 ): 2033 node = node.subquery(copy=False) 2034 if not self.SET_OP_PARENTHESIZED_OPERANDS: 2035 node = exp.select("*").from_(node, copy=False) 2036 elif ( 2037 not self.SET_OP_PARENTHESIZED_OPERANDS 2038 and isinstance(node, exp.Subquery) 2039 and isinstance(node.unnest(), exp.SetOperation) 2040 ): 2041 node = exp.select("*").from_(node, copy=False) 2042 sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0)) 2043 open_parens = "" 2044 2045 this = self.sep().join(sqls) 2046 this = self.query_modifiers(expression, this) 2047 return self.prepend_ctes(expression, this) 2048 2049 def fetch_sql(self, expression: exp.Fetch) -> str: 2050 direction = expression.args.get("direction") 2051 direction = f" {direction}" if direction else "" 2052 count = self.sql(expression, "count") 2053 count = f" {count}" if count else "" 2054 limit_options = self.sql(expression, "limit_options") 2055 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 2056 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 2057 2058 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 2059 percent = " PERCENT" if expression.args.get("percent") else "" 2060 rows = " ROWS" if expression.args.get("rows") else "" 2061 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 2062 if not with_ties and rows: 2063 with_ties = " ONLY" 2064 return f"{percent}{rows}{with_ties}" 2065 2066 def filter_sql(self, expression: exp.Filter) -> str: 2067 this = self.sql(expression, "this") 2068 where = self.sql(expression, "expression").strip() 2069 return f"{this} FILTER({where})" 2070 2071 def per_sql(self, expression: exp.Per) -> str: 2072 return f"{self.sql(expression, 'this')} PER ({self.expressions(expression, flat=True)})" 2073 2074 def hint_sql(self, expression: exp.Hint) -> str: 2075 if not self.QUERY_HINTS: 2076 self.unsupported("Hints are not supported") 2077 return "" 2078 2079 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2080 2081 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2082 using = self.sql(expression, "using") 2083 using = f" USING {using}" if using else "" 2084 columns = self.expressions(expression, key="columns", flat=True) 2085 columns = f"({columns})" if columns else "" 2086 partition_by = self.expressions(expression, key="partition_by", flat=True) 2087 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2088 where = self.sql(expression, "where") 2089 include = self.expressions(expression, key="include", flat=True) 2090 if include: 2091 include = f" INCLUDE ({include})" 2092 with_storage = self.expressions(expression, key="with_storage", flat=True) 2093 with_storage = f" WITH ({with_storage})" if with_storage else "" 2094 tablespace = self.sql(expression, "tablespace") 2095 if tablespace: 2096 prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX" 2097 tablespace = f"{prefix} TABLESPACE {tablespace}" 2098 on = self.sql(expression, "on") 2099 on = f" ON {on}" if on else "" 2100 2101 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2102 2103 def index_sql(self, expression: exp.Index) -> str: 2104 unique = "UNIQUE " if expression.args.get("unique") else "" 2105 primary = "PRIMARY " if expression.args.get("primary") else "" 2106 amp = "AMP " if expression.args.get("amp") else "" 2107 name = self.sql(expression, "this") 2108 name = f"{name} " if name else "" 2109 table = self.sql(expression, "table") 2110 table = f"{self.INDEX_ON} {table}" if table else "" 2111 2112 index = "INDEX " if not table else "" 2113 2114 params = self.sql(expression, "params") 2115 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2116 2117 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2118 this = expression.this 2119 if this and this.is_string: 2120 resolved = maybe_parse(this.name).sql(self.dialect) 2121 if "expressions" in expression.args: 2122 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2123 # We can't safely emit the call to other dialects since name/arg semantics may differ 2124 self.unsupported( 2125 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2126 ) 2127 return resolved 2128 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2129 return self.func("IDENTIFIER", this) 2130 2131 def identifier_sql(self, expression: exp.Identifier) -> str: 2132 text = expression.name 2133 lower = text.lower() 2134 quoted = expression.quoted 2135 text = lower if self.normalize and not quoted else text 2136 if self._identifier_escaped_sequences: 2137 for char, escaped in self._identifier_escaped_sequences.items(): 2138 text = text.replace(char, escaped) 2139 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2140 if ( 2141 quoted 2142 or self.dialect.can_quote(expression, self.identify) 2143 or lower in self.RESERVED_KEYWORDS 2144 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2145 ): 2146 text = ( 2147 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2148 ) 2149 return text 2150 2151 def hex_sql(self, expression: exp.Hex) -> str: 2152 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2153 if self.dialect.HEX_LOWERCASE: 2154 text = self.func("LOWER", text) 2155 2156 return text 2157 2158 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2159 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2160 if not self.dialect.HEX_LOWERCASE: 2161 text = self.func("LOWER", text) 2162 return text 2163 2164 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2165 input_format = self.sql(expression, "input_format") 2166 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2167 output_format = self.sql(expression, "output_format") 2168 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2169 return self.sep().join((input_format, output_format)) 2170 2171 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2172 string = self.sql(exp.Literal.string(expression.name)) 2173 return f"{prefix}{string}" 2174 2175 def partition_sql(self, expression: exp.Partition) -> str: 2176 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2177 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2178 2179 def properties_sql(self, expression: exp.Properties) -> str: 2180 root_properties = [] 2181 with_properties = [] 2182 2183 for p in expression.expressions: 2184 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2185 if p_loc == exp.Properties.Location.POST_WITH: 2186 with_properties.append(p) 2187 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2188 root_properties.append(p) 2189 2190 root_props_ast = exp.Properties(expressions=root_properties) 2191 root_props_ast.parent = expression.parent 2192 2193 with_props_ast = exp.Properties(expressions=with_properties) 2194 with_props_ast.parent = expression.parent 2195 2196 root_props = self.root_properties(root_props_ast) 2197 with_props = self.with_properties(with_props_ast) 2198 2199 if root_props and with_props and not self.pretty: 2200 with_props = " " + with_props 2201 2202 return root_props + with_props 2203 2204 def root_properties(self, properties: exp.Properties) -> str: 2205 if properties.expressions: 2206 return self.expressions(properties, indent=False, sep=" ") 2207 return "" 2208 2209 def properties( 2210 self, 2211 properties: exp.Properties, 2212 prefix: str = "", 2213 sep: str = ", ", 2214 suffix: str = "", 2215 wrapped: bool = True, 2216 ) -> str: 2217 if properties.expressions: 2218 expressions = self.expressions(properties, sep=sep, indent=False) 2219 if expressions: 2220 expressions = self.wrap(expressions) if wrapped else expressions 2221 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2222 return "" 2223 2224 def with_properties(self, properties: exp.Properties) -> str: 2225 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2226 2227 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2228 properties_locs = defaultdict(list) 2229 for p in properties.expressions: 2230 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2231 if p_loc != exp.Properties.Location.UNSUPPORTED: 2232 properties_locs[p_loc].append(p) 2233 else: 2234 self.unsupported(f"Unsupported property {p.key}") 2235 2236 return properties_locs 2237 2238 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2239 if isinstance(expression.this, exp.Dot): 2240 return self.sql(expression, "this") 2241 return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name 2242 2243 def property_sql(self, expression: exp.Property) -> str: 2244 property_cls = expression.__class__ 2245 if property_cls == exp.Property: 2246 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2247 2248 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2249 if not property_name: 2250 self.unsupported(f"Unsupported property {expression.key}") 2251 return "" 2252 2253 return f"{property_name}={self.sql(expression, 'this')}" 2254 2255 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2256 return f"UUID {self.sql(expression, 'this')}" 2257 2258 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2259 if self.SUPPORTS_CREATE_TABLE_LIKE: 2260 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2261 options = f" {options}" if options else "" 2262 2263 like = f"LIKE {self.sql(expression, 'this')}{options}" 2264 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2265 like = f"({like})" 2266 2267 return like 2268 2269 if expression.expressions: 2270 self.unsupported("Transpilation of LIKE property options is unsupported") 2271 2272 select = exp.select("*").from_(expression.this).limit(0) 2273 return f"AS {self.sql(select)}" 2274 2275 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2276 no = "NO " if expression.args.get("no") else "" 2277 protection = " PROTECTION" if expression.args.get("protection") else "" 2278 return f"{no}FALLBACK{protection}" 2279 2280 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2281 no = "NO " if expression.args.get("no") else "" 2282 local = expression.args.get("local") 2283 local = f"{local} " if local else "" 2284 dual = "DUAL " if expression.args.get("dual") else "" 2285 before = "BEFORE " if expression.args.get("before") else "" 2286 after = "AFTER " if expression.args.get("after") else "" 2287 return f"{no}{local}{dual}{before}{after}JOURNAL" 2288 2289 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2290 freespace = self.sql(expression, "this") 2291 percent = " PERCENT" if expression.args.get("percent") else "" 2292 return f"FREESPACE={freespace}{percent}" 2293 2294 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2295 if expression.args.get("default"): 2296 property = "DEFAULT" 2297 elif expression.args.get("on"): 2298 property = "ON" 2299 else: 2300 property = "OFF" 2301 return f"CHECKSUM={property}" 2302 2303 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2304 if expression.args.get("no"): 2305 return "NO MERGEBLOCKRATIO" 2306 if expression.args.get("default"): 2307 return "DEFAULT MERGEBLOCKRATIO" 2308 2309 percent = " PERCENT" if expression.args.get("percent") else "" 2310 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2311 2312 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2313 expressions = self.expressions(expression, flat=True) 2314 expressions = f"({expressions})" if expressions else "" 2315 return f"USING {self.sql(expression, 'this')}{expressions}" 2316 2317 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2318 default = expression.args.get("default") 2319 minimum = expression.args.get("minimum") 2320 maximum = expression.args.get("maximum") 2321 if default or minimum or maximum: 2322 if default: 2323 prop = "DEFAULT" 2324 elif minimum: 2325 prop = "MINIMUM" 2326 else: 2327 prop = "MAXIMUM" 2328 return f"{prop} DATABLOCKSIZE" 2329 units = expression.args.get("units") 2330 units = f" {units}" if units else "" 2331 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2332 2333 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2334 autotemp = expression.args.get("autotemp") 2335 always = expression.args.get("always") 2336 default = expression.args.get("default") 2337 manual = expression.args.get("manual") 2338 never = expression.args.get("never") 2339 2340 if autotemp is not None: 2341 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2342 elif always: 2343 prop = "ALWAYS" 2344 elif default: 2345 prop = "DEFAULT" 2346 elif manual: 2347 prop = "MANUAL" 2348 elif never: 2349 prop = "NEVER" 2350 return f"BLOCKCOMPRESSION={prop}" 2351 2352 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2353 no = expression.args.get("no") 2354 no = " NO" if no else "" 2355 concurrent = expression.args.get("concurrent") 2356 concurrent = " CONCURRENT" if concurrent else "" 2357 target = self.sql(expression, "target") 2358 target = f" {target}" if target else "" 2359 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2360 2361 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2362 if isinstance(expression.this, list): 2363 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2364 if expression.this: 2365 modulus = self.sql(expression, "this") 2366 remainder = self.sql(expression, "expression") 2367 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2368 2369 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2370 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2371 return f"FROM ({from_expressions}) TO ({to_expressions})" 2372 2373 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2374 this = self.sql(expression, "this") 2375 2376 for_values_or_default = expression.expression 2377 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2378 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2379 else: 2380 for_values_or_default = " DEFAULT" 2381 2382 return f"PARTITION OF {this}{for_values_or_default}" 2383 2384 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2385 kind = expression.args.get("kind") 2386 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2387 for_or_in = expression.args.get("for_or_in") 2388 for_or_in = f" {for_or_in}" if for_or_in else "" 2389 lock_type = expression.args.get("lock_type") 2390 override = " OVERRIDE" if expression.args.get("override") else "" 2391 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2392 2393 def lockingstatement_sql(self, expression: exp.LockingStatement) -> str: 2394 self.unsupported("LOCKING request modifier is not supported") 2395 return self.sql(expression, "expression") 2396 2397 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2398 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2399 statistics = expression.args.get("statistics") 2400 statistics_sql = "" 2401 if statistics is not None: 2402 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2403 return f"{data_sql}{statistics_sql}" 2404 2405 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2406 this = self.sql(expression, "this") 2407 this = f"HISTORY_TABLE={this}" if this else "" 2408 data_consistency: str | None = self.sql(expression, "data_consistency") 2409 data_consistency = ( 2410 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2411 ) 2412 retention_period: str | None = self.sql(expression, "retention_period") 2413 retention_period = ( 2414 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2415 ) 2416 2417 if this: 2418 on_sql = self.func("ON", this, data_consistency, retention_period) 2419 else: 2420 on_sql = "ON" if expression.args.get("on") else "OFF" 2421 2422 sql = f"SYSTEM_VERSIONING={on_sql}" 2423 2424 return f"WITH({sql})" if expression.args.get("with_") else sql 2425 2426 def insert_sql(self, expression: exp.Insert) -> str: 2427 hint = self.sql(expression, "hint") 2428 overwrite = expression.args.get("overwrite") 2429 2430 if isinstance(expression.this, exp.Directory): 2431 this = " OVERWRITE" if overwrite else " INTO" 2432 else: 2433 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2434 2435 stored = self.sql(expression, "stored") 2436 stored = f" {stored}" if stored else "" 2437 alternative = expression.args.get("alternative") 2438 alternative = f" OR {alternative}" if alternative else "" 2439 ignore = " IGNORE" if expression.args.get("ignore") else "" 2440 is_function = expression.args.get("is_function") 2441 if is_function: 2442 this = f"{this} FUNCTION" 2443 this = f"{this} {self.sql(expression, 'this')}" 2444 2445 exists = " IF EXISTS" if expression.args.get("exists") else "" 2446 where = self.sql(expression, "where") 2447 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2448 using = self.expressions(expression, key="using", flat=True) 2449 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2450 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2451 on_conflict = self.sql(expression, "conflict") 2452 on_conflict = f" {on_conflict}" if on_conflict else "" 2453 by_name = " BY NAME" if expression.args.get("by_name") else "" 2454 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2455 returning = self.sql(expression, "returning") 2456 2457 if self.RETURNING_END: 2458 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2459 else: 2460 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2461 2462 partition_by = self.sql(expression, "partition") 2463 partition_by = f" {partition_by}" if partition_by else "" 2464 settings = self.sql(expression, "settings") 2465 settings = f" {settings}" if settings else "" 2466 2467 source = self.sql(expression, "source") 2468 source = f"TABLE {source}" if source else "" 2469 2470 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2471 return self.prepend_ctes(expression, sql) 2472 2473 def introducer_sql(self, expression: exp.Introducer) -> str: 2474 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2475 2476 def kill_sql(self, expression: exp.Kill) -> str: 2477 kind = self.sql(expression, "kind") 2478 kind = f" {kind}" if kind else "" 2479 this = self.sql(expression, "this") 2480 this = f" {this}" if this else "" 2481 return f"KILL{kind}{this}" 2482 2483 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2484 return expression.name 2485 2486 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2487 return expression.name 2488 2489 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2490 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2491 2492 constraint = self.sql(expression, "constraint") 2493 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2494 2495 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2496 if conflict_keys: 2497 conflict_keys = f"({conflict_keys})" 2498 2499 index_predicate = self.sql(expression, "index_predicate") 2500 conflict_keys = f"{conflict_keys}{index_predicate} " 2501 2502 action = self.sql(expression, "action") 2503 2504 expressions = self.expressions(expression, flat=True) 2505 if expressions: 2506 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2507 expressions = f" {set_keyword}{expressions}" 2508 2509 where = self.sql(expression, "where") 2510 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2511 2512 def returning_sql(self, expression: exp.Returning) -> str: 2513 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2514 2515 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2516 fields = self.sql(expression, "fields") 2517 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2518 escaped = self.sql(expression, "escaped") 2519 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2520 items = self.sql(expression, "collection_items") 2521 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2522 keys = self.sql(expression, "map_keys") 2523 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2524 lines = self.sql(expression, "lines") 2525 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2526 null = self.sql(expression, "null") 2527 null = f" NULL DEFINED AS {null}" if null else "" 2528 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2529 2530 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2531 return f"WITH ({self.expressions(expression, flat=True)})" 2532 2533 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2534 this = f"{self.sql(expression, 'this')} INDEX" 2535 target = self.sql(expression, "target") 2536 target = f" FOR {target}" if target else "" 2537 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2538 2539 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2540 this = self.sql(expression, "this") 2541 kind = self.sql(expression, "kind") 2542 expr = self.sql(expression, "expression") 2543 return f"{this} ({kind} => {expr})" 2544 2545 def table_parts(self, expression: exp.Table) -> str: 2546 return ".".join( 2547 self.sql(part) 2548 for part in ( 2549 expression.args.get("catalog"), 2550 expression.args.get("db"), 2551 expression.args.get("this"), 2552 ) 2553 if part is not None 2554 ) 2555 2556 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2557 table = self.table_parts(expression) 2558 only = "ONLY " if expression.args.get("only") else "" 2559 partition = self.sql(expression, "partition") 2560 partition = f" {partition}" if partition else "" 2561 version = self.sql(expression, "version") 2562 version = f" {version}" if version else "" 2563 alias = self.sql(expression, "alias") 2564 alias = f"{sep}{alias}" if alias else "" 2565 2566 sample = self.sql(expression, "sample") 2567 post_alias = "" 2568 pre_alias = "" 2569 2570 if self.dialect.ALIAS_POST_TABLESAMPLE: 2571 pre_alias = sample 2572 else: 2573 post_alias = sample 2574 2575 if self.dialect.ALIAS_POST_VERSION: 2576 pre_alias = f"{pre_alias}{version}" 2577 else: 2578 post_alias = f"{post_alias}{version}" 2579 2580 hints = self.expressions(expression, key="hints", sep=" ") 2581 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2582 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2583 joins = self.indent( 2584 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2585 ) 2586 laterals = self.expressions(expression, key="laterals", sep="") 2587 2588 file_format = self.sql(expression, "format") 2589 pattern = self.sql(expression, "pattern") 2590 if file_format: 2591 pattern = f", PATTERN => {pattern}" if pattern else "" 2592 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2593 elif pattern: 2594 file_format = f" (PATTERN => {pattern})" 2595 2596 ordinality = expression.args.get("ordinality") or "" 2597 if ordinality: 2598 ordinality = f" WITH ORDINALITY{alias}" 2599 alias = "" 2600 2601 when = self.sql(expression, "when") 2602 if when: 2603 if self.HISTORICAL_DATA_POST_ALIAS: 2604 alias = f"{alias} {when}" 2605 else: 2606 table = f"{table} {when}" 2607 2608 changes = self.sql(expression, "changes") 2609 changes = f" {changes}" if changes else "" 2610 2611 rows_from = self.expressions(expression, key="rows_from") 2612 if rows_from: 2613 table = f"ROWS FROM {self.wrap(rows_from)}" 2614 2615 indexed = expression.args.get("indexed") 2616 if indexed is not None: 2617 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2618 else: 2619 indexed = "" 2620 2621 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2622 2623 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2624 table = self.func("TABLE", expression.this) 2625 alias = self.sql(expression, "alias") 2626 alias = f" AS {alias}" if alias else "" 2627 sample = self.sql(expression, "sample") 2628 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2629 joins = self.indent( 2630 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2631 ) 2632 return f"{table}{alias}{pivots}{sample}{joins}" 2633 2634 def tablesample_sql( 2635 self, 2636 expression: exp.TableSample, 2637 tablesample_keyword: str | None = None, 2638 ) -> str: 2639 if expression.expressions: 2640 self.unsupported("Multiple sample sets are not supported") 2641 return "" 2642 2643 method = self.sql(expression, "method") 2644 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2645 numerator = self.sql(expression, "bucket_numerator") 2646 denominator = self.sql(expression, "bucket_denominator") 2647 field = self.sql(expression, "bucket_field") 2648 field = f" ON {field}" if field else "" 2649 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2650 seed = self.sql(expression, "seed") 2651 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2652 2653 size = self.sql(expression, "size") 2654 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2655 size = f"{size} ROWS" 2656 2657 percent = self.sql(expression, "percent") 2658 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2659 percent = f"{percent} PERCENT" 2660 2661 expr = f"{bucket}{percent}{size}" 2662 if self.TABLESAMPLE_REQUIRES_PARENS: 2663 expr = f"({expr})" 2664 2665 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2666 2667 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2668 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2669 # the stored column name differs from the target dialect's natural output. 2670 columns = expression.args.get("columns") 2671 if not columns or len(expression.fields) != 1: 2672 return None 2673 2674 args = expression.args 2675 parser_cls = self.dialect.parser_class 2676 2677 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2678 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2679 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2680 2681 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2682 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2683 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2684 2685 if ( 2686 src_identify_pivot_strings == tgt_identify_pivot_strings 2687 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2688 and src_pivot_column_naming == tgt_pivot_column_naming 2689 ): 2690 return None 2691 2692 in_exprs = expression.fields[0].expressions 2693 step = len(columns) // len(in_exprs) 2694 2695 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2696 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2697 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2698 first_stored = columns[0].name 2699 2700 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2701 if not first_stored.startswith(first_base): 2702 return None 2703 2704 suffix = first_stored[len(first_base) :] 2705 2706 # Whether the target dialect would append an agg-name suffix for this pivot. 2707 # Spark single-agg uniquely drops the agg alias entirely. 2708 target_has_suffix = ( 2709 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2710 ) and any(a.alias for a in expression.expressions) 2711 source_has_suffix = suffix != "" 2712 2713 new_exprs: list[exp.Expression] = [] 2714 modified = False 2715 for val_idx, e in enumerate(in_exprs): 2716 if isinstance(e, exp.PivotAlias): 2717 new_exprs.append(e) 2718 continue 2719 2720 i = val_idx * step 2721 stored_full = columns[i].name 2722 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2723 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2724 2725 # Source had a suffix, but target won't apply one 2726 if source_has_suffix and not target_has_suffix: 2727 new_exprs.append( 2728 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2729 ) 2730 modified = True 2731 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2732 elif stored_value != target_value: 2733 new_exprs.append( 2734 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2735 ) 2736 modified = True 2737 else: 2738 new_exprs.append(e) 2739 2740 return new_exprs if modified else None 2741 2742 def pivot_sql(self, expression: exp.Pivot) -> str: 2743 expressions = self.expressions(expression, flat=True) 2744 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2745 2746 group = self.sql(expression, "group") 2747 2748 if expression.this: 2749 this = self.sql(expression, "this") 2750 if not expressions: 2751 sql = f"UNPIVOT {this}" 2752 else: 2753 on = f"{self.seg('ON')} {expressions}" 2754 into = self.sql(expression, "into") 2755 into = f"{self.seg('INTO')} {into}" if into else "" 2756 using = self.expressions(expression, key="using", flat=True) 2757 using = f"{self.seg('USING')} {using}" if using else "" 2758 sql = f"{direction} {this}{on}{into}{using}{group}" 2759 return self.prepend_ctes(expression, sql) 2760 2761 if not expression.unpivot: 2762 # Wrap IN-list values with explicit aliases where the target dialect would differ 2763 new_field_exprs = self._pivot_in_value_aliases(expression) 2764 if new_field_exprs is not None: 2765 expression.fields[0].set("expressions", new_field_exprs) 2766 2767 alias = self.sql(expression, "alias") 2768 if alias: 2769 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2770 2771 fields = self.expressions( 2772 expression, 2773 "fields", 2774 sep=" ", 2775 dynamic=True, 2776 new_line=True, 2777 skip_first=True, 2778 skip_last=True, 2779 ) 2780 2781 include_nulls = expression.args.get("include_nulls") 2782 if include_nulls is not None: 2783 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2784 else: 2785 nulls = "" 2786 2787 default_on_null = self.sql(expression, "default_on_null") 2788 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2789 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2790 return self.prepend_ctes(expression, sql) 2791 2792 def version_sql(self, expression: exp.Version) -> str: 2793 this = f"FOR {expression.name}" 2794 kind = expression.text("kind") 2795 expr = self.sql(expression, "expression") 2796 return f"{this} {kind} {expr}" 2797 2798 def tuple_sql(self, expression: exp.Tuple) -> str: 2799 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2800 2801 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2802 """ 2803 Returns (join_sql, from_sql) for UPDATE statements. 2804 - join_sql: placed after UPDATE table, before SET 2805 - from_sql: placed after SET clause (standard position) 2806 Dialects like MySQL need to convert FROM to JOIN syntax. 2807 """ 2808 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2809 return ("", self.sql(expression, "from_")) 2810 2811 # Qualify unqualified columns in SET clause with the target table 2812 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2813 target_table = expression.this 2814 if isinstance(target_table, exp.Table): 2815 target_name = exp.to_identifier(target_table.alias_or_name) 2816 for eq in expression.expressions: 2817 col = eq.this 2818 if isinstance(col, exp.Column) and not col.table: 2819 col.set("table", target_name) 2820 2821 table = from_expr.this 2822 if nested_joins := table.args.get("joins", []): 2823 table.set("joins", None) 2824 2825 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2826 for nested in nested_joins: 2827 if not nested.args.get("on") and not nested.args.get("using"): 2828 nested.set("on", exp.true()) 2829 join_sql += self.sql(nested) 2830 2831 return (join_sql, "") 2832 2833 def update_sql(self, expression: exp.Update) -> str: 2834 hint = self.sql(expression, "hint") 2835 this = self.sql(expression, "this") 2836 join_sql, from_sql = self._update_from_joins_sql(expression) 2837 set_sql = self.expressions(expression, flat=True) 2838 where_sql = self.sql(expression, "where") 2839 returning = self.sql(expression, "returning") 2840 order = self.sql(expression, "order") 2841 limit = self.sql(expression, "limit") 2842 if self.RETURNING_END: 2843 expression_sql = f"{from_sql}{where_sql}{returning}" 2844 else: 2845 expression_sql = f"{returning}{from_sql}{where_sql}" 2846 options = self.expressions(expression, key="options") 2847 options = f" OPTION({options})" if options else "" 2848 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2849 return self.prepend_ctes(expression, sql) 2850 2851 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2852 values_as_table = values_as_table and self.VALUES_AS_TABLE 2853 2854 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2855 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2856 args = self.expressions(expression) 2857 alias = self.sql(expression, "alias") 2858 values = f"VALUES{self.seg('')}{args}" 2859 values = ( 2860 f"({values})" 2861 if self.WRAP_DERIVED_VALUES 2862 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2863 else values 2864 ) 2865 values = self.query_modifiers(expression, values) 2866 return f"{values} AS {alias}" if alias else values 2867 2868 # Converts `VALUES...` expression into a series of select unions. 2869 alias_node = expression.args.get("alias") 2870 column_names = alias_node and alias_node.columns 2871 2872 selects: list[exp.Query] = [] 2873 2874 for i, tup in enumerate(expression.expressions): 2875 row = tup.expressions 2876 2877 if i == 0 and column_names: 2878 row = [ 2879 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2880 ] 2881 2882 selects.append(exp.Select(expressions=row)) 2883 2884 if self.pretty: 2885 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2886 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2887 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2888 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2889 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2890 2891 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2892 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2893 return f"({unions}){alias}" 2894 2895 def var_sql(self, expression: exp.Var) -> str: 2896 return self.sql(expression, "this") 2897 2898 @unsupported_args("expressions") 2899 def into_sql(self, expression: exp.Into) -> str: 2900 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2901 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2902 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2903 2904 def from_sql(self, expression: exp.From) -> str: 2905 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2906 2907 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2908 grouping_sets = self.expressions(expression, indent=False) 2909 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2910 2911 def rollup_sql(self, expression: exp.Rollup) -> str: 2912 expressions = self.expressions(expression, indent=False) 2913 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2914 2915 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2916 this = self.sql(expression, "this") 2917 2918 columns = self.expressions(expression, flat=True) 2919 2920 from_sql = self.sql(expression, "from_index") 2921 from_sql = f" FROM {from_sql}" if from_sql else "" 2922 2923 properties = expression.args.get("properties") 2924 properties_sql = ( 2925 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2926 ) 2927 2928 return f"{this}({columns}){from_sql}{properties_sql}" 2929 2930 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2931 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2932 2933 def cube_sql(self, expression: exp.Cube) -> str: 2934 expressions = self.expressions(expression, indent=False) 2935 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2936 2937 def group_sql(self, expression: exp.Group) -> str: 2938 group_by_all = expression.args.get("all") 2939 if group_by_all is True: 2940 modifier = " ALL" 2941 elif group_by_all is False: 2942 modifier = " DISTINCT" 2943 else: 2944 modifier = "" 2945 2946 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2947 2948 grouping_sets = self.expressions(expression, key="grouping_sets") 2949 cube = self.expressions(expression, key="cube") 2950 rollup = self.expressions(expression, key="rollup") 2951 2952 groupings = csv( 2953 self.seg(grouping_sets) if grouping_sets else "", 2954 self.seg(cube) if cube else "", 2955 self.seg(rollup) if rollup else "", 2956 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2957 sep=self.GROUPINGS_SEP, 2958 ) 2959 2960 if ( 2961 expression.expressions 2962 and groupings 2963 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2964 ): 2965 add_separator = True 2966 2967 if grouping_sets: 2968 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2969 add_separator = False 2970 else: 2971 self.unsupported( 2972 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2973 ) 2974 2975 if add_separator: 2976 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2977 2978 return f"{group_by}{groupings}" 2979 2980 def having_sql(self, expression: exp.Having) -> str: 2981 this = self.indent(self.sql(expression, "this")) 2982 return f"{self.seg('HAVING')}{self.sep()}{this}" 2983 2984 def connectto_sql(self, expression: exp.ConnectTo) -> str: 2985 return f"CONNECT {self.sql(expression, 'this')}" 2986 2987 def disconnect_sql(self, expression: exp.Disconnect) -> str: 2988 return "DISCONNECT" 2989 2990 def connect_sql(self, expression: exp.Connect) -> str: 2991 start = self.sql(expression, "start") 2992 start = self.seg(f"START WITH {start}") if start else "" 2993 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2994 connect = self.sql(expression, "connect") 2995 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2996 return start + connect 2997 2998 def prior_sql(self, expression: exp.Prior) -> str: 2999 return f"PRIOR {self.sql(expression, 'this')}" 3000 3001 def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str: 3002 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 3003 side = None 3004 else: 3005 side = expression.side 3006 3007 op_sql = " ".join( 3008 op 3009 for op in ( 3010 expression.method, 3011 "GLOBAL" if expression.args.get("global_") else None, 3012 side, 3013 expression.kind, 3014 expression.hint if self.JOIN_HINTS else None, 3015 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 3016 ) 3017 if op 3018 ) 3019 match_cond = self.sql(expression, "match_condition") 3020 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 3021 nearest = self.sql(expression, "nearest") 3022 nearest = f" {nearest}" if nearest else "" 3023 on_sql = self.sql(expression, "on") 3024 using = expression.args.get("using") 3025 3026 if not on_sql and using: 3027 on_sql = csv(*(self.sql(column) for column in using)) 3028 3029 this = expression.this 3030 this_sql = self.sql(this) 3031 3032 exprs = self.expressions(expression) 3033 if exprs: 3034 this_sql = f"{this_sql},{self.seg(exprs)}" 3035 3036 if on_sql: 3037 on_sql = self.indent(on_sql, skip_first=True) 3038 space = self.seg(" " * self.pad) if self.pretty else " " 3039 if using: 3040 on_sql = f"{space}USING ({on_sql})" 3041 else: 3042 on_sql = f"{space}ON {on_sql}" 3043 elif not op_sql and not nearest: 3044 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 3045 return f" {this_sql}" 3046 3047 return f", {this_sql}" 3048 3049 if join_op: 3050 op_sql = join_op 3051 elif op_sql != "STRAIGHT_JOIN": 3052 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 3053 3054 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3055 return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}" 3056 3057 def nearest_sql(self, expression: exp.Nearest) -> str: 3058 if not self.SUPPORTS_NEAREST_JOIN: 3059 self.unsupported("NEAREST joins are not supported") 3060 3061 approximate = "APPROX " if expression.args.get("approximate") else "" 3062 metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE" 3063 return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}" 3064 3065 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 3066 args = self.expressions(expression, flat=True) 3067 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 3068 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 3069 3070 def lateral_op(self, expression: exp.Lateral) -> str: 3071 cross_apply = expression.args.get("cross_apply") 3072 3073 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 3074 if cross_apply is True: 3075 op = "INNER JOIN " 3076 elif cross_apply is False: 3077 op = "LEFT JOIN " 3078 else: 3079 op = "" 3080 3081 return f"{op}LATERAL" 3082 3083 def lateral_sql(self, expression: exp.Lateral) -> str: 3084 this = self.sql(expression, "this") 3085 3086 if expression.args.get("view"): 3087 alias = expression.args["alias"] 3088 columns = self.expressions(alias, key="columns", flat=True) 3089 table = f" {alias.name}" if alias.name else "" 3090 columns = f" AS {columns}" if columns else "" 3091 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 3092 return f"{op_sql}{self.sep()}{this}{table}{columns}" 3093 3094 table_alias = expression.args.get("alias") 3095 offset = expression.this.args.get("offset") 3096 3097 if ( 3098 self.UNNEST_WITH_ORDINALITY 3099 and table_alias 3100 and isinstance(expression.this, exp.Unnest) 3101 and isinstance(offset, exp.Identifier) 3102 ): 3103 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3104 table_alias = table_alias.copy() 3105 table_alias.append("columns", offset.copy()) 3106 3107 alias = self.sql(table_alias) 3108 alias = f" AS {alias}" if alias else "" 3109 3110 ordinality = expression.args.get("ordinality") or "" 3111 if ordinality: 3112 ordinality = f" WITH ORDINALITY{alias}" 3113 alias = "" 3114 3115 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3116 3117 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3118 this = self.sql(expression, "this") 3119 3120 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3121 return this 3122 3123 args = [ 3124 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3125 for e in (expression.args.get(k) for k in ("offset", "expression")) 3126 if e 3127 ] 3128 3129 args_sql = ", ".join(self.sql(e) for e in args) 3130 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3131 expressions = self.expressions(expression, flat=True) 3132 limit_options = self.sql(expression, "limit_options") 3133 expressions = f" BY {expressions}" if expressions else "" 3134 3135 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3136 3137 def offset_sql(self, expression: exp.Offset) -> str: 3138 this = self.sql(expression, "this") 3139 value = expression.expression 3140 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3141 expressions = self.expressions(expression, flat=True) 3142 expressions = f" BY {expressions}" if expressions else "" 3143 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3144 3145 def setitem_sql(self, expression: exp.SetItem) -> str: 3146 kind = self.sql(expression, "kind") 3147 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3148 kind = "" 3149 else: 3150 kind = f"{kind} " if kind else "" 3151 this = self.sql(expression, "this") 3152 expressions = self.expressions(expression) 3153 collate = self.sql(expression, "collate") 3154 collate = f" COLLATE {collate}" if collate else "" 3155 global_ = "GLOBAL " if expression.args.get("global_") else "" 3156 return f"{global_}{kind}{this}{expressions}{collate}" 3157 3158 def set_sql(self, expression: exp.Set) -> str: 3159 expressions = f" {self.expressions(expression, flat=True)}" 3160 tag = " TAG" if expression.args.get("tag") else "" 3161 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3162 3163 def queryband_sql(self, expression: exp.QueryBand) -> str: 3164 this = self.sql(expression, "this") 3165 update = " UPDATE" if expression.args.get("update") else "" 3166 scope = self.sql(expression, "scope") 3167 scope = f" FOR {scope}" if scope else "" 3168 3169 return f"QUERY_BAND = {this}{update}{scope}" 3170 3171 def pragma_sql(self, expression: exp.Pragma) -> str: 3172 return f"PRAGMA {self.sql(expression, 'this')}" 3173 3174 def lock_sql(self, expression: exp.Lock) -> str: 3175 if not self.LOCKING_READS_SUPPORTED: 3176 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3177 return "" 3178 3179 update = expression.args["update"] 3180 key = expression.args.get("key") 3181 if update: 3182 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3183 else: 3184 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3185 expressions = self.expressions(expression, flat=True) 3186 expressions = f" OF {expressions}" if expressions else "" 3187 wait = expression.args.get("wait") 3188 3189 if wait is not None: 3190 if isinstance(wait, exp.Literal): 3191 wait = f" WAIT {self.sql(wait)}" 3192 else: 3193 wait = " NOWAIT" if wait else " SKIP LOCKED" 3194 3195 return f"{lock_type}{expressions}{wait or ''}" 3196 3197 def literal_sql(self, expression: exp.Literal) -> str: 3198 text = expression.this or "" 3199 if expression.is_string: 3200 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3201 return text 3202 3203 def escape_str( 3204 self, 3205 text: str, 3206 escape_backslash: bool = True, 3207 delimiter: str | None = None, 3208 escaped_delimiter: str | None = None, 3209 is_byte_string: bool = False, 3210 is_bytes: bool = False, 3211 ) -> str: 3212 if is_byte_string: 3213 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3214 else: 3215 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3216 3217 if supports_escape_sequences: 3218 text = "".join( 3219 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3220 for ch in text 3221 ) 3222 3223 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3224 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3225 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3226 3227 delimiter = delimiter or self.dialect.QUOTE_END 3228 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3229 3230 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3231 3232 def loaddata_sql(self, expression: exp.LoadData) -> str: 3233 is_overwrite = expression.args.get("overwrite") 3234 overwrite = " OVERWRITE" if is_overwrite else "" 3235 this = self.sql(expression, "this") 3236 3237 files = expression.args.get("files") 3238 if files: 3239 files_sql = self.expressions(files, flat=True) 3240 files_sql = f"FILES{self.wrap(files_sql)}" 3241 if is_overwrite: 3242 this = f" {this}" 3243 elif expression.args.get("temp"): 3244 this = f" INTO TEMP TABLE {this}" 3245 else: 3246 this = f" INTO TABLE {this}" 3247 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3248 3249 local = " LOCAL" if expression.args.get("local") else "" 3250 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3251 this = f" INTO TABLE {this}" 3252 partition = self.sql(expression, "partition") 3253 partition = f" {partition}" if partition else "" 3254 input_format = self.sql(expression, "input_format") 3255 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3256 serde = self.sql(expression, "serde") 3257 serde = f" SERDE {serde}" if serde else "" 3258 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3259 3260 def null_sql(self, *_) -> str: 3261 return "NULL" 3262 3263 def boolean_sql(self, expression: exp.Boolean) -> str: 3264 return "TRUE" if expression.this else "FALSE" 3265 3266 def booland_sql(self, expression: exp.Booland) -> str: 3267 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3268 3269 def boolor_sql(self, expression: exp.Boolor) -> str: 3270 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3271 3272 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3273 this = self.sql(expression, "this") 3274 this = f"{this} " if this else this 3275 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3276 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3277 3278 def withfill_sql(self, expression: exp.WithFill) -> str: 3279 from_sql = self.sql(expression, "from_") 3280 from_sql = f" FROM {from_sql}" if from_sql else "" 3281 to_sql = self.sql(expression, "to") 3282 to_sql = f" TO {to_sql}" if to_sql else "" 3283 step_sql = self.sql(expression, "step") 3284 step_sql = f" STEP {step_sql}" if step_sql else "" 3285 interpolated_values = [ 3286 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3287 if isinstance(e, exp.Alias) 3288 else self.sql(e, "this") 3289 for e in expression.args.get("interpolate") or [] 3290 ] 3291 interpolate = ( 3292 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3293 ) 3294 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3295 3296 def cluster_sql(self, expression: exp.Cluster) -> str: 3297 return self.op_expressions("CLUSTER BY", expression) 3298 3299 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3300 if expression.this: 3301 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3302 return "" 3303 expressions = self.expressions(expression, flat=True) 3304 return f"CLUSTER BY ({expressions})" 3305 3306 def distribute_sql(self, expression: exp.Distribute) -> str: 3307 return self.op_expressions("DISTRIBUTE BY", expression) 3308 3309 def sort_sql(self, expression: exp.Sort) -> str: 3310 return self.op_expressions("SORT BY", expression) 3311 3312 def _resolve_ordered_for_null_ordering_simulation( 3313 self, expression: exp.Ordered 3314 ) -> exp.Expr | None: 3315 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3316 3317 Returns the underlying expression of the uniquely-matching projection 3318 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3319 simulation, since the CASE is evaluated in FROM-clause scope rather 3320 than alias scope (MySQL error 1052). Returns None if no safe 3321 substitution applies, leaving the original behaviour unchanged. 3322 """ 3323 this = expression.this 3324 if not (isinstance(this, exp.Column) and not this.table): 3325 return None 3326 3327 # Only a query's own ORDER BY can reference its projection aliases 3328 order = expression.parent 3329 ancestor = order.parent if isinstance(order, exp.Order) else None 3330 if not isinstance(ancestor, exp.Select): 3331 return None 3332 3333 column_name = this.name 3334 matched: list[exp.Expr] = [ 3335 p.this if isinstance(p, exp.Alias) else p 3336 for p in ancestor.selects 3337 if p.output_name == column_name 3338 ] 3339 match = matched[0] if len(matched) == 1 else None 3340 3341 # Skip the substitution when it would be identical to the existing 3342 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3343 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3344 return None 3345 3346 return match 3347 3348 def ordered_sql(self, expression: exp.Ordered) -> str: 3349 desc = expression.args.get("desc") 3350 asc = not desc 3351 3352 nulls_first = expression.args.get("nulls_first") 3353 nulls_last = not nulls_first 3354 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3355 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3356 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3357 3358 this = self.sql(expression, "this") 3359 3360 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3361 nulls_sort_change = "" 3362 if nulls_first and ( 3363 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3364 ): 3365 nulls_sort_change = " NULLS FIRST" 3366 elif ( 3367 nulls_last 3368 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3369 and not nulls_are_last 3370 ): 3371 nulls_sort_change = " NULLS LAST" 3372 3373 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3374 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3375 window = expression.find_ancestor(exp.Window, exp.Select) 3376 3377 if isinstance(window, exp.Window): 3378 window_this = window.this 3379 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3380 window_this = window_this.this 3381 spec = window.args.get("spec") 3382 else: 3383 window_this = None 3384 spec = None 3385 3386 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3387 # without a spec or with a ROWS spec, but not with RANGE 3388 if not ( 3389 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3390 and (not spec or spec.text("kind").upper() == "ROWS") 3391 ): 3392 if window_this and spec: 3393 self.unsupported( 3394 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3395 ) 3396 nulls_sort_change = "" 3397 elif self.NULL_ORDERING_SUPPORTED is False and ( 3398 (asc and nulls_sort_change == " NULLS LAST") 3399 or (desc and nulls_sort_change == " NULLS FIRST") 3400 ): 3401 # BigQuery does not allow these ordering/nulls combinations when used under 3402 # an aggregation func or under a window containing one 3403 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3404 3405 if isinstance(ancestor, exp.Window): 3406 ancestor = ancestor.this 3407 if isinstance(ancestor, exp.AggFunc): 3408 self.unsupported( 3409 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3410 ) 3411 nulls_sort_change = "" 3412 elif self.NULL_ORDERING_SUPPORTED is None: 3413 if expression.this.is_int: 3414 self.unsupported( 3415 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3416 ) 3417 elif not isinstance(expression.this, exp.Rand): 3418 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3419 target = self.sql(resolved) if resolved is not None else this 3420 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3421 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3422 nulls_sort_change = "" 3423 3424 with_fill = self.sql(expression, "with_fill") 3425 with_fill = f" {with_fill}" if with_fill else "" 3426 3427 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3428 3429 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3430 window_frame = self.sql(expression, "window_frame") 3431 window_frame = f"{window_frame} " if window_frame else "" 3432 3433 this = self.sql(expression, "this") 3434 3435 return f"{window_frame}{this}" 3436 3437 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3438 partition = self.partition_by_sql(expression) 3439 order = self.sql(expression, "order") 3440 measures = self.expressions(expression, key="measures") 3441 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3442 rows = self.sql(expression, "rows") 3443 rows = self.seg(rows) if rows else "" 3444 after = self.sql(expression, "after") 3445 after = self.seg(after) if after else "" 3446 pattern = self.sql(expression, "pattern") 3447 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3448 definition_sqls = [ 3449 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3450 for definition in expression.args.get("define", []) 3451 ] 3452 definitions = self.expressions(sqls=definition_sqls) 3453 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3454 body = "".join( 3455 ( 3456 partition, 3457 order, 3458 measures, 3459 rows, 3460 after, 3461 pattern, 3462 define, 3463 ) 3464 ) 3465 alias = self.sql(expression, "alias") 3466 alias = f" {alias}" if alias else "" 3467 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3468 3469 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3470 limit = expression.args.get("limit") 3471 3472 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3473 count = limit.args.get("count") 3474 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3475 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3476 limit = exp.Limit( 3477 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3478 ) 3479 elif ( 3480 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3481 ): 3482 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3483 3484 return csv( 3485 *sqls, 3486 *[self.sql(join) for join in expression.args.get("joins") or []], 3487 self.sql(expression, "match"), 3488 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3489 self.sql(expression, "prewhere"), 3490 self.sql(expression, "where"), 3491 self.sql(expression, "connect"), 3492 self.sql(expression, "group"), 3493 self.sql(expression, "having"), 3494 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3495 self.sql(expression, "order"), 3496 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3497 *self.after_limit_modifiers(expression), 3498 self.sql(expression, "for_"), 3499 self.options_modifier(expression), 3500 sep="", 3501 ) 3502 3503 def options_modifier(self, expression: exp.Expr) -> str: 3504 options = self.expressions(expression, key="options") 3505 return f" {options}" if options else "" 3506 3507 def forclause_sql(self, expression: exp.ForClause) -> str: 3508 kind = expression.args["kind"] 3509 if kind == "BROWSE": 3510 return f"{self.sep()}FOR BROWSE" 3511 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3512 # the target dialect doesn't support QueryOption, so we drop the clause. 3513 options = self.expressions(expression, key="expressions") 3514 if not options: 3515 return "" 3516 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3517 3518 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3519 self.unsupported("Unsupported query option.") 3520 return "" 3521 3522 def offset_limit_modifiers( 3523 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3524 ) -> list[str]: 3525 return [ 3526 self.sql(expression, "offset") if fetch else self.sql(limit), 3527 self.sql(limit) if fetch else self.sql(expression, "offset"), 3528 ] 3529 3530 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3531 locks = self.expressions(expression, key="locks", sep=" ") 3532 locks = f" {locks}" if locks else "" 3533 return [locks, self.sql(expression, "sample")] 3534 3535 def select_sql(self, expression: exp.Select) -> str: 3536 into = expression.args.get("into") 3537 if not self.SUPPORTS_SELECT_INTO and into: 3538 into.pop() 3539 3540 hint = self.sql(expression, "hint") 3541 distinct = self.sql(expression, "distinct") 3542 distinct = f" {distinct}" if distinct else "" 3543 kind = self.sql(expression, "kind") 3544 3545 limit = expression.args.get("limit") 3546 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3547 top = self.limit_sql(limit, top=True) 3548 limit.pop() 3549 else: 3550 top = "" 3551 3552 expressions = self.expressions(expression) 3553 3554 if kind: 3555 if kind in self.SELECT_KINDS: 3556 kind = f" AS {kind}" 3557 else: 3558 if kind == "STRUCT": 3559 expressions = self.expressions( 3560 sqls=[ 3561 self.sql( 3562 exp.Struct( 3563 expressions=[ 3564 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3565 if isinstance(e, exp.Alias) 3566 else e 3567 for e in expression.expressions 3568 ] 3569 ) 3570 ) 3571 ] 3572 ) 3573 kind = "" 3574 3575 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3576 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3577 3578 exclude = expression.args.get("exclude") 3579 3580 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3581 exclude_sql = self.expressions(sqls=exclude, flat=True) 3582 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3583 3584 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3585 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3586 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3587 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3588 sql = self.query_modifiers( 3589 expression, 3590 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3591 self.sql(expression, "into", comment=False), 3592 self.sql(expression, "from_", comment=False), 3593 ) 3594 3595 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3596 if expression.args.get("with_"): 3597 sql = self.maybe_comment(sql, expression) 3598 expression.pop_comments() 3599 3600 sql = self.prepend_ctes(expression, sql) 3601 3602 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3603 expression.set("exclude", None) 3604 subquery = expression.subquery(copy=False) 3605 star = exp.Star(except_=exclude) 3606 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3607 3608 if not self.SUPPORTS_SELECT_INTO and into: 3609 if into.args.get("temporary"): 3610 table_kind = " TEMPORARY" 3611 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3612 table_kind = " UNLOGGED" 3613 else: 3614 table_kind = "" 3615 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3616 3617 return sql 3618 3619 def schema_sql(self, expression: exp.Schema) -> str: 3620 this = self.sql(expression, "this") 3621 sql = self.schema_columns_sql(expression) 3622 return f"{this} {sql}" if this and sql else this or sql 3623 3624 def schema_columns_sql(self, expression: exp.Expr) -> str: 3625 if expression.expressions: 3626 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3627 return "" 3628 3629 def star_sql(self, expression: exp.Star) -> str: 3630 except_ = self.expressions(expression, key="except_", flat=True) 3631 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3632 replace = self.expressions(expression, key="replace", flat=True) 3633 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3634 rename = self.expressions(expression, key="rename", flat=True) 3635 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3636 ilike = self.sql(expression, "ilike") 3637 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3638 return f"*{ilike}{except_}{replace}{rename}" 3639 3640 def parameter_sql(self, expression: exp.Parameter) -> str: 3641 this = self.sql(expression, "this") 3642 return f"{self.PARAMETER_TOKEN}{this}" 3643 3644 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3645 this = self.sql(expression, "this") 3646 kind = expression.text("kind") 3647 if kind: 3648 kind = f"{kind}." 3649 return f"@@{kind}{this}" 3650 3651 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3652 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3653 3654 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3655 alias = self.sql(expression, "alias") 3656 alias = f"{sep}{alias}" if alias else "" 3657 sample = self.sql(expression, "sample") 3658 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3659 alias = f"{sample}{alias}" 3660 3661 # Set to None so it's not generated again by self.query_modifiers() 3662 expression.set("sample", None) 3663 3664 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3665 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3666 return self.prepend_ctes(expression, sql) 3667 3668 def qualify_sql(self, expression: exp.Qualify) -> str: 3669 this = self.indent(self.sql(expression, "this")) 3670 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3671 3672 def unnest_sql(self, expression: exp.Unnest) -> str: 3673 args = self.expressions(expression, flat=True) 3674 3675 alias = expression.args.get("alias") 3676 offset = expression.args.get("offset") 3677 3678 if self.UNNEST_WITH_ORDINALITY: 3679 if alias and isinstance(offset, exp.Expr): 3680 alias.append("columns", offset) 3681 expression.set("offset", None) 3682 3683 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3684 columns = alias.columns 3685 alias = self.sql(columns[0]) if columns else "" 3686 else: 3687 alias = self.sql(alias) 3688 3689 alias = f" AS {alias}" if alias else alias 3690 if self.UNNEST_WITH_ORDINALITY: 3691 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3692 else: 3693 if isinstance(offset, exp.Expr): 3694 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3695 elif offset: 3696 suffix = f"{alias} WITH OFFSET" 3697 else: 3698 suffix = alias 3699 3700 return f"UNNEST({args}){suffix}" 3701 3702 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3703 return "" 3704 3705 def where_sql(self, expression: exp.Where) -> str: 3706 this = self.indent(self.sql(expression, "this")) 3707 return f"{self.seg('WHERE')}{self.sep()}{this}" 3708 3709 def window_sql(self, expression: exp.Window) -> str: 3710 this = self.sql(expression, "this") 3711 partition = self.partition_by_sql(expression) 3712 order = expression.args.get("order") 3713 order = self.order_sql(order, flat=True) if order else "" 3714 spec = self.sql(expression, "spec") 3715 alias = self.sql(expression, "alias") 3716 over = self.sql(expression, "over") or "OVER" 3717 3718 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3719 3720 first = expression.args.get("first") 3721 if first is None: 3722 first = "" 3723 else: 3724 first = "FIRST" if first else "LAST" 3725 3726 if not partition and not order and not spec and alias: 3727 # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required. 3728 if expression.arg_key == "windows": 3729 return f"{this} ({alias})" 3730 return f"{this} {alias}" 3731 3732 args = self.format_args( 3733 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3734 ) 3735 return f"{this} ({args})" 3736 3737 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3738 partition = self.expressions(expression, key="partition_by", flat=True) 3739 return f"PARTITION BY {partition}" if partition else "" 3740 3741 def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str: 3742 return f"EXCLUDING {self.expressions(expression, flat=True)}" 3743 3744 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3745 kind = self.sql(expression, "kind") 3746 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3747 end = ( 3748 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3749 or "CURRENT ROW" 3750 ) 3751 3752 window_spec = f"{kind} BETWEEN {start} AND {end}" 3753 3754 exclude = self.sql(expression, "exclude") 3755 if exclude: 3756 if self.SUPPORTS_WINDOW_EXCLUDE: 3757 window_spec += f" EXCLUDE {exclude}" 3758 else: 3759 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3760 3761 return window_spec 3762 3763 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3764 this = self.sql(expression, "this") 3765 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3766 return f"{this} WITHIN GROUP ({expression_sql})" 3767 3768 def between_sql(self, expression: exp.Between) -> str: 3769 this = self.sql(expression, "this") 3770 low = self.sql(expression, "low") 3771 high = self.sql(expression, "high") 3772 symmetric = expression.args.get("symmetric") 3773 3774 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3775 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3776 3777 flag = ( 3778 " SYMMETRIC" 3779 if symmetric 3780 else " ASYMMETRIC" 3781 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3782 else "" # silently drop ASYMMETRIC – semantics identical 3783 ) 3784 return f"{this} BETWEEN{flag} {low} AND {high}" 3785 3786 def bracket_offset_expressions( 3787 self, expression: exp.Bracket, index_offset: int | None = None 3788 ) -> list[exp.Expr]: 3789 if expression.args.get("json_access"): 3790 return expression.expressions 3791 3792 return apply_index_offset( 3793 expression.this, 3794 expression.expressions, 3795 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3796 dialect=self.dialect, 3797 ) 3798 3799 def bracket_sql(self, expression: exp.Bracket) -> str: 3800 expressions = self.bracket_offset_expressions(expression) 3801 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3802 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3803 3804 def all_sql(self, expression: exp.All) -> str: 3805 this = self.sql(expression, "this") 3806 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3807 this = self.wrap(this) 3808 return f"ALL {this}" 3809 3810 def any_sql(self, expression: exp.Any) -> str: 3811 this = self.sql(expression, "this") 3812 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3813 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3814 this = self.wrap(this) 3815 return f"ANY{this}" 3816 return f"ANY {this}" 3817 3818 def exists_sql(self, expression: exp.Exists) -> str: 3819 return f"EXISTS{self.wrap(expression)}" 3820 3821 def case_sql(self, expression: exp.Case) -> str: 3822 this = self.sql(expression, "this") 3823 statements = [f"CASE {this}" if this else "CASE"] 3824 3825 for e in expression.args["ifs"]: 3826 statements.append(f"WHEN {self.sql(e, 'this')}") 3827 statements.append(f"THEN {self.sql(e, 'true')}") 3828 3829 default = self.sql(expression, "default") 3830 3831 if default: 3832 statements.append(f"ELSE {default}") 3833 3834 statements.append("END") 3835 3836 if self.pretty and self.too_wide(statements): 3837 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3838 3839 return " ".join(statements) 3840 3841 def constraint_sql(self, expression: exp.Constraint) -> str: 3842 this = self.sql(expression, "this") 3843 expressions = self.expressions(expression, flat=True) 3844 return f"CONSTRAINT {this} {expressions}" 3845 3846 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3847 order = expression.args.get("order") 3848 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3849 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3850 3851 def extract_sql(self, expression: exp.Extract) -> str: 3852 import sqlglot.dialects.dialect 3853 3854 this = ( 3855 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3856 if self.NORMALIZE_EXTRACT_DATE_PARTS 3857 else expression.this 3858 ) 3859 if self.EXTRACT_ALLOWS_QUOTES: 3860 this_sql = self.sql(this) 3861 elif isinstance(this, exp.WeekStart): 3862 this_sql = self.weekstart_name(this) 3863 else: 3864 this_sql = this.name 3865 expression_sql = self.sql(expression, "expression") 3866 3867 return f"EXTRACT({this_sql} FROM {expression_sql})" 3868 3869 def trim_sql(self, expression: exp.Trim) -> str: 3870 trim_type = self.sql(expression, "position") 3871 3872 if trim_type == "LEADING": 3873 func_name = "LTRIM" 3874 elif trim_type == "TRAILING": 3875 func_name = "RTRIM" 3876 else: 3877 func_name = "TRIM" 3878 3879 return self.func(func_name, expression.this, expression.expression) 3880 3881 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3882 args = expression.expressions 3883 if isinstance(expression, exp.ConcatWs): 3884 args = args[1:] # Skip the delimiter 3885 3886 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3887 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3888 3889 concat_coalesce = ( 3890 self.dialect.CONCAT_WS_COALESCE 3891 if isinstance(expression, exp.ConcatWs) 3892 else self.dialect.CONCAT_COALESCE 3893 ) 3894 3895 if not concat_coalesce and expression.args.get("coalesce"): 3896 3897 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3898 if not e.type: 3899 import sqlglot.optimizer.annotate_types 3900 3901 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3902 3903 if e.is_string or e.is_type(exp.DType.ARRAY): 3904 return e 3905 3906 return exp.func("coalesce", e, exp.Literal.string("")) 3907 3908 args = [_wrap_with_coalesce(e) for e in args] 3909 3910 return args 3911 3912 def concat_sql(self, expression: exp.Concat) -> str: 3913 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3914 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3915 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3916 # instead of coalescing them to empty string. 3917 import sqlglot.dialects.dialect 3918 3919 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3920 3921 expressions = self.convert_concat_args(expression) 3922 3923 # Some dialects don't allow a single-argument CONCAT call 3924 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3925 return self.sql(expressions[0]) 3926 3927 return self.func("CONCAT", *expressions) 3928 3929 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3930 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3931 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3932 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3933 all_args = expression.expressions 3934 expression.set("coalesce", True) 3935 return self.sql( 3936 exp.case() 3937 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3938 .else_(expression) 3939 ) 3940 3941 return self.func( 3942 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3943 ) 3944 3945 def check_sql(self, expression: exp.Check) -> str: 3946 this = self.sql(expression, key="this") 3947 return f"CHECK ({this})" 3948 3949 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3950 this = self.sql(expression, "this") 3951 this = f" {this}" if this else "" 3952 expressions = self.expressions(expression, flat=True) 3953 expressions = f" ({expressions})" if expressions else "" 3954 reference = self.sql(expression, "reference") 3955 reference = f" {reference}" if reference else "" 3956 delete = self.sql(expression, "delete") 3957 delete = f" ON DELETE {delete}" if delete else "" 3958 update = self.sql(expression, "update") 3959 update = f" ON UPDATE {update}" if update else "" 3960 options = self.expressions(expression, key="options", flat=True, sep=" ") 3961 options = f" {options}" if options else "" 3962 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}" 3963 3964 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3965 this = self.sql(expression, "this") 3966 this = f" {this}" if this else "" 3967 expressions = self.expressions(expression, flat=True) 3968 include = self.sql(expression, "include") 3969 options = self.expressions(expression, key="options", flat=True, sep=" ") 3970 options = f" {options}" if options else "" 3971 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3972 3973 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3974 self.unsupported("TIMESERIES primary key columns are not supported") 3975 return self.sql(expression, "this") 3976 3977 def if_sql(self, expression: exp.If) -> str: 3978 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3979 3980 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3981 if self.MATCH_AGAINST_TABLE_PREFIX: 3982 expressions = [] 3983 for expr in expression.expressions: 3984 if isinstance(expr, exp.Table): 3985 expressions.append(f"TABLE {self.sql(expr)}") 3986 else: 3987 expressions.append(expr) 3988 else: 3989 expressions = expression.expressions 3990 3991 modifier = expression.args.get("modifier") 3992 modifier = f" {modifier}" if modifier else "" 3993 return ( 3994 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3995 ) 3996 3997 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3998 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3999 4000 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 4001 path = self.expressions(expression, sep="", flat=True).lstrip(".") 4002 4003 if self.QUOTE_JSON_PATH: 4004 path = self.escape_str(path) 4005 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 4006 4007 return path 4008 4009 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 4010 if isinstance(expression, exp.JSONPathPart): 4011 transform = self.TRANSFORMS.get(expression.__class__) 4012 if not callable(transform): 4013 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 4014 return "" 4015 4016 return transform(self, expression) 4017 4018 if isinstance(expression, int): 4019 return str(expression) 4020 4021 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 4022 escaped = expression.replace("'", "\\'") 4023 escaped = f"'{escaped}'" 4024 else: 4025 escaped = expression.replace('"', '\\"') 4026 escaped = f'"{escaped}"' 4027 4028 return escaped 4029 4030 def formatjson_sql(self, expression: exp.FormatJson) -> str: 4031 return f"{self.sql(expression, 'this')} FORMAT JSON" 4032 4033 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 4034 # Output the Teradata column FORMAT override. 4035 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 4036 this = self.sql(expression, "this") 4037 fmt = self.sql(expression, "format") 4038 return f"{this} (FORMAT {fmt})" 4039 4040 def _jsonobject_sql( 4041 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 4042 ) -> str: 4043 null_handling = expression.args.get("null_handling") 4044 null_handling = f" {null_handling}" if null_handling else "" 4045 4046 unique_keys = expression.args.get("unique_keys") 4047 if unique_keys is not None: 4048 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 4049 else: 4050 unique_keys = "" 4051 4052 return_type = self.sql(expression, "return_type") 4053 return_type = f" RETURNING {return_type}" if return_type else "" 4054 encoding = self.sql(expression, "encoding") 4055 encoding = f" ENCODING {encoding}" if encoding else "" 4056 4057 if not name: 4058 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 4059 4060 return self.func( 4061 name, 4062 *expression.expressions, 4063 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 4064 ) 4065 4066 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 4067 null_handling = expression.args.get("null_handling") 4068 null_handling = f" {null_handling}" if null_handling else "" 4069 return_type = self.sql(expression, "return_type") 4070 return_type = f" RETURNING {return_type}" if return_type else "" 4071 strict = " STRICT" if expression.args.get("strict") else "" 4072 return self.func( 4073 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 4074 ) 4075 4076 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 4077 this = self.sql(expression, "this") 4078 order = self.sql(expression, "order") 4079 null_handling = expression.args.get("null_handling") 4080 null_handling = f" {null_handling}" if null_handling else "" 4081 return_type = self.sql(expression, "return_type") 4082 return_type = f" RETURNING {return_type}" if return_type else "" 4083 strict = " STRICT" if expression.args.get("strict") else "" 4084 return self.func( 4085 "JSON_ARRAYAGG", 4086 this, 4087 suffix=f"{order}{null_handling}{return_type}{strict})", 4088 ) 4089 4090 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 4091 path = self.sql(expression, "path") 4092 path = f" PATH {path}" if path else "" 4093 nested_schema = self.sql(expression, "nested_schema") 4094 4095 if nested_schema: 4096 return f"NESTED{path} {nested_schema}" 4097 4098 this = self.sql(expression, "this") 4099 kind = self.sql(expression, "kind") 4100 kind = f" {kind}" if kind else "" 4101 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4102 4103 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4104 return f"{this}{kind}{format_json}{path}{ordinality}" 4105 4106 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 4107 return self.func("COLUMNS", *expression.expressions) 4108 4109 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4110 this = self.sql(expression, "this") 4111 path = self.sql(expression, "path") 4112 path = f", {path}" if path else "" 4113 error_handling = expression.args.get("error_handling") 4114 error_handling = f" {error_handling}" if error_handling else "" 4115 empty_handling = expression.args.get("empty_handling") 4116 empty_handling = f" {empty_handling}" if empty_handling else "" 4117 schema = self.sql(expression, "schema") 4118 return self.func( 4119 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4120 ) 4121 4122 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4123 this = self.sql(expression, "this") 4124 kind = self.sql(expression, "kind") 4125 path = self.sql(expression, "path") 4126 path = f" {path}" if path else "" 4127 as_json = " AS JSON" if expression.args.get("as_json") else "" 4128 return f"{this} {kind}{path}{as_json}" 4129 4130 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4131 this = self.sql(expression, "this") 4132 path = self.sql(expression, "path") 4133 path = f", {path}" if path else "" 4134 expressions = self.expressions(expression) 4135 with_ = ( 4136 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4137 if expressions 4138 else "" 4139 ) 4140 return f"OPENJSON({this}{path}){with_}" 4141 4142 def in_sql(self, expression: exp.In) -> str: 4143 query = expression.args.get("query") 4144 unnest = expression.args.get("unnest") 4145 field = expression.args.get("field") 4146 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4147 4148 if query: 4149 in_sql = self.sql(query) 4150 elif unnest: 4151 in_sql = self.in_unnest_op(unnest) 4152 elif field: 4153 in_sql = self.sql(field) 4154 else: 4155 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4156 4157 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4158 4159 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4160 return f"(SELECT {self.sql(unnest)})" 4161 4162 def interval_sql(self, expression: exp.Interval) -> str: 4163 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4164 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4165 exp.AutoRefreshProperty, 4166 ) 4167 interval_keyword = "INTERVAL" if include_keyword else "" 4168 unit_expression = expression.args.get("unit") 4169 unit = self.sql(unit_expression) if unit_expression else "" 4170 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4171 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4172 unit = f" {unit}" if unit else "" 4173 4174 if self.SINGLE_STRING_INTERVAL: 4175 this = expression.this.name if expression.this else "" 4176 if this: 4177 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4178 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4179 return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}" 4180 return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}" 4181 return f"{interval_keyword}{unit}" 4182 4183 this = self.sql(expression, "this") 4184 if this: 4185 if not include_keyword and expression.this.is_string: 4186 this = expression.this.name 4187 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4188 this = f"({this})" 4189 if include_keyword: 4190 this = f" {this}" 4191 4192 return f"{interval_keyword}{this}{unit}" 4193 4194 def return_sql(self, expression: exp.Return) -> str: 4195 return f"RETURN {self.sql(expression, 'this')}" 4196 4197 def reference_sql(self, expression: exp.Reference) -> str: 4198 this = self.sql(expression, "this") 4199 expressions = self.expressions(expression, flat=True) 4200 expressions = f"({expressions})" if expressions else "" 4201 options = self.expressions(expression, key="options", flat=True, sep=" ") 4202 options = f" {options}" if options else "" 4203 return f"REFERENCES {this}{expressions}{options}" 4204 4205 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4206 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4207 parent = expression.parent 4208 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4209 4210 return self.func( 4211 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4212 ) 4213 4214 def paren_sql(self, expression: exp.Paren) -> str: 4215 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4216 return f"({sql}{self.seg(')', sep='')}" 4217 4218 def neg_sql(self, expression: exp.Neg) -> str: 4219 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4220 this_sql = self.sql(expression, "this") 4221 sep = " " if this_sql[0] == "-" else "" 4222 return f"-{sep}{this_sql}" 4223 4224 def not_sql(self, expression: exp.Not) -> str: 4225 return f"NOT {self.sql(expression, 'this')}" 4226 4227 def alias_sql(self, expression: exp.Alias) -> str: 4228 alias = self.sql(expression, "alias") 4229 alias = f" AS {alias}" if alias else "" 4230 return f"{self.sql(expression, 'this')}{alias}" 4231 4232 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4233 alias = expression.args["alias"] 4234 4235 parent = expression.parent 4236 pivot = parent and parent.parent 4237 4238 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4239 identifier_alias = isinstance(alias, exp.Identifier) 4240 literal_alias = isinstance(alias, exp.Literal) 4241 4242 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4243 alias.replace(exp.Literal.string(alias.output_name)) 4244 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4245 alias.replace(exp.to_identifier(alias.output_name)) 4246 4247 return self.alias_sql(expression) 4248 4249 def aliases_sql(self, expression: exp.Aliases) -> str: 4250 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4251 4252 def atindex_sql(self, expression: exp.AtIndex) -> str: 4253 this = self.sql(expression, "this") 4254 index = self.sql(expression, "expression") 4255 return f"{this} AT {index}" 4256 4257 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4258 this = self.sql(expression, "this") 4259 zone = self.sql(expression, "zone") 4260 return f"{this} AT TIME ZONE {zone}" 4261 4262 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4263 this = self.sql(expression, "this") 4264 zone = self.sql(expression, "zone") 4265 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4266 4267 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4268 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4269 4270 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4271 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4272 4273 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4274 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4275 4276 def add_sql(self, expression: exp.Add) -> str: 4277 return self.binary(expression, "+") 4278 4279 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4280 return self.connector_sql(expression, "AND", stack) 4281 4282 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4283 return self.connector_sql(expression, "OR", stack) 4284 4285 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4286 return self.connector_sql(expression, "XOR", stack) 4287 4288 def connector_sql( 4289 self, 4290 expression: exp.Connector, 4291 op: str, 4292 stack: list[str | exp.Expr] | None = None, 4293 ) -> str: 4294 if stack is not None: 4295 stack.append(expression.right) 4296 if expression.comments and self.comments: 4297 op = self.maybe_comment(op, comments=expression.comments) 4298 4299 stack.extend((op, expression.left)) 4300 return op 4301 4302 stack = [expression] 4303 sqls: list[str] = [] 4304 ops = set() 4305 4306 while stack: 4307 node = stack.pop() 4308 if isinstance(node, exp.Connector): 4309 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4310 else: 4311 sql = self.sql(node) 4312 if sqls and sqls[-1] in ops: 4313 sqls[-1] += f" {sql}" 4314 else: 4315 sqls.append(sql) 4316 4317 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4318 return sep.join(sqls) 4319 4320 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4321 return self.binary(expression, "&") 4322 4323 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4324 return self.binary(expression, "<<") 4325 4326 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4327 return f"~{self.sql(expression, 'this')}" 4328 4329 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4330 return self.binary(expression, "|") 4331 4332 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4333 return self.binary(expression, ">>") 4334 4335 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4336 return self.binary(expression, "^") 4337 4338 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4339 format_sql = self.sql(expression, "format") 4340 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4341 to_sql = self.sql(expression, "to") 4342 to_sql = f" {to_sql}" if to_sql else "" 4343 action = self.sql(expression, "action") 4344 action = f" {action}" if action else "" 4345 default = self.sql(expression, "default") 4346 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4347 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4348 4349 # Base implementation that excludes safe, zone, and target_type metadata args 4350 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4351 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4352 4353 # Base implementation that excludes the safe and default_year metadata args 4354 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4355 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4356 4357 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4358 return self.func( 4359 "PARSE_DATETIME", 4360 expression.this, 4361 expression.args.get("format"), 4362 expression.args.get("zone"), 4363 ) 4364 4365 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4366 zone = self.sql(expression, "this") 4367 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4368 4369 def collate_sql(self, expression: exp.Collate) -> str: 4370 if self.COLLATE_IS_FUNC: 4371 return self.function_fallback_sql(expression) 4372 return self.binary(expression, "COLLATE") 4373 4374 def command_sql(self, expression: exp.Command) -> str: 4375 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4376 4377 def comment_sql(self, expression: exp.Comment) -> str: 4378 this = self.sql(expression, "this") 4379 kind = expression.args["kind"] 4380 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4381 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4382 expression_sql = self.sql(expression, "expression") 4383 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4384 4385 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4386 this = self.sql(expression, "this") 4387 delete = " DELETE" if expression.args.get("delete") else "" 4388 recompress = self.sql(expression, "recompress") 4389 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4390 to_disk = self.sql(expression, "to_disk") 4391 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4392 to_volume = self.sql(expression, "to_volume") 4393 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4394 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4395 4396 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4397 where = self.sql(expression, "where") 4398 group = self.sql(expression, "group") 4399 aggregates = self.expressions(expression, key="aggregates") 4400 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4401 4402 if not (where or group or aggregates) and len(expression.expressions) == 1: 4403 return f"TTL {self.expressions(expression, flat=True)}" 4404 4405 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4406 4407 def transaction_sql(self, expression: exp.Transaction) -> str: 4408 modes = self.expressions(expression, key="modes") 4409 modes = f" {modes}" if modes else "" 4410 return f"BEGIN{modes}" 4411 4412 def commit_sql(self, expression: exp.Commit) -> str: 4413 chain = expression.args.get("chain") 4414 if chain is not None: 4415 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4416 4417 return f"COMMIT{chain or ''}" 4418 4419 def rollback_sql(self, expression: exp.Rollback) -> str: 4420 savepoint = expression.args.get("savepoint") 4421 savepoint = f" TO {savepoint}" if savepoint else "" 4422 return f"ROLLBACK{savepoint}" 4423 4424 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4425 this = self.sql(expression, "this") 4426 4427 exists = "" 4428 if expression.args.get("exists"): 4429 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4430 exists = " IF EXISTS" 4431 else: 4432 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4433 4434 dtype = self.sql(expression, "dtype") 4435 if dtype: 4436 collate = self.sql(expression, "collate") 4437 collate = f" COLLATE {collate}" if collate else "" 4438 using = self.sql(expression, "using") 4439 using = f" USING {using}" if using else "" 4440 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4441 null_constraint = self._alter_column_null_constraint_sql(expression) 4442 4443 return ( 4444 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4445 f"{collate}{using}{null_constraint}" 4446 ) 4447 4448 default = self.sql(expression, "default") 4449 if default: 4450 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4451 4452 comment = self.sql(expression, "comment") 4453 if comment: 4454 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4455 4456 visible = expression.args.get("visible") 4457 if visible: 4458 return f"ALTER COLUMN{exists} {this} SET {visible}" 4459 4460 allow_null = expression.args.get("allow_null") 4461 drop = expression.args.get("drop") 4462 4463 if not drop and not allow_null: 4464 self.unsupported("Unsupported ALTER COLUMN syntax") 4465 4466 if allow_null is not None: 4467 keyword = "DROP" if drop else "SET" 4468 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4469 4470 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4471 4472 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4473 allow_null = expression.args.get("allow_null") 4474 if allow_null is None: 4475 return "" 4476 4477 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4478 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4479 return "" 4480 4481 return " NULL" if allow_null else " NOT NULL" 4482 4483 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4484 this = self.sql(expression, "this") 4485 rename_from = self.sql(expression, "rename_from") 4486 if rename_from: 4487 if not self.SUPPORTS_CHANGE_COLUMN: 4488 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4489 return f"CHANGE COLUMN {rename_from} {this}" 4490 if not self.SUPPORTS_MODIFY_COLUMN: 4491 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4492 return f"MODIFY COLUMN {this}" 4493 4494 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4495 this = self.sql(expression, "this") 4496 4497 visible = expression.args.get("visible") 4498 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4499 4500 return f"ALTER INDEX {this} {visible_sql}" 4501 4502 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4503 this = self.sql(expression, "this") 4504 if not isinstance(expression.this, exp.Var): 4505 this = f"KEY DISTKEY {this}" 4506 return f"ALTER DISTSTYLE {this}" 4507 4508 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4509 compound = " COMPOUND" if expression.args.get("compound") else "" 4510 this = self.sql(expression, "this") 4511 expressions = self.expressions(expression, flat=True) 4512 expressions = f"({expressions})" if expressions else "" 4513 return f"ALTER{compound} SORTKEY {this or expressions}" 4514 4515 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4516 if not self.RENAME_TABLE_WITH_DB: 4517 # Remove db from tables 4518 expression = expression.transform( 4519 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4520 ).assert_is(exp.AlterRename) 4521 this = self.sql(expression, "this") 4522 to_kw = " TO" if include_to else "" 4523 return f"RENAME{to_kw} {this}" 4524 4525 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4526 exists = " IF EXISTS" if expression.args.get("exists") else "" 4527 old_column = self.sql(expression, "this") 4528 new_column = self.sql(expression, "to") 4529 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4530 4531 def alterset_sql(self, expression: exp.AlterSet) -> str: 4532 exprs = self.expressions(expression, flat=True) 4533 if self.ALTER_SET_WRAPPED: 4534 exprs = f"({exprs})" 4535 4536 return f"SET {exprs}" 4537 4538 def alter_sql(self, expression: exp.Alter) -> str: 4539 actions = expression.actions 4540 4541 if ( 4542 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4543 and actions 4544 and isinstance(actions[0], exp.ColumnDef) 4545 ): 4546 actions_sql = self.expressions(expression, key="actions", flat=True) 4547 actions_sql = f"ADD {actions_sql}" 4548 else: 4549 actions_list = [] 4550 for action in actions: 4551 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4552 action_sql = self.add_column_sql(action) 4553 else: 4554 action_sql = self.sql(action) 4555 if isinstance(action, exp.Query): 4556 action_sql = f"AS {action_sql}" 4557 4558 actions_list.append(action_sql) 4559 4560 actions_sql = self.format_args(*actions_list).lstrip("\n") 4561 4562 iceberg = ( 4563 "ICEBERG " 4564 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4565 else "" 4566 ) 4567 exists = " IF EXISTS" if expression.args.get("exists") else "" 4568 on_cluster = self.sql(expression, "cluster") 4569 on_cluster = f" {on_cluster}" if on_cluster else "" 4570 only = " ONLY" if expression.args.get("only") else "" 4571 options = expression.args.get("options") or [] 4572 options_sql = self.expressions( 4573 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4574 ) 4575 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4576 partitions_sql = self.expressions( 4577 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4578 sep=" ", 4579 flat=True, 4580 ) 4581 if partitions_sql and (actions_sql or options_sql): 4582 partitions_sql = f" {partitions_sql}" 4583 kind = self.sql(expression, "kind") 4584 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4585 check = " WITH CHECK" if expression.args.get("check") else "" 4586 cascade = ( 4587 " CASCADE" 4588 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4589 else "" 4590 ) 4591 this = self.sql(expression, "this") 4592 this = f" {this}" if this else "" 4593 4594 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4595 4596 def altersession_sql(self, expression: exp.AlterSession) -> str: 4597 items_sql = self.expressions(expression, flat=True) 4598 keyword = "UNSET" if expression.args.get("unset") else "SET" 4599 return f"{keyword} {items_sql}" 4600 4601 def add_column_sql(self, expression: exp.Expr) -> str: 4602 column_text = "" 4603 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4604 if isinstance(expression, exp.ColumnDef): 4605 column_text = " COLUMN" 4606 elif isinstance(expression, exp.Schema): 4607 column_text = " COLUMNS" 4608 4609 if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED: 4610 sql = self.expressions(expression, flat=True) 4611 else: 4612 sql = self.sql(expression) 4613 4614 return f"ADD{column_text} {sql}" 4615 4616 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4617 expressions = self.expressions(expression) 4618 exists = " IF EXISTS " if expression.args.get("exists") else " " 4619 return f"DROP{exists}{expressions}" 4620 4621 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4622 return "DROP PRIMARY KEY" 4623 4624 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4625 return f"ADD {self.expressions(expression, indent=False)}" 4626 4627 def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str: 4628 return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})" 4629 4630 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4631 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4632 location = self.sql(expression, "location") 4633 location = f" {location}" if location else "" 4634 return f"ADD {exists}{self.sql(expression.this)}{location}" 4635 4636 def distinct_sql(self, expression: exp.Distinct) -> str: 4637 this = self.expressions(expression, flat=True) 4638 4639 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4640 case = exp.case() 4641 for arg in expression.expressions: 4642 case = case.when(arg.is_(exp.null()), exp.null()) 4643 this = self.sql(case.else_(f"({this})")) 4644 4645 this = f" {this}" if this else "" 4646 4647 on = self.sql(expression, "on") 4648 on = f" ON {on}" if on else "" 4649 return f"DISTINCT{this}{on}" 4650 4651 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4652 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4653 4654 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4655 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4656 4657 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4658 this_sql = self.sql(expression, "this") 4659 expression_sql = self.sql(expression, "expression") 4660 kind = "MAX" if expression.args.get("max") else "MIN" 4661 return f"{this_sql} HAVING {kind} {expression_sql}" 4662 4663 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4664 return self._truncated_div_sql(expression.this, expression.expression) 4665 4666 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4667 div: exp.Expr = exp.Div(this=this, expression=divisor) 4668 if self.CAST_TO_INT_ROUNDS: 4669 div = exp.Trunc(this=div) 4670 4671 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4672 4673 def dpipe_sql(self, expression: exp.DPipe) -> str: 4674 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4675 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4676 return self.binary(expression, "||") 4677 4678 def div_sql(self, expression: exp.Div) -> str: 4679 l, r = expression.left, expression.right 4680 4681 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4682 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4683 4684 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4685 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4686 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4687 4688 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4689 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4690 # Native integer division depends on the target's operand types, which may differ 4691 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4692 # integer literals or casts 4693 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4694 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4695 ): 4696 return self.sql(exp.IntDiv(this=l, expression=r)) 4697 return self._truncated_div_sql(l, r) 4698 4699 return self.binary(expression, "/") 4700 4701 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4702 n = exp._wrap(expression.this, exp.Binary) 4703 d = exp._wrap(expression.expression, exp.Binary) 4704 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4705 4706 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4707 return self.binary(expression, "OVERLAPS") 4708 4709 def distance_sql(self, expression: exp.Distance) -> str: 4710 return self.binary(expression, "<->") 4711 4712 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4713 return self.binary(expression, "<<->>") 4714 4715 def dot_sql(self, expression: exp.Dot) -> str: 4716 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4717 4718 def eq_sql(self, expression: exp.EQ) -> str: 4719 return self.binary(expression, "=") 4720 4721 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4722 return self.binary(expression, ":=") 4723 4724 def escape_sql(self, expression: exp.Escape) -> str: 4725 this = expression.this 4726 if ( 4727 isinstance(this, (exp.Like, exp.ILike)) 4728 and isinstance(this.expression, (exp.All, exp.Any)) 4729 and not self.SUPPORTS_LIKE_QUANTIFIERS 4730 ): 4731 return self._like_sql(this, escape=expression) 4732 return self.binary(expression, "ESCAPE") 4733 4734 def glob_sql(self, expression: exp.Glob) -> str: 4735 return self.binary(expression, "GLOB") 4736 4737 def gt_sql(self, expression: exp.GT) -> str: 4738 return self.binary(expression, ">") 4739 4740 def gte_sql(self, expression: exp.GTE) -> str: 4741 return self.binary(expression, ">=") 4742 4743 def is_sql(self, expression: exp.Is) -> str: 4744 negate = expression.args.get("negate") 4745 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4746 positive = bool(expression.expression.this) != bool(negate) 4747 return self.sql(expression.this if positive else exp.not_(expression.this)) 4748 return self.binary(expression, "IS NOT" if negate else "IS") 4749 4750 def _like_sql( 4751 self, 4752 expression: exp.Like | exp.ILike, 4753 escape: exp.Escape | None = None, 4754 ) -> str: 4755 this = expression.this 4756 rhs = expression.expression 4757 4758 if isinstance(expression, exp.Like): 4759 exp_class: type[exp.Like | exp.ILike] = exp.Like 4760 op = "LIKE" 4761 else: 4762 exp_class = exp.ILike 4763 op = "ILIKE" 4764 4765 if expression.args.get("negate"): 4766 op = f"NOT {op}" 4767 4768 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4769 exprs = rhs.this.unnest() 4770 4771 if isinstance(exprs, exp.Tuple): 4772 exprs = exprs.expressions 4773 else: 4774 exprs = [exprs] 4775 4776 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4777 4778 def _make_like(expr: exp.Expression) -> exp.Expression: 4779 like: exp.Expression = exp_class( 4780 this=this, expression=expr, negate=expression.args.get("negate") 4781 ) 4782 if escape: 4783 like = exp.Escape(this=like, expression=escape.expression.copy()) 4784 return like 4785 4786 like_expr: exp.Expr = _make_like(exprs[0]) 4787 for expr in exprs[1:]: 4788 like_expr = connective(like_expr, _make_like(expr), copy=False) 4789 4790 parent = escape.parent if escape else expression.parent 4791 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4792 parent, exp.Condition 4793 ): 4794 like_expr = exp.paren(like_expr, copy=False) 4795 4796 return self.sql(like_expr) 4797 4798 return self.binary(expression, op) 4799 4800 def like_sql(self, expression: exp.Like) -> str: 4801 return self._like_sql(expression) 4802 4803 def ilike_sql(self, expression: exp.ILike) -> str: 4804 return self._like_sql(expression) 4805 4806 def match_sql(self, expression: exp.Match) -> str: 4807 if expression.args.get("kind") or expression.args.get("analyzer"): 4808 self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect") 4809 return self.binary(expression, "MATCH") 4810 4811 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4812 return self.binary(expression, "SIMILAR TO") 4813 4814 def lt_sql(self, expression: exp.LT) -> str: 4815 return self.binary(expression, "<") 4816 4817 def lte_sql(self, expression: exp.LTE) -> str: 4818 return self.binary(expression, "<=") 4819 4820 def mod_sql(self, expression: exp.Mod) -> str: 4821 this = self.sql(expression, "this") 4822 expr = self.sql(expression, "expression") 4823 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4824 4825 parent = expression.parent 4826 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4827 return f"({sql})" 4828 4829 return sql 4830 4831 def mul_sql(self, expression: exp.Mul) -> str: 4832 return self.binary(expression, "*") 4833 4834 def neq_sql(self, expression: exp.NEQ) -> str: 4835 return self.binary(expression, "<>") 4836 4837 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4838 return self.binary(expression, "IS NOT DISTINCT FROM") 4839 4840 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4841 return self.binary(expression, "IS DISTINCT FROM") 4842 4843 def sub_sql(self, expression: exp.Sub) -> str: 4844 return self.binary(expression, "-") 4845 4846 def trycast_sql(self, expression: exp.TryCast) -> str: 4847 return self.cast_sql(expression, safe_prefix="TRY_") 4848 4849 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4850 return self.cast_sql(expression) 4851 4852 def try_sql(self, expression: exp.Try) -> str: 4853 if not self.TRY_SUPPORTED: 4854 self.unsupported("Unsupported TRY function") 4855 return self.sql(expression, "this") 4856 4857 return self.func("TRY", expression.this) 4858 4859 def log_sql(self, expression: exp.Log) -> str: 4860 this = expression.this 4861 expr = expression.expression 4862 4863 if self.dialect.LOG_BASE_FIRST is False: 4864 this, expr = expr, this 4865 elif self.dialect.LOG_BASE_FIRST is None and expr: 4866 if this.name in ("2", "10"): 4867 return self.func(f"LOG{this.name}", expr) 4868 4869 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4870 4871 return self.func("LOG", this, expr) 4872 4873 def use_sql(self, expression: exp.Use) -> str: 4874 kind = self.sql(expression, "kind") 4875 kind = f" {kind}" if kind else "" 4876 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4877 this = f" {this}" if this else "" 4878 return f"USE{kind}{this}" 4879 4880 def binary(self, expression: exp.Binary, op: str) -> str: 4881 sqls: list[str] = [] 4882 stack: list[None | str | exp.Expr] = [expression] 4883 binary_type = type(expression) 4884 is_is = binary_type is exp.Is and op in ("IS", "IS NOT") 4885 negate = expression.args.get("negate") 4886 4887 while stack: 4888 node = stack.pop() 4889 4890 if type(node) is binary_type and ( 4891 node is expression 4892 or (is_is and not isinstance(node.expression, exp.Boolean)) 4893 or node.args.get("negate") == negate 4894 ): 4895 op_func = node.args.get("operator") 4896 if op_func: 4897 op = f"OPERATOR({self.sql(op_func)})" 4898 elif is_is: 4899 op = "IS NOT" if node.args.get("negate") else "IS" 4900 4901 stack.append(node.args.get("expression")) 4902 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4903 stack.append(node.args.get("this")) 4904 else: 4905 sqls.append(self.sql(node)) 4906 4907 return "".join(sqls) 4908 4909 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4910 to_clause = self.sql(expression, "to") 4911 if to_clause: 4912 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4913 4914 return self.function_fallback_sql(expression) 4915 4916 def function_fallback_sql(self, expression: exp.Func) -> str: 4917 args = [] 4918 4919 for key in expression.arg_types: 4920 arg_value = expression.args.get(key) 4921 4922 if isinstance(arg_value, list): 4923 for value in arg_value: 4924 args.append(value) 4925 elif arg_value is not None: 4926 args.append(arg_value) 4927 4928 return self.func(self.function_name(expression), *args) 4929 4930 def function_name(self, expression: exp.Func) -> str: 4931 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4932 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4933 4934 def func( 4935 self, 4936 name: str, 4937 *args: t.Any, 4938 prefix: str = "(", 4939 suffix: str = ")", 4940 normalize: bool = True, 4941 ) -> str: 4942 name = self.normalize_func(name) if normalize else name 4943 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4944 4945 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4946 arg_sqls = tuple( 4947 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4948 ) 4949 if self.pretty and self.too_wide(arg_sqls): 4950 return self.indent( 4951 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4952 ) 4953 return sep.join(arg_sqls) 4954 4955 def too_wide(self, args: t.Iterable) -> bool: 4956 return sum(len(arg) for arg in args) > self.max_text_width 4957 4958 def format_time( 4959 self, 4960 expression: exp.Expr, 4961 inverse_time_mapping: dict[str, str] | None = None, 4962 inverse_time_trie: dict | None = None, 4963 ) -> str | None: 4964 return format_time( 4965 self.sql(expression, "format"), 4966 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4967 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4968 ) 4969 4970 def expressions( 4971 self, 4972 expression: exp.Expr | None = None, 4973 key: str | None = None, 4974 sqls: t.Collection[str | exp.Expr] | None = None, 4975 flat: bool = False, 4976 indent: bool = True, 4977 skip_first: bool = False, 4978 skip_last: bool = False, 4979 sep: str = ", ", 4980 prefix: str = "", 4981 dynamic: bool = False, 4982 new_line: bool = False, 4983 ) -> str: 4984 expressions = expression.args.get(key or "expressions") if expression else sqls 4985 4986 if not expressions: 4987 return "" 4988 4989 if flat: 4990 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4991 4992 num_sqls = len(expressions) 4993 result_sqls = [] 4994 4995 for i, e in enumerate(expressions): 4996 sql = self.sql(e, comment=False) 4997 if not sql: 4998 continue 4999 5000 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 5001 5002 if self.pretty: 5003 if self.leading_comma: 5004 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 5005 else: 5006 result_sqls.append( 5007 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 5008 ) 5009 else: 5010 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 5011 5012 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 5013 if new_line: 5014 result_sqls.insert(0, "") 5015 result_sqls.append("") 5016 result_sql = "\n".join(s.rstrip() for s in result_sqls) 5017 else: 5018 result_sql = "".join(result_sqls) 5019 5020 return ( 5021 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 5022 if indent 5023 else result_sql 5024 ) 5025 5026 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 5027 flat = flat or isinstance(expression.parent, exp.Properties) 5028 expressions_sql = self.expressions(expression, flat=flat) 5029 if flat: 5030 return f"{op} {expressions_sql}" 5031 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 5032 5033 def naked_property(self, expression: exp.Property) -> str: 5034 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 5035 if not property_name: 5036 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 5037 return f"{property_name} {self.sql(expression, 'this')}" 5038 5039 def tag_sql(self, expression: exp.Tag) -> str: 5040 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 5041 5042 def token_sql(self, token_type: TokenType) -> str: 5043 return self.TOKEN_MAPPING.get(token_type, token_type.name) 5044 5045 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 5046 this = self.sql(expression, "this") 5047 expressions = self.no_identify(self.expressions, expression) 5048 expressions = ( 5049 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 5050 ) 5051 return f"{this}{expressions}" if expressions.strip() != "" else this 5052 5053 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 5054 return self.expressions(expression, flat=True) 5055 5056 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 5057 params = self.no_identify(self.expressions, expression, flat=True) 5058 body = self.sql(expression, "this") 5059 prefix = "TABLE " if expression.args.get("is_table") else "" 5060 return f"({params}) AS {prefix}{body}" 5061 5062 def joinhint_sql(self, expression: exp.JoinHint) -> str: 5063 this = self.sql(expression, "this") 5064 expressions = self.expressions(expression, flat=True) 5065 return f"{this}({expressions})" 5066 5067 def kwarg_sql(self, expression: exp.Kwarg) -> str: 5068 return self.binary(expression, "=>") 5069 5070 def when_sql(self, expression: exp.When) -> str: 5071 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 5072 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 5073 condition = self.sql(expression, "condition") 5074 condition = f" AND {condition}" if condition else "" 5075 5076 then_expression = expression.args.get("then") 5077 if isinstance(then_expression, exp.Insert): 5078 this = ( 5079 "ALL BY NAME" 5080 if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star) 5081 else self.sql(then_expression, "this") 5082 ) 5083 this = f"INSERT {this}" if this else "INSERT" 5084 then = self.sql(then_expression, "expression") 5085 then = f"{this} VALUES {then}" if then else this 5086 elif isinstance(then_expression, exp.Update): 5087 if isinstance(then_expression.args.get("expressions"), exp.Star): 5088 then = ( 5089 "UPDATE ALL BY NAME" 5090 if self.MERGE_ALL_BY_NAME 5091 else f"UPDATE {self.sql(then_expression, 'expressions')}" 5092 ) 5093 elif ( 5094 self.MERGE_ALL_BY_NAME 5095 and len(then_expression.expressions) == 1 5096 and isinstance(then_expression.expressions[0], exp.Star) 5097 ): 5098 then = "UPDATE ALL BY NAME" 5099 else: 5100 expressions_sql = self.expressions(then_expression) 5101 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 5102 else: 5103 then = self.sql(then_expression) 5104 5105 if isinstance(then_expression, (exp.Insert, exp.Update)): 5106 where = self.sql(then_expression, "where") 5107 if where and not self.SUPPORTS_MERGE_WHERE: 5108 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 5109 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 5110 where = "" 5111 then = f"{then}{where}" 5112 return f"WHEN {matched}{source}{condition} THEN {then}" 5113 5114 def whens_sql(self, expression: exp.Whens) -> str: 5115 return self.expressions(expression, sep=" ", indent=False) 5116 5117 def merge_sql(self, expression: exp.Merge) -> str: 5118 table = expression.this 5119 table_alias = "" 5120 5121 hints = table.args.get("hints") 5122 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 5123 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 5124 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 5125 5126 this = self.sql(table) 5127 using = f"USING {self.sql(expression, 'using')}" 5128 whens = self.sql(expression, "whens") 5129 5130 on = self.sql(expression, "on") 5131 on = f"ON {on}" if on else "" 5132 5133 if not on: 5134 on = self.expressions(expression, key="using_cond") 5135 on = f"USING ({on})" if on else "" 5136 5137 returning = self.sql(expression, "returning") 5138 if returning: 5139 whens = f"{whens}{returning}" 5140 5141 sep = self.sep() 5142 5143 return self.prepend_ctes( 5144 expression, 5145 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5146 ) 5147 5148 @unsupported_args("format") 5149 def tochar_sql(self, expression: exp.ToChar) -> str: 5150 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5151 5152 @unsupported_args("default") 5153 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5154 if not self.SUPPORTS_TO_NUMBER: 5155 self.unsupported("Unsupported TO_NUMBER function") 5156 return self._tonumber_cast_sql(expression) 5157 5158 fmt = expression.args.get("format") 5159 if not fmt: 5160 self.unsupported("Conversion format is required for TO_NUMBER") 5161 return self._tonumber_cast_sql(expression) 5162 5163 return self.func("TO_NUMBER", expression.this, fmt) 5164 5165 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5166 if expression.args.get("safe"): 5167 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5168 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5169 5170 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5171 this = self.sql(expression, "this") 5172 kind = self.sql(expression, "kind") 5173 settings_sql = self.expressions(expression, key="settings", sep=" ") 5174 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5175 return f"{this}({kind}{args})" 5176 5177 def dictrange_sql(self, expression: exp.DictRange) -> str: 5178 this = self.sql(expression, "this") 5179 max = self.sql(expression, "max") 5180 min = self.sql(expression, "min") 5181 return f"{this}(MIN {min} MAX {max})" 5182 5183 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5184 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5185 5186 def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str: 5187 return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})" 5188 5189 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5190 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5191 5192 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5193 def uniquekeyproperty_sql( 5194 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5195 ) -> str: 5196 return f"{prefix} ({self.expressions(expression, flat=True)})" 5197 5198 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5199 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5200 expressions = self.expressions(expression, flat=True) 5201 expressions = f" {self.wrap(expressions)}" if expressions else "" 5202 buckets = self.sql(expression, "buckets") 5203 kind = self.sql(expression, "kind") 5204 buckets = f" BUCKETS {buckets}" if buckets else "" 5205 order = self.sql(expression, "order") 5206 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5207 5208 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5209 return "" 5210 5211 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5212 expressions = self.expressions(expression, key="expressions", flat=True) 5213 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5214 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5215 buckets = self.sql(expression, "buckets") 5216 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5217 5218 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5219 this = self.sql(expression, "this") 5220 having = self.sql(expression, "having") 5221 5222 if having: 5223 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5224 5225 return self.func("ANY_VALUE", this) 5226 5227 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5228 transform = self.func("TRANSFORM", *expression.expressions) 5229 row_format_before = self.sql(expression, "row_format_before") 5230 row_format_before = f" {row_format_before}" if row_format_before else "" 5231 record_writer = self.sql(expression, "record_writer") 5232 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5233 using = f" USING {self.sql(expression, 'command_script')}" 5234 schema = self.sql(expression, "schema") 5235 schema = f" AS {schema}" if schema else "" 5236 row_format_after = self.sql(expression, "row_format_after") 5237 row_format_after = f" {row_format_after}" if row_format_after else "" 5238 record_reader = self.sql(expression, "record_reader") 5239 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5240 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5241 5242 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5243 key_block_size = self.sql(expression, "key_block_size") 5244 if key_block_size: 5245 return f"KEY_BLOCK_SIZE = {key_block_size}" 5246 5247 using = self.sql(expression, "using") 5248 if using: 5249 return f"USING {using}" 5250 5251 parser = self.sql(expression, "parser") 5252 if parser: 5253 return f"WITH PARSER {parser}" 5254 5255 comment = self.sql(expression, "comment") 5256 if comment: 5257 return f"COMMENT {comment}" 5258 5259 visible = expression.args.get("visible") 5260 if visible is not None: 5261 return "VISIBLE" if visible else "INVISIBLE" 5262 5263 engine_attr = self.sql(expression, "engine_attr") 5264 if engine_attr: 5265 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5266 5267 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5268 if secondary_engine_attr: 5269 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5270 5271 self.unsupported("Unsupported index constraint option.") 5272 return "" 5273 5274 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5275 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5276 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5277 5278 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5279 kind = self.sql(expression, "kind") 5280 kind = f"{kind} INDEX" if kind else "INDEX" 5281 this = self.sql(expression, "this") 5282 this = f" {this}" if this else "" 5283 index_type = self.sql(expression, "index_type") 5284 index_type = f" USING {index_type}" if index_type else "" 5285 expressions = self.expressions(expression, flat=True) 5286 expressions = f" ({expressions})" if expressions else "" 5287 options = self.expressions(expression, key="options", sep=" ") 5288 options = f" {options}" if options else "" 5289 return f"{kind}{this}{index_type}{expressions}{options}" 5290 5291 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5292 if self.NVL2_SUPPORTED: 5293 return self.function_fallback_sql(expression) 5294 5295 case = exp.Case().when( 5296 expression.this.is_(exp.null()).not_(copy=False), 5297 expression.args["true"], 5298 copy=False, 5299 ) 5300 else_cond = expression.args.get("false") 5301 if else_cond: 5302 case.else_(else_cond, copy=False) 5303 5304 return self.sql(case) 5305 5306 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5307 if expression.args.get("from_first") is False: 5308 self.unsupported("NTH_VALUE FROM LAST is not supported") 5309 5310 return self.function_fallback_sql(expression) 5311 5312 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5313 this = self.sql(expression, "this") 5314 expr = self.sql(expression, "expression") 5315 position = self.sql(expression, "position") 5316 position = f", {position}" if position else "" 5317 iterator = self.sql(expression, "iterator") 5318 condition = self.sql(expression, "condition") 5319 condition = f" IF {condition}" if condition else "" 5320 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5321 5322 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5323 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5324 5325 def opclass_sql(self, expression: exp.Opclass) -> str: 5326 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5327 5328 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5329 model = self.sql(expression, "this") 5330 model = f"MODEL {model}" 5331 expr = expression.expression 5332 if expr: 5333 expr_sql = self.sql(expression, "expression") 5334 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5335 else: 5336 expr_sql = None 5337 5338 parameters = self.sql(expression, "params_struct") or None 5339 5340 return self.func(name, model, expr_sql, parameters) 5341 5342 def predict_sql(self, expression: exp.Predict) -> str: 5343 return self._ml_sql(expression, "PREDICT") 5344 5345 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5346 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5347 return self._ml_sql(expression, name) 5348 5349 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5350 return self._ml_sql(expression, "GENERATE_TEXT") 5351 5352 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5353 return self._ml_sql(expression, "GENERATE_TABLE") 5354 5355 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5356 return self._ml_sql(expression, "GENERATE_BOOL") 5357 5358 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5359 return self._ml_sql(expression, "GENERATE_INT") 5360 5361 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5362 return self._ml_sql(expression, "GENERATE_DOUBLE") 5363 5364 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5365 return self._ml_sql(expression, "TRANSLATE") 5366 5367 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5368 return self._ml_sql(expression, "FORECAST") 5369 5370 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5371 this_sql = self.sql(expression, "this") 5372 if isinstance(expression.this, exp.Table): 5373 this_sql = f"TABLE {this_sql}" 5374 5375 return self.func( 5376 "FORECAST", 5377 this_sql, 5378 expression.args.get("data_col"), 5379 expression.args.get("timestamp_col"), 5380 expression.args.get("model"), 5381 expression.args.get("id_cols"), 5382 expression.args.get("horizon"), 5383 expression.args.get("forecast_end_timestamp"), 5384 expression.args.get("confidence_level"), 5385 expression.args.get("output_historical_time_series"), 5386 expression.args.get("context_window"), 5387 ) 5388 5389 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5390 this_sql = self.sql(expression, "this") 5391 if isinstance(expression.this, exp.Table): 5392 this_sql = f"TABLE {this_sql}" 5393 5394 return self.func( 5395 "FEATURES_AT_TIME", 5396 this_sql, 5397 expression.args.get("time"), 5398 expression.args.get("num_rows"), 5399 expression.args.get("ignore_feature_nulls"), 5400 ) 5401 5402 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5403 this_sql = self.sql(expression, "this") 5404 if isinstance(expression.this, exp.Table): 5405 this_sql = f"TABLE {this_sql}" 5406 5407 query_table = self.sql(expression, "query_table") 5408 if isinstance(expression.args["query_table"], exp.Table): 5409 query_table = f"TABLE {query_table}" 5410 5411 return self.func( 5412 "VECTOR_SEARCH", 5413 this_sql, 5414 expression.args.get("column_to_search"), 5415 query_table, 5416 expression.args.get("query_column_to_search"), 5417 expression.args.get("top_k"), 5418 expression.args.get("distance_type"), 5419 expression.args.get("options"), 5420 ) 5421 5422 def forin_sql(self, expression: exp.ForIn) -> str: 5423 this = self.sql(expression, "this") 5424 expression_sql = self.sql(expression, "expression") 5425 return f"FOR {this} DO {expression_sql}" 5426 5427 @unsupported_args("partition_start", "partition_end", "force", "mode") 5428 def refresh_sql(self, expression: exp.Refresh) -> str: 5429 this = self.sql(expression, "this") 5430 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5431 return f"REFRESH {kind}{this}" 5432 5433 def toarray_sql(self, expression: exp.ToArray) -> str: 5434 arg = expression.this 5435 if not arg.type: 5436 import sqlglot.optimizer.annotate_types 5437 5438 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5439 5440 if arg.is_type(exp.DType.ARRAY): 5441 return self.sql(arg) 5442 5443 cond_for_null = arg.is_(exp.null()) 5444 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5445 5446 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5447 this = expression.this 5448 time_format = self.format_time(expression) 5449 5450 if time_format: 5451 return self.sql( 5452 exp.cast( 5453 exp.StrToTime(this=this, format=expression.args["format"]), 5454 exp.DType.TIME, 5455 ) 5456 ) 5457 5458 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5459 return self.sql(this) 5460 5461 return self.sql(exp.cast(this, exp.DType.TIME)) 5462 5463 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5464 this = expression.this 5465 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5466 return self.sql(this) 5467 5468 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5469 5470 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5471 this = expression.this 5472 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5473 return self.sql(this) 5474 5475 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5476 5477 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5478 this = expression.this 5479 time_format = self.format_time(expression) 5480 safe = expression.args.get("safe") 5481 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5482 return self.sql( 5483 exp.cast( 5484 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5485 exp.DType.DATE, 5486 ) 5487 ) 5488 5489 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5490 return self.sql(this) 5491 5492 if safe: 5493 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5494 5495 return self.sql(exp.cast(this, exp.DType.DATE)) 5496 5497 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5498 return self.sql( 5499 exp.func( 5500 "DATEDIFF", 5501 expression.this, 5502 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5503 "day", 5504 ) 5505 ) 5506 5507 def lastday_sql(self, expression: exp.LastDay) -> str: 5508 if self.LAST_DAY_SUPPORTS_DATE_PART: 5509 return self.function_fallback_sql(expression) 5510 5511 unit = expression.args.get("unit") 5512 if unit and unit.name.upper() != "MONTH": 5513 self.unsupported("Date parts are not supported in LAST_DAY.") 5514 5515 return self.func("LAST_DAY", expression.this) 5516 5517 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5518 import sqlglot.dialects.dialect 5519 5520 return self.func( 5521 "DATE_ADD", 5522 expression.this, 5523 expression.expression, 5524 sqlglot.dialects.dialect.unit_to_str(expression), 5525 ) 5526 5527 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5528 this = expression.this 5529 position = expression.args["position"] 5530 offset = index_offset - (expression.args.get("offset") or 0) 5531 5532 if offset: 5533 if position.is_int: 5534 value = position.to_py() 5535 if value >= 0: 5536 position = exp.Literal.number(value + offset) 5537 elif offset < 0 and value == -1: 5538 # 1-based -1 appends, which a 0-based position can only express as the size 5539 position = exp.ArraySize(this=this.copy()) 5540 else: 5541 # Negative positions count from the end, so they shift in the opposite 5542 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5543 position = exp.Literal.number(value - offset) 5544 else: 5545 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5546 5547 return self.func("ARRAY_INSERT", this, position, expression.expression) 5548 5549 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5550 if self.CAN_IMPLEMENT_ARRAY_ANY: 5551 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5552 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5553 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5554 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5555 5556 import sqlglot.dialects.dialect 5557 5558 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5559 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5560 self.unsupported("ARRAY_ANY is unsupported") 5561 5562 return self.function_fallback_sql(expression) 5563 5564 def struct_sql(self, expression: exp.Struct) -> str: 5565 expression.set( 5566 "expressions", 5567 [ 5568 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5569 if isinstance(e, exp.PropertyEQ) 5570 else e 5571 for e in expression.expressions 5572 ], 5573 ) 5574 5575 return self.function_fallback_sql(expression) 5576 5577 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5578 low = self.sql(expression, "this") 5579 high = self.sql(expression, "expression") 5580 5581 return f"{low} TO {high}" 5582 5583 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5584 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5585 tables = f" {self.expressions(expression)}" 5586 5587 exists = " IF EXISTS" if expression.args.get("exists") else "" 5588 5589 on_cluster = self.sql(expression, "cluster") 5590 on_cluster = f" {on_cluster}" if on_cluster else "" 5591 5592 identity = self.sql(expression, "identity") 5593 identity = f" {identity} IDENTITY" if identity else "" 5594 5595 option = self.sql(expression, "option") 5596 option = f" {option}" if option else "" 5597 5598 partition = self.sql(expression, "partition") 5599 partition = f" {partition}" if partition else "" 5600 5601 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5602 5603 # This transpiles T-SQL's CONVERT function 5604 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5605 def convert_sql(self, expression: exp.Convert) -> str: 5606 to = expression.this 5607 value = expression.expression 5608 style = expression.args.get("style") 5609 safe = expression.args.get("safe") 5610 strict = expression.args.get("strict") 5611 5612 if not to or not value: 5613 return "" 5614 5615 # Retrieve length of datatype and override to default if not specified 5616 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5617 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5618 5619 transformed: exp.Expr | None = None 5620 cast = exp.Cast if strict else exp.TryCast 5621 5622 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5623 if isinstance(style, exp.Literal) and style.is_int: 5624 import sqlglot.dialects.tsql 5625 5626 style_value = style.name 5627 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5628 if not converted_style: 5629 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5630 5631 fmt = exp.Literal.string(converted_style) 5632 5633 if to.this == exp.DType.DATE: 5634 transformed = exp.StrToDate(this=value, format=fmt) 5635 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5636 transformed = exp.StrToTime(this=value, format=fmt) 5637 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5638 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5639 elif to.this == exp.DType.TEXT: 5640 transformed = exp.TimeToStr(this=value, format=fmt) 5641 5642 if not transformed: 5643 transformed = cast(this=value, to=to, safe=safe) 5644 5645 return self.sql(transformed) 5646 5647 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5648 this = expression.this 5649 if isinstance(this, exp.JSONPathWildcard): 5650 this = self.json_path_part(this) 5651 return f".{this}" if this else "" 5652 5653 quoted = expression.args.get("quoted") 5654 if not ( 5655 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5656 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5657 return f".{this}" 5658 5659 this = self.json_path_part(this) 5660 5661 return ( 5662 f"[{this}]" 5663 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5664 else f".{this}" 5665 ) 5666 5667 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5668 this = self.json_path_part(expression.this) 5669 return f"[{this}]" if this else "" 5670 5671 def _simplify_unless_literal(self, expression: E) -> E: 5672 if not isinstance(expression, exp.Literal): 5673 import sqlglot.optimizer.simplify 5674 5675 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5676 5677 return expression 5678 5679 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5680 this = expression.this 5681 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5682 self.unsupported( 5683 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5684 ) 5685 return self.sql(this) 5686 5687 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5688 if self.IGNORE_NULLS_BEFORE_ORDER: 5689 from sqlglot.optimizer.scope import find_all_in_scope 5690 5691 # The first modifier here will be the one closest to the AggFunc's arg 5692 mods = sorted( 5693 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5694 key=lambda x: ( 5695 0 5696 if isinstance(x, exp.HavingMax) 5697 else (1 if isinstance(x, exp.Order) else 2) 5698 ), 5699 ) 5700 5701 if mods: 5702 mod = mods[0] 5703 this = expression.__class__(this=mod.this.copy()) 5704 this.meta["inline"] = True 5705 mod.this.replace(this) 5706 return self.sql(expression.this) 5707 5708 agg_func = expression.find(exp.AggFunc) 5709 5710 if agg_func: 5711 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5712 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5713 5714 return f"{self.sql(expression, 'this')} {text}" 5715 5716 def _replace_line_breaks(self, string: str) -> str: 5717 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5718 if self.pretty: 5719 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5720 return string 5721 5722 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5723 option = self.sql(expression, "this") 5724 5725 if expression.expressions: 5726 upper = option.upper() 5727 5728 # Snowflake FILE_FORMAT options are separated by whitespace 5729 sep = " " if upper == "FILE_FORMAT" else ", " 5730 5731 # Databricks copy/format options do not set their list of values with EQ 5732 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5733 values = self.expressions(expression, flat=True, sep=sep) 5734 return f"{option}{op}({values})" 5735 5736 value = self.sql(expression, "expression") 5737 5738 if not value: 5739 return option 5740 5741 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5742 5743 return f"{option}{op}{value}" 5744 5745 def credentials_sql(self, expression: exp.Credentials) -> str: 5746 cred_expr = expression.args.get("credentials") 5747 if isinstance(cred_expr, exp.Literal): 5748 # Redshift case: CREDENTIALS <string> 5749 credentials = self.sql(expression, "credentials") 5750 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5751 else: 5752 # Snowflake case: CREDENTIALS = (...) 5753 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5754 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5755 5756 storage = self.sql(expression, "storage") 5757 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5758 5759 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5760 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5761 5762 iam_role = self.sql(expression, "iam_role") 5763 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5764 5765 region = self.sql(expression, "region") 5766 region = f" REGION {region}" if region else "" 5767 5768 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5769 5770 def copy_sql(self, expression: exp.Copy) -> str: 5771 this = self.sql(expression, "this") 5772 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5773 5774 credentials = self.sql(expression, "credentials") 5775 credentials = self.seg(credentials) if credentials else "" 5776 files = self.expressions(expression, key="files", flat=True) 5777 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5778 5779 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5780 params = self.expressions( 5781 expression, 5782 key="params", 5783 sep=sep, 5784 new_line=True, 5785 skip_last=True, 5786 skip_first=True, 5787 indent=self.COPY_PARAMS_ARE_WRAPPED, 5788 ) 5789 5790 if params: 5791 if self.COPY_PARAMS_ARE_WRAPPED: 5792 params = f" WITH ({params})" 5793 elif not self.pretty and (files or credentials): 5794 params = f" {params}" 5795 5796 return f"COPY{this}{kind} {files}{credentials}{params}" 5797 5798 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5799 return "" 5800 5801 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5802 on_sql = "ON" if expression.args.get("on") else "OFF" 5803 filter_col: str | None = self.sql(expression, "filter_column") 5804 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5805 retention_period: str | None = self.sql(expression, "retention_period") 5806 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5807 5808 if filter_col or retention_period: 5809 on_sql = self.func("ON", filter_col, retention_period) 5810 5811 return f"DATA_DELETION={on_sql}" 5812 5813 def maskingpolicycolumnconstraint_sql( 5814 self, expression: exp.MaskingPolicyColumnConstraint 5815 ) -> str: 5816 this = self.sql(expression, "this") 5817 expressions = self.expressions(expression, flat=True) 5818 expressions = f" USING ({expressions})" if expressions else "" 5819 return f"MASKING POLICY {this}{expressions}" 5820 5821 def gapfill_sql(self, expression: exp.GapFill) -> str: 5822 this = self.sql(expression, "this") 5823 this = f"TABLE {this}" 5824 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5825 5826 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5827 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5828 5829 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5830 this = self.sql(expression, "this") 5831 expr = expression.expression 5832 5833 if isinstance(expr, exp.Func): 5834 # T-SQL's CLR functions are case sensitive 5835 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5836 else: 5837 expr = self.sql(expression, "expression") 5838 5839 return self.scope_resolution(expr, this) 5840 5841 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5842 if self.PARSE_JSON_NAME is None: 5843 return self.sql(expression.this) 5844 5845 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5846 5847 def rand_sql(self, expression: exp.Rand) -> str: 5848 lower = self.sql(expression, "lower") 5849 upper = self.sql(expression, "upper") 5850 5851 if lower and upper: 5852 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5853 return self.func("RAND", expression.this) 5854 5855 def changes_sql(self, expression: exp.Changes) -> str: 5856 information = self.sql(expression, "information") 5857 information = f"INFORMATION => {information}" 5858 at_before = self.sql(expression, "at_before") 5859 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5860 end = self.sql(expression, "end") 5861 end = f"{self.seg('')}{end}" if end else "" 5862 5863 return f"CHANGES ({information}){at_before}{end}" 5864 5865 def pad_sql(self, expression: exp.Pad) -> str: 5866 prefix = "L" if expression.args.get("is_left") else "R" 5867 5868 fill_pattern = self.sql(expression, "fill_pattern") or None 5869 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5870 fill_pattern = "' '" 5871 5872 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5873 5874 def summarize_sql(self, expression: exp.Summarize) -> str: 5875 table = " TABLE" if expression.args.get("table") else "" 5876 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5877 5878 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5879 generate_series = exp.GenerateSeries(**expression.args) 5880 5881 parent = expression.parent 5882 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5883 parent = parent.parent 5884 5885 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5886 return self.sql(exp.Unnest(expressions=[generate_series])) 5887 5888 if isinstance(parent, exp.Select): 5889 self.unsupported("GenerateSeries projection unnesting is not supported.") 5890 5891 return self.sql(generate_series) 5892 5893 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5894 if self.SUPPORTS_CONVERT_TIMEZONE: 5895 return self.function_fallback_sql(expression) 5896 5897 source_tz = expression.args.get("source_tz") 5898 target_tz = expression.args.get("target_tz") 5899 timestamp = expression.args.get("timestamp") 5900 5901 if source_tz and timestamp: 5902 timestamp = exp.AtTimeZone( 5903 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5904 ) 5905 5906 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5907 5908 return self.sql(expr) 5909 5910 def json_sql(self, expression: exp.JSON) -> str: 5911 this = self.sql(expression, "this") 5912 this = f" {this}" if this else "" 5913 5914 _with = expression.args.get("with_") 5915 5916 if _with is None: 5917 with_sql = "" 5918 elif not _with: 5919 with_sql = " WITHOUT" 5920 else: 5921 with_sql = " WITH" 5922 5923 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5924 5925 return f"JSON{this}{with_sql}{unique_sql}" 5926 5927 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5928 path = self.sql(expression, "path") 5929 returning = self.sql(expression, "returning") 5930 returning = f" RETURNING {returning}" if returning else "" 5931 5932 on_condition = self.sql(expression, "on_condition") 5933 on_condition = f" {on_condition}" if on_condition else "" 5934 5935 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5936 5937 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5938 regexp = " REGEXP" if expression.args.get("regexp") else "" 5939 return f"SKIP{regexp} {self.sql(expression.expression)}" 5940 5941 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5942 else_ = "ELSE " if expression.args.get("else_") else "" 5943 condition = self.sql(expression, "expression") 5944 condition = f"WHEN {condition} THEN " if condition else else_ 5945 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5946 return f"{condition}{insert}" 5947 5948 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5949 kind = self.sql(expression, "kind") 5950 source = self.sql(expression, "source") 5951 5952 if kind: 5953 expressions = self.seg(self.expressions(expression, sep=" ")) 5954 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5955 else: 5956 # If kind isn't present, we have Hive's FROM-first form: 5957 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5958 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5959 sql = f"FROM {source}{self.seg(inserts)}" 5960 5961 return self.prepend_ctes(expression, sql) 5962 5963 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5964 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5965 empty = expression.args.get("empty") 5966 empty = ( 5967 f"DEFAULT {empty} ON EMPTY" 5968 if isinstance(empty, exp.Expr) 5969 else self.sql(expression, "empty") 5970 ) 5971 5972 error = expression.args.get("error") 5973 error = ( 5974 f"DEFAULT {error} ON ERROR" 5975 if isinstance(error, exp.Expr) 5976 else self.sql(expression, "error") 5977 ) 5978 5979 if error and empty: 5980 error = ( 5981 f"{empty} {error}" 5982 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5983 else f"{error} {empty}" 5984 ) 5985 empty = "" 5986 5987 null = self.sql(expression, "null") 5988 5989 return f"{empty}{error}{null}" 5990 5991 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5992 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5993 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5994 5995 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5996 this = self.sql(expression, "this") 5997 path = self.sql(expression, "path") 5998 5999 passing = self.expressions(expression, "passing") 6000 passing = f" PASSING {passing}" if passing else "" 6001 6002 on_condition = self.sql(expression, "on_condition") 6003 on_condition = f" {on_condition}" if on_condition else "" 6004 6005 path = f"{path}{passing}{on_condition}" 6006 6007 return self.func("JSON_EXISTS", this, path) 6008 6009 def _add_arrayagg_null_filter( 6010 self, 6011 array_agg_sql: str, 6012 array_agg_expr: exp.ArrayAgg, 6013 column_expr: exp.Expr, 6014 ) -> str: 6015 """ 6016 Add NULL filter to ARRAY_AGG if dialect requires it. 6017 6018 Args: 6019 array_agg_sql: The generated ARRAY_AGG SQL string 6020 array_agg_expr: The ArrayAgg expression node 6021 column_expr: The column/expression to filter (before ORDER BY wrapping) 6022 6023 Returns: 6024 SQL string with FILTER clause added if needed 6025 """ 6026 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 6027 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 6028 if not ( 6029 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 6030 ): 6031 return array_agg_sql 6032 6033 parent = array_agg_expr.parent 6034 if isinstance(parent, exp.Filter): 6035 parent_cond = parent.expression.this 6036 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 6037 elif column_expr.find(exp.Column): 6038 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 6039 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 6040 this_sql = ( 6041 self.expressions(column_expr) 6042 if isinstance(column_expr, exp.Distinct) 6043 else self.sql(column_expr) 6044 ) 6045 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 6046 6047 return array_agg_sql 6048 6049 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 6050 array_agg = self.function_fallback_sql(expression) 6051 column_expr = expression.this 6052 if isinstance(column_expr, exp.Order): 6053 column_expr = column_expr.this 6054 6055 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 6056 6057 def slice_sql(self, expression: exp.Slice) -> str: 6058 step = self.sql(expression, "step") 6059 # An omitted end is written as `-` before a step, since `::` is the cast operator 6060 end = self.sql(expression.expression) or ("-" if step else "") 6061 begin = self.sql(expression.this) 6062 6063 sql = f"{end}:{step}" if step else end 6064 return f"{begin}:{sql}" if sql else f"{begin}:" 6065 6066 def apply_sql(self, expression: exp.Apply) -> str: 6067 this = self.sql(expression, "this") 6068 expr = self.sql(expression, "expression") 6069 6070 return f"{this} APPLY({expr})" 6071 6072 def _grant_or_revoke_sql( 6073 self, 6074 expression: exp.Grant | exp.Revoke, 6075 keyword: str, 6076 preposition: str, 6077 grant_option_prefix: str = "", 6078 grant_option_suffix: str = "", 6079 ) -> str: 6080 privileges_sql = self.expressions(expression, key="privileges", flat=True) 6081 6082 kind = self.sql(expression, "kind") 6083 kind = f" {kind}" if kind else "" 6084 6085 securable = self.sql(expression, "securable") 6086 securable = f" {securable}" if securable else "" 6087 6088 principals = self.expressions(expression, key="principals", flat=True) 6089 6090 if not expression.args.get("grant_option"): 6091 grant_option_prefix = grant_option_suffix = "" 6092 6093 # cascade for revoke only 6094 cascade = self.sql(expression, "cascade") 6095 cascade = f" {cascade}" if cascade else "" 6096 6097 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 6098 6099 def grant_sql(self, expression: exp.Grant) -> str: 6100 return self._grant_or_revoke_sql( 6101 expression, 6102 keyword="GRANT", 6103 preposition="TO", 6104 grant_option_suffix=" WITH GRANT OPTION", 6105 ) 6106 6107 def revoke_sql(self, expression: exp.Revoke) -> str: 6108 return self._grant_or_revoke_sql( 6109 expression, 6110 keyword="REVOKE", 6111 preposition="FROM", 6112 grant_option_prefix="GRANT OPTION FOR ", 6113 ) 6114 6115 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 6116 this = self.sql(expression, "this") 6117 columns = self.expressions(expression, flat=True) 6118 columns = f"({columns})" if columns else "" 6119 6120 return f"{this}{columns}" 6121 6122 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 6123 this = self.sql(expression, "this") 6124 6125 kind = self.sql(expression, "kind") 6126 kind = f"{kind} " if kind else "" 6127 6128 return f"{kind}{this}" 6129 6130 def columns_sql(self, expression: exp.Columns) -> str: 6131 func = self.function_fallback_sql(expression) 6132 if expression.args.get("unpack"): 6133 func = f"*{func}" 6134 6135 return func 6136 6137 def overlay_sql(self, expression: exp.Overlay) -> str: 6138 this = self.sql(expression, "this") 6139 expr = self.sql(expression, "expression") 6140 from_sql = self.sql(expression, "from_") 6141 for_sql = self.sql(expression, "for_") 6142 for_sql = f" FOR {for_sql}" if for_sql else "" 6143 6144 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6145 6146 @unsupported_args("format") 6147 def todouble_sql(self, expression: exp.ToDouble) -> str: 6148 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6149 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6150 6151 def string_sql(self, expression: exp.String) -> str: 6152 this = expression.this 6153 zone = expression.args.get("zone") 6154 6155 if zone: 6156 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6157 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6158 # set for source_tz to transpile the time conversion before the STRING cast 6159 this = exp.ConvertTimezone( 6160 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6161 ) 6162 6163 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6164 6165 def median_sql(self, expression: exp.Median) -> str: 6166 if not self.SUPPORTS_MEDIAN: 6167 return self.sql( 6168 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6169 ) 6170 6171 return self.function_fallback_sql(expression) 6172 6173 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6174 filler = self.sql(expression, "this") 6175 filler = f" {filler}" if filler else "" 6176 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6177 return f"TRUNCATE{filler} {with_count}" 6178 6179 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6180 if self.SUPPORTS_UNIX_SECONDS: 6181 return self.function_fallback_sql(expression) 6182 6183 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6184 6185 return self.sql( 6186 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6187 ) 6188 6189 def arraysort_sql(self, expression: exp.ArraySort) -> str: 6190 asc = expression.args.get("asc") 6191 nulls_first = expression.args.get("nulls_first") 6192 if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()): 6193 self.unsupported("ARRAY_SORT only sorts ascending with NULLs last") 6194 return self.func("ARRAY_SORT", expression.this, expression.expression) 6195 6196 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6197 dim = expression.expression 6198 6199 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6200 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6201 if not (dim.is_int and dim.name == "1"): 6202 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6203 dim = None 6204 6205 # If dimension is required but not specified, default initialize it 6206 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6207 dim = exp.Literal.number(1) 6208 6209 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6210 6211 def attach_sql(self, expression: exp.Attach) -> str: 6212 this = self.sql(expression, "this") 6213 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6214 expressions = self.expressions(expression) 6215 expressions = f" ({expressions})" if expressions else "" 6216 6217 return f"ATTACH{exists_sql} {this}{expressions}" 6218 6219 def detach_sql(self, expression: exp.Detach) -> str: 6220 kind = self.sql(expression, "kind") 6221 kind = f" {kind}" if kind else "" 6222 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6223 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6224 exists = " IF EXISTS" if expression.args.get("exists") else "" 6225 if exists: 6226 kind = kind or " DATABASE" 6227 6228 this = self.sql(expression, "this") 6229 this = f" {this}" if this else "" 6230 cluster = self.sql(expression, "cluster") 6231 cluster = f" {cluster}" if cluster else "" 6232 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6233 sync = " SYNC" if expression.args.get("sync") else "" 6234 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6235 6236 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6237 this = self.sql(expression, "this") 6238 value = self.sql(expression, "expression") 6239 value = f" {value}" if value else "" 6240 return f"{this}{value}" 6241 6242 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6243 return ( 6244 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6245 ) 6246 6247 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6248 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6249 encode = f"{encode} {self.sql(expression, 'this')}" 6250 6251 properties = expression.args.get("properties") 6252 if properties: 6253 encode = f"{encode} {self.properties(properties)}" 6254 6255 return encode 6256 6257 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6258 this = self.sql(expression, "this") 6259 include = f"INCLUDE {this}" 6260 6261 column_def = self.sql(expression, "column_def") 6262 if column_def: 6263 include = f"{include} {column_def}" 6264 6265 alias = self.sql(expression, "alias") 6266 if alias: 6267 include = f"{include} AS {alias}" 6268 6269 return include 6270 6271 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6272 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6273 name = f"{prefix} {self.sql(expression, 'this')}" 6274 return self.func("XMLELEMENT", name, *expression.expressions) 6275 6276 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6277 this = self.sql(expression, "this") 6278 expr = self.sql(expression, "expression") 6279 expr = f"({expr})" if expr else "" 6280 return f"{this}{expr}" 6281 6282 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6283 partitions = self.expressions(expression, "partition_expressions") 6284 create = self.expressions(expression, "create_expressions") 6285 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6286 6287 def partitionbyrangepropertydynamic_sql( 6288 self, expression: exp.PartitionByRangePropertyDynamic 6289 ) -> str: 6290 start = self.sql(expression, "start") 6291 end = self.sql(expression, "end") 6292 6293 every = expression.args["every"] 6294 if isinstance(every, exp.Interval) and every.this.is_string: 6295 every.this.replace(exp.Literal.number(every.name)) 6296 6297 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6298 6299 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6300 name = self.sql(expression, "this") 6301 values = self.expressions(expression, flat=True) 6302 6303 return f"NAME {name} VALUE {values}" 6304 6305 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6306 kind = self.sql(expression, "kind") 6307 sample = self.sql(expression, "sample") 6308 return f"SAMPLE {sample} {kind}" 6309 6310 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6311 kind = self.sql(expression, "kind") 6312 option = self.sql(expression, "option") 6313 option = f" {option}" if option else "" 6314 this = self.sql(expression, "this") 6315 this = f" {this}" if this else "" 6316 columns = self.expressions(expression) 6317 columns = f" {columns}" if columns else "" 6318 return f"{kind}{option} STATISTICS{this}{columns}" 6319 6320 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6321 this = self.sql(expression, "this") 6322 columns = self.expressions(expression) 6323 inner_expression = self.sql(expression, "expression") 6324 inner_expression = f" {inner_expression}" if inner_expression else "" 6325 update_options = self.sql(expression, "update_options") 6326 update_options = f" {update_options} UPDATE" if update_options else "" 6327 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6328 6329 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6330 kind = self.sql(expression, "kind") 6331 kind = f" {kind}" if kind else "" 6332 return f"DELETE{kind} STATISTICS" 6333 6334 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6335 inner_expression = self.sql(expression, "expression") 6336 return f"LIST CHAINED ROWS{inner_expression}" 6337 6338 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6339 kind = self.sql(expression, "kind") 6340 this = self.sql(expression, "this") 6341 this = f" {this}" if this else "" 6342 inner_expression = self.sql(expression, "expression") 6343 return f"VALIDATE {kind}{this}{inner_expression}" 6344 6345 def analyze_sql(self, expression: exp.Analyze) -> str: 6346 options = self.expressions(expression, key="options", sep=" ") 6347 options = f" {options}" if options else "" 6348 kind = self.sql(expression, "kind") 6349 kind = f" {kind}" if kind else "" 6350 tables = self.expressions(expression, key="tables", flat=True) 6351 tables = f" {tables}" if tables else "" 6352 mode = self.sql(expression, "mode") 6353 mode = f" {mode}" if mode else "" 6354 properties = self.sql(expression, "properties") 6355 properties = f" {properties}" if properties else "" 6356 partition = self.sql(expression, "partition") 6357 partition = f" {partition}" if partition else "" 6358 inner_expression = self.sql(expression, "expression") 6359 inner_expression = f" {inner_expression}" if inner_expression else "" 6360 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6361 6362 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6363 this = self.sql(expression, "this") 6364 namespaces = self.expressions(expression, key="namespaces") 6365 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6366 passing = self.expressions(expression, key="passing") 6367 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6368 columns = self.expressions(expression, key="columns") 6369 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6370 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6371 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6372 6373 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6374 this = self.sql(expression, "this") 6375 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6376 6377 def export_sql(self, expression: exp.Export) -> str: 6378 this = self.sql(expression, "this") 6379 connection = self.sql(expression, "connection") 6380 connection = f"WITH CONNECTION {connection} " if connection else "" 6381 options = self.sql(expression, "options") 6382 return f"EXPORT DATA {connection}{options} AS {this}" 6383 6384 def declare_sql(self, expression: exp.Declare) -> str: 6385 replace = "OR REPLACE " if expression.args.get("replace") else "" 6386 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6387 6388 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6389 variables = self.expressions(expression, "this") 6390 default = self.sql(expression, "default") 6391 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6392 6393 kind = self.sql(expression, "kind") 6394 if isinstance(expression.args.get("kind"), exp.Schema): 6395 kind = f"TABLE {kind}" 6396 6397 kind = f" {kind}" if kind else "" 6398 6399 return f"{variables}{kind}{default}" 6400 6401 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6402 kind = self.sql(expression, "kind") 6403 this = self.sql(expression, "this") 6404 set = self.sql(expression, "expression") 6405 to = self.sql(expression, "to") 6406 to = f" TO {to}" if to else "" 6407 default = self.sql(expression, "default") 6408 default = f" DEFAULT {default}" if default else "" 6409 using = self.sql(expression, "using") 6410 using = f" USING {using}" if using else "" 6411 6412 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6413 6414 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6415 6416 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6417 params = self.expressions(expression, key="params", flat=True) 6418 return self.func(expression.name, *expression.expressions) + f"({params})" 6419 6420 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6421 return self.func(expression.name, *expression.expressions) 6422 6423 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6424 return self.anonymousaggfunc_sql(expression) 6425 6426 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6427 return self.parameterizedagg_sql(expression) 6428 6429 def show_sql(self, expression: exp.Show) -> str: 6430 self.unsupported("Unsupported SHOW statement") 6431 return "" 6432 6433 def install_sql(self, expression: exp.Install) -> str: 6434 self.unsupported("Unsupported INSTALL statement") 6435 return "" 6436 6437 def liststage_sql(self, expression: exp.ListStage) -> str: 6438 pattern = self.sql(expression, "pattern") 6439 pattern = f" PATTERN = {pattern}" if pattern else "" 6440 return f"LIST {self.sql(expression, 'this')}{pattern}" 6441 6442 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6443 # Snowflake GET/PUT statements: 6444 # PUT <file> <internalStage> <properties> 6445 # GET <internalStage> <file> <properties> 6446 props = expression.args.get("properties") 6447 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6448 this = self.sql(expression, "this") 6449 target = self.sql(expression, "target") 6450 6451 if isinstance(expression, exp.Put): 6452 return f"PUT {this} {target}{props_sql}" 6453 else: 6454 return f"GET {target} {this}{props_sql}" 6455 6456 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6457 this = self.sql(expression, "this") 6458 expr = self.sql(expression, "expression") 6459 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6460 return f"TRANSLATE({this} USING {expr}{with_error})" 6461 6462 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6463 if self.SUPPORTS_DECODE_CASE: 6464 return self.func("DECODE", *expression.expressions) 6465 6466 decode_expr, *expressions = expression.expressions 6467 6468 ifs = [] 6469 for search, result in zip(expressions[::2], expressions[1::2]): 6470 if isinstance(search, exp.Literal): 6471 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6472 elif isinstance(search, exp.Null): 6473 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6474 else: 6475 if isinstance(search, exp.Binary): 6476 search = exp.paren(search) 6477 6478 cond = exp.or_( 6479 decode_expr.eq(search), 6480 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6481 copy=False, 6482 ) 6483 ifs.append(exp.If(this=cond, true=result)) 6484 6485 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6486 return self.sql(case) 6487 6488 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6489 this = self.sql(expression, "this") 6490 this = self.seg(this, sep="") 6491 dimensions = self.expressions( 6492 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6493 ) 6494 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6495 metrics = self.expressions( 6496 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6497 ) 6498 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6499 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6500 facts = self.seg(f"FACTS {facts}") if facts else "" 6501 where = self.sql(expression, "where") 6502 where = self.seg(f"WHERE {where}") if where else "" 6503 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6504 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6505 6506 def getextract_sql(self, expression: exp.GetExtract) -> str: 6507 this = expression.this 6508 expr = expression.expression 6509 6510 if not this.type or not expression.type: 6511 import sqlglot.optimizer.annotate_types 6512 6513 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6514 6515 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6516 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6517 6518 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6519 6520 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6521 return self.sql( 6522 exp.DateAdd( 6523 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6524 expression=expression.this, 6525 unit=exp.var("DAY"), 6526 ) 6527 ) 6528 6529 def space_sql(self: Generator, expression: exp.Space) -> str: 6530 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6531 6532 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6533 return f"BUILD {self.sql(expression, 'this')}" 6534 6535 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6536 method = self.sql(expression, "method") 6537 kind = expression.args.get("kind") 6538 if not kind: 6539 return f"REFRESH {method}" 6540 6541 every = self.sql(expression, "every") 6542 unit = self.sql(expression, "unit") 6543 every = f" EVERY {every} {unit}" if every else "" 6544 starts = self.sql(expression, "starts") 6545 starts = f" STARTS {starts}" if starts else "" 6546 6547 return f"REFRESH {method} ON {kind}{every}{starts}" 6548 6549 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6550 self.unsupported("The model!attribute syntax is not supported") 6551 return "" 6552 6553 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6554 return self.func("DIRECTORY", expression.this) 6555 6556 def uuid_sql(self, expression: exp.Uuid) -> str: 6557 is_string = expression.args.get("is_string", False) 6558 uuid_func_sql = self.func("UUID") 6559 6560 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6561 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6562 6563 return uuid_func_sql 6564 6565 def initcap_sql(self, expression: exp.Initcap) -> str: 6566 delimiters = expression.expression 6567 6568 if delimiters: 6569 # do not generate delimiters arg if we are round-tripping from default delimiters 6570 if ( 6571 delimiters.is_string 6572 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6573 ): 6574 delimiters = None 6575 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6576 self.unsupported("INITCAP does not support custom delimiters") 6577 delimiters = None 6578 6579 return self.func("INITCAP", expression.this, delimiters) 6580 6581 def localtime_sql(self, expression: exp.Localtime) -> str: 6582 this = expression.this 6583 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6584 6585 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6586 this = expression.this 6587 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6588 6589 def weekstart_name(self, expression: exp.WeekStart) -> str: 6590 import sqlglot.dialects.dialect 6591 6592 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6593 this = expression.this.name.upper() 6594 6595 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6596 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6597 6598 if dow_from_week_start_day != dow_from_week_offset: 6599 self.unsupported( 6600 f"WEEK({this}) is not supported; falling back to the default week start day" 6601 ) 6602 6603 return "WEEK" 6604 6605 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6606 name = self.weekstart_name(expression) 6607 6608 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6609 if isinstance(expression.parent, exp.DateTrunc): 6610 return self.sql(exp.Literal.string(name)) 6611 6612 return name 6613 6614 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6615 this = self.expressions(expression) 6616 charset = self.sql(expression, "charset") 6617 using = f" USING {charset}" if charset else "" 6618 return self.func(name, this + using) 6619 6620 def weightstring_sql(self, expression: exp.WeightString) -> str: 6621 to = self.sql(expression, "to") 6622 to = f" AS {to}" if to else "" 6623 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6624 6625 def block_sql(self, expression: exp.Block) -> str: 6626 expressions = self.expressions(expression, sep="; ", flat=True) 6627 begin = "BEGIN " if expression.args.get("begin") else "" 6628 return f"{begin}{expressions}" if expressions else "" 6629 6630 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6631 self.unsupported("Unsupported Inline UDFs syntax") 6632 return "" 6633 6634 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6635 self.unsupported("Unsupported Stored Procedure syntax") 6636 return "" 6637 6638 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6639 self.unsupported("Unsupported If block syntax") 6640 return "" 6641 6642 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6643 self.unsupported("Unsupported Case statement syntax") 6644 return "" 6645 6646 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6647 self.unsupported("Unsupported While block syntax") 6648 return "" 6649 6650 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6651 self.unsupported("Unsupported Loop block syntax") 6652 return "" 6653 6654 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6655 self.unsupported("Unsupported Repeat block syntax") 6656 return "" 6657 6658 def leave_sql(self, expression: exp.Leave) -> str: 6659 self.unsupported("Unsupported Leave syntax") 6660 return "" 6661 6662 def iterate_sql(self, expression: exp.Iterate) -> str: 6663 self.unsupported("Unsupported Iterate syntax") 6664 return "" 6665 6666 def execute_sql(self, expression: exp.Execute) -> str: 6667 self.unsupported("Unsupported Execute syntax") 6668 return "" 6669 6670 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6671 self.unsupported("Unsupported Execute syntax") 6672 return "" 6673 6674 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6675 props = self.expressions(expression, sep=" ") 6676 return f"MODIFY {props}" 6677 6678 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6679 kind = expression.args.get("kind") 6680 return f"USING {kind} {self.sql(expression, 'this')}" 6681 6682 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6683 this = self.sql(expression, "this") 6684 to = self.sql(expression, "to") 6685 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)
934 def __init__( 935 self, 936 pretty: bool | int | None = None, 937 identify: str | bool = False, 938 normalize: bool = False, 939 pad: int = 2, 940 indent: int = 2, 941 normalize_functions: str | bool | None = None, 942 unsupported_level: ErrorLevel = ErrorLevel.WARN, 943 max_unsupported: int = 3, 944 leading_comma: bool = False, 945 max_text_width: int = 80, 946 comments: bool = True, 947 dialect: DialectType = None, 948 ): 949 import sqlglot 950 import sqlglot.dialects.dialect 951 952 self.pretty = pretty if pretty is not None else sqlglot.pretty 953 self.identify = identify 954 self.normalize = normalize 955 self.pad = pad 956 self._indent = indent 957 self.unsupported_level = unsupported_level 958 self.max_unsupported = max_unsupported 959 self.leading_comma = leading_comma 960 self.max_text_width = max_text_width 961 self.comments = comments 962 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 963 964 # This is both a Dialect property and a Generator argument, so we prioritize the latter 965 self.normalize_functions = ( 966 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 967 ) 968 969 self.unsupported_messages: list[str] = [] 970 self._escaped_quote_end: str = ( 971 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 972 ) 973 self._escaped_byte_quote_end: str = ( 974 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 975 if self.dialect.BYTE_END 976 else "" 977 ) 978 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 979 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 980 981 self._next_name = name_sequence("_t") 982 983 self._identifier_start = self.dialect.IDENTIFIER_START 984 self._identifier_end = self.dialect.IDENTIFIER_END 985 986 self._quote_json_path_key_using_brackets = True 987 988 cls = type(self) 989 dispatch = _DISPATCH_CACHE.get(cls) 990 if dispatch is None: 991 dispatch = _build_dispatch(cls) 992 _DISPATCH_CACHE[cls] = dispatch 993 self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] =
{<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyTagsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsTopKey'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] =
()
SUPPORTED_JSON_PATH_PARTS: ClassVar =
{<class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>}
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.AggregateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <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.properties.CopyTagsProperty'>: <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.properties.PhysicalProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.StorageProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.TableSpaceProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
MOD_PAREN_PARENT_TYPES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Mul'>, <class 'sqlglot.expressions.core.Div'>, <class 'sqlglot.expressions.core.IntDiv'>, <class 'sqlglot.expressions.core.Mod'>)
995 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 996 """ 997 Generates the SQL string corresponding to the given syntax tree. 998 999 Args: 1000 expression: The syntax tree. 1001 copy: Whether to copy the expression. The generator performs mutations so 1002 it is safer to copy. 1003 1004 Returns: 1005 The SQL string corresponding to `expression`. 1006 """ 1007 if copy: 1008 expression = expression.copy() 1009 1010 expression = self.preprocess(expression) 1011 1012 self.unsupported_messages = [] 1013 sql = self.sql(expression).strip() 1014 1015 if self.pretty: 1016 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1017 1018 if self.unsupported_level == ErrorLevel.IGNORE: 1019 return sql 1020 1021 if self.unsupported_level == ErrorLevel.WARN: 1022 for msg in self.unsupported_messages: 1023 logger.warning(msg) 1024 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1025 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1026 1027 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.
1029 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1030 """Apply generic preprocessing transformations to a given expression.""" 1031 expression = self._move_ctes_to_top_level(expression) 1032 1033 if self.ENSURE_BOOLS: 1034 import sqlglot.transforms 1035 1036 expression = sqlglot.transforms.ensure_bools(expression) 1037 1038 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1062 def sanitize_comment(self, comment: str) -> str: 1063 comment = " " + comment if comment[0].strip() else comment 1064 comment = comment + " " if comment[-1].strip() else comment 1065 1066 # Escape block comment markers to prevent premature closure or unintended nesting. 1067 # This is necessary because single-line comments (--) are converted to block comments 1068 # (/* */) on output, and any */ in the original text would close the comment early. 1069 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1070 1071 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1073 def maybe_comment( 1074 self, 1075 sql: str, 1076 expression: exp.Expr | None = None, 1077 comments: list[str] | None = None, 1078 separated: bool = False, 1079 ) -> str: 1080 comments = ( 1081 ((expression and expression.comments) if comments is None else comments) # type: ignore 1082 if self.comments 1083 else None 1084 ) 1085 1086 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1087 return sql 1088 1089 comments_list = [ 1090 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1091 for comment in comments 1092 if comment 1093 ] 1094 1095 if not comments_list: 1096 return sql 1097 1098 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1099 comments_sql = self.sep().join(comments_list) 1100 return ( 1101 f"{self.sep()}{comments_sql}{sql}" 1102 if not sql or sql[0].isspace() 1103 else f"{comments_sql}{self.sep()}{sql}" 1104 ) 1105 1106 return f"{sql} {' '.join(comments_list)}"
1108 def wrap(self, expression: exp.Expr | str) -> str: 1109 this_sql = ( 1110 self.sql(expression) 1111 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1112 else self.sql(expression, "this") 1113 ) 1114 if not this_sql: 1115 return "()" 1116 1117 this_sql = self.indent(this_sql, level=1, pad=0) 1118 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:
1134 def indent( 1135 self, 1136 sql: str, 1137 level: int = 0, 1138 pad: int | None = None, 1139 skip_first: bool = False, 1140 skip_last: bool = False, 1141 ) -> str: 1142 if not self.pretty or not sql: 1143 return sql 1144 1145 pad = self.pad if pad is None else pad 1146 lines = sql.split("\n") 1147 1148 return "\n".join( 1149 ( 1150 line 1151 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1152 else f"{' ' * (level * self._indent + pad)}{line}" 1153 ) 1154 for i, line in enumerate(lines) 1155 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1157 def sql( 1158 self, 1159 expression: str | exp.Expr | None, 1160 key: str | None = None, 1161 comment: bool = True, 1162 ) -> str: 1163 if not expression: 1164 return "" 1165 1166 if isinstance(expression, str): 1167 return expression 1168 1169 if key: 1170 value = expression.args.get(key) 1171 if value: 1172 return self.sql(value) 1173 return "" 1174 1175 handler = self._dispatch.get(expression.__class__) 1176 1177 if handler: 1178 sql = handler(self, expression) 1179 elif isinstance(expression, exp.Func): 1180 sql = self.function_fallback_sql(expression) 1181 elif isinstance(expression, exp.Property): 1182 sql = self.property_sql(expression) 1183 else: 1184 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1185 1186 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1193 def cache_sql(self, expression: exp.Cache) -> str: 1194 lazy = " LAZY" if expression.args.get("lazy") else "" 1195 table = self.sql(expression, "this") 1196 options = expression.args.get("options") 1197 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1198 sql = self.sql(expression, "expression") 1199 sql = f" AS{self.sep()}{sql}" if sql else "" 1200 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1201 return self.prepend_ctes(expression, sql)
1207 def column_parts(self, expression: exp.Column) -> str: 1208 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1209 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1210 return self.sql(expression, "this") 1211 1212 return ".".join( 1213 self.sql(part) 1214 for part in ( 1215 expression.args.get("catalog"), 1216 expression.args.get("db"), 1217 expression.args.get("table"), 1218 expression.args.get("this"), 1219 ) 1220 if part 1221 )
1223 def column_sql(self, expression: exp.Column) -> str: 1224 join_mark = " (+)" if expression.args.get("join_mark") else "" 1225 1226 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1227 join_mark = "" 1228 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1229 1230 return f"{self.column_parts(expression)}{join_mark}"
1241 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1242 column = self.sql(expression, "this") 1243 kind = self.sql(expression, "kind") 1244 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1245 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1246 kind = f"{sep}{kind}" if kind else "" 1247 constraints = f" {constraints}" if constraints else "" 1248 position = self.sql(expression, "position") 1249 position = f" {position}" if position else "" 1250 1251 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1252 kind = "" 1253 1254 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:
1261 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1262 this = self.sql(expression, "this") 1263 if expression.args.get("not_null"): 1264 persisted = " PERSISTED NOT NULL" 1265 elif expression.args.get("persisted"): 1266 persisted = " PERSISTED" 1267 else: 1268 persisted = "" 1269 1270 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:
1283 def generatedasidentitycolumnconstraint_sql( 1284 self, expression: exp.GeneratedAsIdentityColumnConstraint 1285 ) -> str: 1286 this = "" 1287 if expression.this is not None: 1288 on_null = " ON NULL" if expression.args.get("on_null") else "" 1289 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1290 1291 start = expression.args.get("start") 1292 start = f"START WITH {start}" if start else "" 1293 increment = expression.args.get("increment") 1294 increment = f" INCREMENT BY {increment}" if increment else "" 1295 minvalue = expression.args.get("minvalue") 1296 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1297 maxvalue = expression.args.get("maxvalue") 1298 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1299 cycle = expression.args.get("cycle") 1300 cycle_sql = "" 1301 1302 if cycle is not None: 1303 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1304 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1305 1306 sequence_opts = "" 1307 if start or increment or cycle_sql: 1308 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1309 sequence_opts = f" ({sequence_opts.strip()})" 1310 1311 expr = self.sql(expression, "expression") 1312 expr = f"({expr})" if expr else "IDENTITY" 1313 1314 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1316 def generatedasrowcolumnconstraint_sql( 1317 self, expression: exp.GeneratedAsRowColumnConstraint 1318 ) -> str: 1319 start = "START" if expression.args.get("start") else "END" 1320 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1321 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:
1331 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1332 desc = expression.args.get("desc") 1333 if desc is not None: 1334 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1335 options = self.expressions(expression, key="options", flat=True, sep=" ") 1336 options = f" {options}" if options else "" 1337 include = self.sql(expression, "include") 1338 return f"PRIMARY KEY{include}{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1340 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1341 this = self.sql(expression, "this") 1342 this = f" {this}" if this else "" 1343 index_type = expression.args.get("index_type") 1344 index_type = f" USING {index_type}" if index_type else "" 1345 on_conflict = self.sql(expression, "on_conflict") 1346 on_conflict = f" {on_conflict}" if on_conflict else "" 1347 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1348 options = self.expressions(expression, key="options", flat=True, sep=" ") 1349 options = f" {options}" if options else "" 1350 include = self.sql(expression, "include") 1351 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1353 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1354 input_ = expression.args.get("input_") 1355 output = expression.args.get("output") 1356 variadic = expression.args.get("variadic") 1357 1358 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1359 if variadic: 1360 return "VARIADIC" 1361 1362 if input_ and output: 1363 return f"IN{self.INOUT_SEPARATOR}OUT" 1364 if input_: 1365 return "IN" 1366 if output: 1367 return "OUT" 1368 1369 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1374 def create_sql(self, expression: exp.Create) -> str: 1375 kind = self.sql(expression, "kind") 1376 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1377 1378 properties = expression.args.get("properties") 1379 1380 if ( 1381 kind == "TRIGGER" 1382 and properties 1383 and properties.expressions 1384 and isinstance(properties.expressions[0], exp.TriggerProperties) 1385 and properties.expressions[0].args.get("constraint") 1386 ): 1387 kind = f"CONSTRAINT {kind}" 1388 1389 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1390 1391 this = self.createable_sql(expression, properties_locs) 1392 1393 properties_sql = "" 1394 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1395 exp.Properties.Location.POST_WITH 1396 ): 1397 props_ast = exp.Properties( 1398 expressions=[ 1399 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1400 *properties_locs[exp.Properties.Location.POST_WITH], 1401 ] 1402 ) 1403 props_ast.parent = expression 1404 properties_sql = self.sql(props_ast) 1405 1406 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1407 properties_sql = self.sep() + properties_sql 1408 elif not self.pretty: 1409 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1410 properties_sql = f" {properties_sql}" 1411 1412 begin = " BEGIN" if expression.args.get("begin") else "" 1413 1414 expression_sql = self.sql(expression, "expression") 1415 if expression_sql: 1416 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1417 1418 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1419 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1420 ): 1421 postalias_props_sql = "" 1422 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1423 postalias_props_sql = self.properties( 1424 exp.Properties( 1425 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1426 ), 1427 wrapped=False, 1428 ) 1429 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1430 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1431 1432 postindex_props_sql = "" 1433 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1434 postindex_props_sql = self.properties( 1435 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1436 wrapped=False, 1437 prefix=" ", 1438 ) 1439 1440 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1441 indexes = f" {indexes}" if indexes else "" 1442 index_sql = indexes + postindex_props_sql 1443 1444 replace = " OR REPLACE" if expression.args.get("replace") else "" 1445 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1446 unique = " UNIQUE" if expression.args.get("unique") else "" 1447 1448 clustered = expression.args.get("clustered") 1449 if clustered is None: 1450 clustered_sql = "" 1451 elif clustered: 1452 clustered_sql = " CLUSTERED COLUMNSTORE" 1453 else: 1454 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1455 1456 postcreate_props_sql = "" 1457 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1458 postcreate_props_sql = self.properties( 1459 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1460 sep=" ", 1461 prefix=" ", 1462 wrapped=False, 1463 ) 1464 1465 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1466 1467 postexpression_props_sql = "" 1468 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1469 postexpression_props_sql = self.properties( 1470 exp.Properties( 1471 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1472 ), 1473 sep=" ", 1474 prefix=" ", 1475 wrapped=False, 1476 ) 1477 1478 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1479 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1480 no_schema_binding = ( 1481 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1482 ) 1483 1484 clone = self.sql(expression, "clone") 1485 clone = f" {clone}" if clone else "" 1486 1487 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1488 properties_expression = f"{expression_sql}{properties_sql}" 1489 else: 1490 properties_expression = f"{properties_sql}{expression_sql}" 1491 1492 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1493 return self.prepend_ctes(expression, expression_sql)
1495 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1496 start = self.sql(expression, "start") 1497 start = f"START WITH {start}" if start else "" 1498 increment = self.sql(expression, "increment") 1499 increment = f" INCREMENT BY {increment}" if increment else "" 1500 minvalue = self.sql(expression, "minvalue") 1501 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1502 maxvalue = self.sql(expression, "maxvalue") 1503 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1504 owned = self.sql(expression, "owned") 1505 owned = f" OWNED BY {owned}" if owned else "" 1506 1507 cache = expression.args.get("cache") 1508 if cache is None: 1509 cache_str = "" 1510 elif cache is True: 1511 cache_str = " CACHE" 1512 else: 1513 cache_str = f" CACHE {cache}" 1514 1515 options = self.expressions(expression, key="options", flat=True, sep=" ") 1516 options = f" {options}" if options else "" 1517 1518 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1520 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1521 timing = expression.args.get("timing", "") 1522 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1523 timing_events = f"{timing} {events}".strip() if timing or events else "" 1524 1525 parts = [timing_events, "ON", self.sql(expression, "table")] 1526 1527 if referenced_table := expression.args.get("referenced_table"): 1528 parts.extend(["FROM", self.sql(referenced_table)]) 1529 1530 if deferrable := expression.args.get("deferrable"): 1531 parts.append(deferrable) 1532 1533 if initially := expression.args.get("initially"): 1534 parts.append(f"INITIALLY {initially}") 1535 1536 if referencing := expression.args.get("referencing"): 1537 parts.append(self.sql(referencing)) 1538 1539 if for_each := expression.args.get("for_each"): 1540 parts.append(f"FOR EACH {for_each}") 1541 1542 if when := expression.args.get("when"): 1543 parts.append(f"WHEN ({self.sql(when)})") 1544 1545 parts.append(self.sql(expression, "execute")) 1546 1547 return self.sep().join(parts)
1549 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1550 parts = [] 1551 1552 if old_alias := expression.args.get("old"): 1553 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1554 1555 if new_alias := expression.args.get("new"): 1556 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1557 1558 return f"REFERENCING {' '.join(parts)}"
1567 def clone_sql(self, expression: exp.Clone) -> str: 1568 this = self.sql(expression, "this") 1569 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1570 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1571 return f"{shallow}{keyword} {this}"
1573 def describe_sql(self, expression: exp.Describe) -> str: 1574 style = expression.args.get("style") 1575 style = f" {style}" if style else "" 1576 partition = self.sql(expression, "partition") 1577 partition = f" {partition}" if partition else "" 1578 format = self.sql(expression, "format") 1579 format = f" {format}" if format else "" 1580 as_json = " AS JSON" if expression.args.get("as_json") else "" 1581 1582 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1594 def with_sql(self, expression: exp.With) -> str: 1595 udfs = self.expressions(expression, key="udfs", flat=True) 1596 udfs = f"WITH {udfs}" if udfs else "" 1597 1598 sql = self.expressions(expression, flat=True) 1599 1600 recursive = ( 1601 "RECURSIVE " 1602 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1603 else "" 1604 ) 1605 search = self.sql(expression, "search") 1606 search = f" {search}" if search else "" 1607 cycle = self.sql(expression, "cycle") 1608 cycle = f" {cycle}" if cycle else "" 1609 1610 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1611 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1613 def cte_sql(self, expression: exp.CTE) -> str: 1614 alias = expression.args.get("alias") 1615 if alias: 1616 alias.add_comments(expression.pop_comments()) 1617 1618 alias_sql = self.sql(expression, "alias") 1619 1620 materialized = expression.args.get("materialized") 1621 if materialized is False: 1622 materialized = "NOT MATERIALIZED " 1623 elif materialized: 1624 materialized = "MATERIALIZED " 1625 1626 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1627 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1628 1629 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1631 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1632 alias = self.sql(expression, "this") 1633 columns = self.expressions(expression, key="columns", flat=True) 1634 columns = f"({columns})" if columns else "" 1635 1636 if ( 1637 columns 1638 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1639 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1640 ): 1641 columns = "" 1642 self.unsupported("Named columns are not supported in table alias.") 1643 1644 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1645 alias = self._next_name() 1646 1647 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1655 def hexstring_sql( 1656 self, expression: exp.HexString, binary_function_repr: str | None = None 1657 ) -> str: 1658 this = self.sql(expression, "this") 1659 is_integer_type = expression.args.get("is_integer") 1660 1661 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1662 not self.dialect.HEX_START and not binary_function_repr 1663 ): 1664 # Integer representation will be returned if: 1665 # - The read dialect treats the hex value as integer literal but not the write 1666 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1667 return f"{int(this, 16)}" 1668 1669 if not is_integer_type: 1670 # Read dialect treats the hex value as BINARY/BLOB 1671 if binary_function_repr: 1672 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1673 return self.func(binary_function_repr, exp.Literal.string(this)) 1674 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1675 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1676 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1677 1678 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1680 def bytestring_sql(self, expression: exp.ByteString) -> str: 1681 this = self.sql(expression, "this") 1682 is_bytes = bool(expression.args.get("is_bytes")) 1683 if is_bytes and self.BYTE_STRING_FUNCTION: 1684 # Hex digits can be quoted directly without escaping. 1685 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1686 1687 if self.dialect.BYTE_START: 1688 escaped_byte_string = self.escape_str( 1689 this, 1690 escape_backslash=bool( 1691 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1692 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1693 ), 1694 delimiter=self.dialect.BYTE_END, 1695 escaped_delimiter=self._escaped_byte_quote_end, 1696 is_byte_string=True, 1697 is_bytes=is_bytes, 1698 ) 1699 delimited_byte_string = ( 1700 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1701 ) 1702 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1703 return self.sql( 1704 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1705 ) 1706 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1707 return self.sql( 1708 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1709 ) 1710 1711 return delimited_byte_string 1712 1713 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1714 return self.sql(exp.Literal.string(this)) 1715 1716 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1717 return ""
1719 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1720 this = self.sql(expression, "this") 1721 escape = expression.args.get("escape") 1722 unicode_start = self.dialect.UNICODE_START 1723 1724 if unicode_start: 1725 escape_substitute = r"\\\1" 1726 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1727 else: 1728 escape_substitute = r"\\u\1" 1729 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1730 1731 if escape: 1732 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1733 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1734 else: 1735 escape_pattern = ESCAPED_UNICODE_RE 1736 escape_sql = "" 1737 1738 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1739 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1740 1741 if unicode_start: 1742 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1743 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1744 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1745 else: 1746 this = self.escape_str(this, escape_backslash=False) 1747 1748 return f"{left_quote}{this}{right_quote}{escape_sql}"
1750 def rawstring_sql(self, expression: exp.RawString) -> str: 1751 string = expression.this 1752 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1753 string = string.replace("\\", "\\\\") 1754 1755 string = self.escape_str(string, escape_backslash=False) 1756 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:
1764 def datatype_param_bound_limiter( 1765 self, 1766 expression: exp.DataType, 1767 type_value: exp.DType, 1768 defaults: tuple[int, ...], 1769 bounds: tuple[int | None, ...], 1770 ) -> exp.DataType: 1771 params = expression.expressions 1772 1773 if not params: 1774 if defaults: 1775 expression.set( 1776 "expressions", 1777 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1778 ) 1779 return expression 1780 1781 if not bounds: 1782 return expression 1783 1784 for i, param in enumerate(params): 1785 bound = bounds[i] if i < len(bounds) else None 1786 if bound is None: 1787 continue 1788 1789 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1790 value = ( 1791 param_value.to_py() 1792 if isinstance(param_value, exp.Literal) and param_value.is_number 1793 else None 1794 ) 1795 if isinstance(value, (int, Decimal)) and value > bound: 1796 self.unsupported( 1797 f"{type_value.value} parameter {param_value.name} exceeds " 1798 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1799 ) 1800 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1801 1802 return expression
1804 def datatype_sql(self, expression: exp.DataType) -> str: 1805 nested = "" 1806 values = "" 1807 1808 expr_nested = expression.args.get("nested") 1809 type_value = expression.this 1810 1811 if ( 1812 not expr_nested 1813 and isinstance(type_value, exp.DType) 1814 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1815 ): 1816 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1817 1818 interior = ( 1819 self.expressions( 1820 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1821 ) 1822 if expr_nested and self.pretty 1823 else self.expressions(expression, flat=True) 1824 ) 1825 1826 if type_value in self.UNSUPPORTED_TYPES: 1827 self.unsupported( 1828 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1829 ) 1830 1831 type_sql: t.Any = "" 1832 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1833 type_sql = self.sql(expression, "kind") 1834 elif type_value == exp.DType.CHARACTER_SET: 1835 char = f"CHAR({interior})" if interior else "CHAR" 1836 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1837 else: 1838 type_sql = ( 1839 self.TYPE_MAPPING.get(type_value, type_value.value) 1840 if isinstance(type_value, exp.DType) 1841 else type_value 1842 ) 1843 1844 if interior: 1845 if expr_nested: 1846 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1847 if expression.args.get("values") is not None: 1848 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1849 values = self.expressions(expression, key="values", flat=True) 1850 values = f"{delimiters[0]}{values}{delimiters[1]}" 1851 elif type_value == exp.DType.INTERVAL: 1852 nested = f" {interior}" 1853 else: 1854 nested = f"({interior})" 1855 1856 type_sql = f"{type_sql}{nested}{values}" 1857 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1858 exp.DType.TIMETZ, 1859 exp.DType.TIMESTAMPTZ, 1860 ): 1861 type_sql = f"{type_sql} WITH TIME ZONE" 1862 1863 collate = self.sql(expression, "collate") 1864 if collate: 1865 type_sql = f"{type_sql} COLLATE {collate}" 1866 1867 return type_sql
1869 def directory_sql(self, expression: exp.Directory) -> str: 1870 local = "LOCAL " if expression.args.get("local") else "" 1871 row_format = self.sql(expression, "row_format") 1872 row_format = f" {row_format}" if row_format else "" 1873 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1875 def delete_sql(self, expression: exp.Delete) -> str: 1876 hint = self.sql(expression, "hint") 1877 this = self.sql(expression, "this") 1878 this = f" FROM {this}" if this else "" 1879 using = self.expressions(expression, key="using") 1880 using = f" USING {using}" if using else "" 1881 cluster = self.sql(expression, "cluster") 1882 cluster = f" {cluster}" if cluster else "" 1883 where = self.sql(expression, "where") 1884 returning = self.sql(expression, "returning") 1885 order = self.sql(expression, "order") 1886 limit = self.sql(expression, "limit") 1887 tables = self.expressions(expression, key="tables") 1888 tables = f" {tables}" if tables else "" 1889 if self.RETURNING_END: 1890 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1891 else: 1892 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1893 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1895 def drop_sql(self, expression: exp.Drop) -> str: 1896 tables = self.expressions(expression, key="tables", flat=True) 1897 expressions = self.expressions(expression, flat=True) 1898 expressions = f" ({expressions})" if expressions else "" 1899 kind = expression.args["kind"] 1900 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1901 iceberg = ( 1902 " ICEBERG" 1903 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1904 else "" 1905 ) 1906 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1907 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1908 on_cluster = self.sql(expression, "cluster") 1909 on_cluster = f" {on_cluster}" if on_cluster else "" 1910 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1911 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1912 cascade = " CASCADE" if expression.args.get("cascade") else "" 1913 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1914 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1915 purge = " PURGE" if expression.args.get("purge") else "" 1916 sync = " SYNC" if expression.args.get("sync") else "" 1917 force = " FORCE" if expression.args.get("force") else "" 1918 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1920 def set_operation(self, expression: exp.SetOperation) -> str: 1921 op_type = type(expression) 1922 op_name = op_type.key.upper() 1923 1924 distinct = expression.args.get("distinct") 1925 if ( 1926 distinct is False 1927 and op_type in (exp.Except, exp.Intersect) 1928 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1929 ): 1930 self.unsupported(f"{op_name} ALL is not supported") 1931 1932 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1933 1934 if distinct is None: 1935 distinct = default_distinct 1936 if distinct is None: 1937 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1938 1939 if distinct is default_distinct: 1940 distinct_or_all = "" 1941 else: 1942 distinct_or_all = " DISTINCT" if distinct else " ALL" 1943 1944 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1945 side_kind = f"{side_kind} " if side_kind else "" 1946 1947 by_name = " BY NAME" if expression.args.get("by_name") else "" 1948 on = self.expressions(expression, key="on", flat=True) 1949 on = f" ON ({on})" if on else "" 1950 1951 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1953 def set_operations(self, expression: exp.SetOperation) -> str: 1954 if not self.SET_OP_MODIFIERS: 1955 limit = expression.args.get("limit") 1956 order = expression.args.get("order") 1957 offset = expression.args.get("offset") 1958 1959 if limit or order or offset: 1960 select = self._move_ctes_to_top_level( 1961 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1962 ) 1963 1964 for arg in ("limit", "order", "offset"): 1965 if value := expression.args.get(arg): 1966 select.set(arg, value.pop()) 1967 return self.sql(select) 1968 1969 sqls: list[str] = [] 1970 stack: list[str | exp.Expr | int | None] = [expression] 1971 depth = 0 1972 open_parens = "" 1973 1974 while stack: 1975 node = stack.pop() 1976 1977 if isinstance(node, int): 1978 # Closes a plain grouping opened below, restoring its depth 1979 depth = node 1980 sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0) 1981 elif isinstance(node, exp.SetOperation): 1982 stack.append(node.expression) 1983 stack.append( 1984 self.maybe_comment( 1985 self.set_operation(node), comments=node.comments, separated=True 1986 ) 1987 ) 1988 left = node.this 1989 if ( 1990 not self.SET_OP_PARENTHESIZED_OPERANDS 1991 and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 1992 ): 1993 # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper: 1994 # it renames duplicate columns and deepens recursion. Inner CTEs and 1995 # modifiers still need the wrapper. 1996 wrappers: list[exp.Subquery] = [] 1997 inner = left 1998 while isinstance(inner, exp.Subquery) and inner.is_wrapper: 1999 wrappers.append(inner) 2000 inner = inner.this 2001 2002 if ( 2003 isinstance(inner, exp.SetOperation) 2004 and not inner.args.get("with_") 2005 and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS) 2006 ): 2007 for wrapper in wrappers: 2008 inner.add_comments(wrapper.pop_comments()) 2009 left = inner 2010 elif ( 2011 self.SET_OP_PARENTHESIZED_OPERANDS 2012 and isinstance(left, exp.Subquery) 2013 and left.is_wrapper 2014 and not left.comments 2015 and isinstance(left.this, exp.SetOperation) 2016 and not left.this.args.get("with_") 2017 and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS) 2018 ): 2019 # Write a plain grouping's parentheses here rather than recursing through 2020 # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and 2021 # long ones would exhaust the stack. The depth pushed here marks the close. 2022 open_parens += self.indent("(", level=depth, pad=0) + self.sep("") 2023 stack.append(depth) 2024 depth += 1 2025 left = left.this 2026 stack.append(left) 2027 else: 2028 if ( 2029 not self.SET_OP_LIMITS 2030 and isinstance(node, exp.Select) 2031 and node.args.get("limit") 2032 ): 2033 node = node.subquery(copy=False) 2034 if not self.SET_OP_PARENTHESIZED_OPERANDS: 2035 node = exp.select("*").from_(node, copy=False) 2036 elif ( 2037 not self.SET_OP_PARENTHESIZED_OPERANDS 2038 and isinstance(node, exp.Subquery) 2039 and isinstance(node.unnest(), exp.SetOperation) 2040 ): 2041 node = exp.select("*").from_(node, copy=False) 2042 sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0)) 2043 open_parens = "" 2044 2045 this = self.sep().join(sqls) 2046 this = self.query_modifiers(expression, this) 2047 return self.prepend_ctes(expression, this)
2049 def fetch_sql(self, expression: exp.Fetch) -> str: 2050 direction = expression.args.get("direction") 2051 direction = f" {direction}" if direction else "" 2052 count = self.sql(expression, "count") 2053 count = f" {count}" if count else "" 2054 limit_options = self.sql(expression, "limit_options") 2055 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 2056 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
2058 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 2059 percent = " PERCENT" if expression.args.get("percent") else "" 2060 rows = " ROWS" if expression.args.get("rows") else "" 2061 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 2062 if not with_ties and rows: 2063 with_ties = " ONLY" 2064 return f"{percent}{rows}{with_ties}"
2081 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2082 using = self.sql(expression, "using") 2083 using = f" USING {using}" if using else "" 2084 columns = self.expressions(expression, key="columns", flat=True) 2085 columns = f"({columns})" if columns else "" 2086 partition_by = self.expressions(expression, key="partition_by", flat=True) 2087 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2088 where = self.sql(expression, "where") 2089 include = self.expressions(expression, key="include", flat=True) 2090 if include: 2091 include = f" INCLUDE ({include})" 2092 with_storage = self.expressions(expression, key="with_storage", flat=True) 2093 with_storage = f" WITH ({with_storage})" if with_storage else "" 2094 tablespace = self.sql(expression, "tablespace") 2095 if tablespace: 2096 prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX" 2097 tablespace = f"{prefix} TABLESPACE {tablespace}" 2098 on = self.sql(expression, "on") 2099 on = f" ON {on}" if on else "" 2100 2101 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2103 def index_sql(self, expression: exp.Index) -> str: 2104 unique = "UNIQUE " if expression.args.get("unique") else "" 2105 primary = "PRIMARY " if expression.args.get("primary") else "" 2106 amp = "AMP " if expression.args.get("amp") else "" 2107 name = self.sql(expression, "this") 2108 name = f"{name} " if name else "" 2109 table = self.sql(expression, "table") 2110 table = f"{self.INDEX_ON} {table}" if table else "" 2111 2112 index = "INDEX " if not table else "" 2113 2114 params = self.sql(expression, "params") 2115 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2117 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2118 this = expression.this 2119 if this and this.is_string: 2120 resolved = maybe_parse(this.name).sql(self.dialect) 2121 if "expressions" in expression.args: 2122 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2123 # We can't safely emit the call to other dialects since name/arg semantics may differ 2124 self.unsupported( 2125 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2126 ) 2127 return resolved 2128 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2129 return self.func("IDENTIFIER", this)
2131 def identifier_sql(self, expression: exp.Identifier) -> str: 2132 text = expression.name 2133 lower = text.lower() 2134 quoted = expression.quoted 2135 text = lower if self.normalize and not quoted else text 2136 if self._identifier_escaped_sequences: 2137 for char, escaped in self._identifier_escaped_sequences.items(): 2138 text = text.replace(char, escaped) 2139 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2140 if ( 2141 quoted 2142 or self.dialect.can_quote(expression, self.identify) 2143 or lower in self.RESERVED_KEYWORDS 2144 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2145 ): 2146 text = ( 2147 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2148 ) 2149 return text
2164 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2165 input_format = self.sql(expression, "input_format") 2166 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2167 output_format = self.sql(expression, "output_format") 2168 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2169 return self.sep().join((input_format, output_format))
2179 def properties_sql(self, expression: exp.Properties) -> str: 2180 root_properties = [] 2181 with_properties = [] 2182 2183 for p in expression.expressions: 2184 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2185 if p_loc == exp.Properties.Location.POST_WITH: 2186 with_properties.append(p) 2187 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2188 root_properties.append(p) 2189 2190 root_props_ast = exp.Properties(expressions=root_properties) 2191 root_props_ast.parent = expression.parent 2192 2193 with_props_ast = exp.Properties(expressions=with_properties) 2194 with_props_ast.parent = expression.parent 2195 2196 root_props = self.root_properties(root_props_ast) 2197 with_props = self.with_properties(with_props_ast) 2198 2199 if root_props and with_props and not self.pretty: 2200 with_props = " " + with_props 2201 2202 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2209 def properties( 2210 self, 2211 properties: exp.Properties, 2212 prefix: str = "", 2213 sep: str = ", ", 2214 suffix: str = "", 2215 wrapped: bool = True, 2216 ) -> str: 2217 if properties.expressions: 2218 expressions = self.expressions(properties, sep=sep, indent=False) 2219 if expressions: 2220 expressions = self.wrap(expressions) if wrapped else expressions 2221 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2222 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2227 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2228 properties_locs = defaultdict(list) 2229 for p in properties.expressions: 2230 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2231 if p_loc != exp.Properties.Location.UNSUPPORTED: 2232 properties_locs[p_loc].append(p) 2233 else: 2234 self.unsupported(f"Unsupported property {p.key}") 2235 2236 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2243 def property_sql(self, expression: exp.Property) -> str: 2244 property_cls = expression.__class__ 2245 if property_cls == exp.Property: 2246 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2247 2248 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2249 if not property_name: 2250 self.unsupported(f"Unsupported property {expression.key}") 2251 return "" 2252 2253 return f"{property_name}={self.sql(expression, 'this')}"
2258 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2259 if self.SUPPORTS_CREATE_TABLE_LIKE: 2260 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2261 options = f" {options}" if options else "" 2262 2263 like = f"LIKE {self.sql(expression, 'this')}{options}" 2264 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2265 like = f"({like})" 2266 2267 return like 2268 2269 if expression.expressions: 2270 self.unsupported("Transpilation of LIKE property options is unsupported") 2271 2272 select = exp.select("*").from_(expression.this).limit(0) 2273 return f"AS {self.sql(select)}"
2280 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2281 no = "NO " if expression.args.get("no") else "" 2282 local = expression.args.get("local") 2283 local = f"{local} " if local else "" 2284 dual = "DUAL " if expression.args.get("dual") else "" 2285 before = "BEFORE " if expression.args.get("before") else "" 2286 after = "AFTER " if expression.args.get("after") else "" 2287 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:
2303 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2304 if expression.args.get("no"): 2305 return "NO MERGEBLOCKRATIO" 2306 if expression.args.get("default"): 2307 return "DEFAULT MERGEBLOCKRATIO" 2308 2309 percent = " PERCENT" if expression.args.get("percent") else "" 2310 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2317 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2318 default = expression.args.get("default") 2319 minimum = expression.args.get("minimum") 2320 maximum = expression.args.get("maximum") 2321 if default or minimum or maximum: 2322 if default: 2323 prop = "DEFAULT" 2324 elif minimum: 2325 prop = "MINIMUM" 2326 else: 2327 prop = "MAXIMUM" 2328 return f"{prop} DATABLOCKSIZE" 2329 units = expression.args.get("units") 2330 units = f" {units}" if units else "" 2331 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2333 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2334 autotemp = expression.args.get("autotemp") 2335 always = expression.args.get("always") 2336 default = expression.args.get("default") 2337 manual = expression.args.get("manual") 2338 never = expression.args.get("never") 2339 2340 if autotemp is not None: 2341 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2342 elif always: 2343 prop = "ALWAYS" 2344 elif default: 2345 prop = "DEFAULT" 2346 elif manual: 2347 prop = "MANUAL" 2348 elif never: 2349 prop = "NEVER" 2350 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2352 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2353 no = expression.args.get("no") 2354 no = " NO" if no else "" 2355 concurrent = expression.args.get("concurrent") 2356 concurrent = " CONCURRENT" if concurrent else "" 2357 target = self.sql(expression, "target") 2358 target = f" {target}" if target else "" 2359 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2361 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2362 if isinstance(expression.this, list): 2363 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2364 if expression.this: 2365 modulus = self.sql(expression, "this") 2366 remainder = self.sql(expression, "expression") 2367 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2368 2369 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2370 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2371 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2373 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2374 this = self.sql(expression, "this") 2375 2376 for_values_or_default = expression.expression 2377 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2378 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2379 else: 2380 for_values_or_default = " DEFAULT" 2381 2382 return f"PARTITION OF {this}{for_values_or_default}"
2384 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2385 kind = expression.args.get("kind") 2386 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2387 for_or_in = expression.args.get("for_or_in") 2388 for_or_in = f" {for_or_in}" if for_or_in else "" 2389 lock_type = expression.args.get("lock_type") 2390 override = " OVERRIDE" if expression.args.get("override") else "" 2391 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2397 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2398 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2399 statistics = expression.args.get("statistics") 2400 statistics_sql = "" 2401 if statistics is not None: 2402 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2403 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2405 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2406 this = self.sql(expression, "this") 2407 this = f"HISTORY_TABLE={this}" if this else "" 2408 data_consistency: str | None = self.sql(expression, "data_consistency") 2409 data_consistency = ( 2410 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2411 ) 2412 retention_period: str | None = self.sql(expression, "retention_period") 2413 retention_period = ( 2414 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2415 ) 2416 2417 if this: 2418 on_sql = self.func("ON", this, data_consistency, retention_period) 2419 else: 2420 on_sql = "ON" if expression.args.get("on") else "OFF" 2421 2422 sql = f"SYSTEM_VERSIONING={on_sql}" 2423 2424 return f"WITH({sql})" if expression.args.get("with_") else sql
2426 def insert_sql(self, expression: exp.Insert) -> str: 2427 hint = self.sql(expression, "hint") 2428 overwrite = expression.args.get("overwrite") 2429 2430 if isinstance(expression.this, exp.Directory): 2431 this = " OVERWRITE" if overwrite else " INTO" 2432 else: 2433 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2434 2435 stored = self.sql(expression, "stored") 2436 stored = f" {stored}" if stored else "" 2437 alternative = expression.args.get("alternative") 2438 alternative = f" OR {alternative}" if alternative else "" 2439 ignore = " IGNORE" if expression.args.get("ignore") else "" 2440 is_function = expression.args.get("is_function") 2441 if is_function: 2442 this = f"{this} FUNCTION" 2443 this = f"{this} {self.sql(expression, 'this')}" 2444 2445 exists = " IF EXISTS" if expression.args.get("exists") else "" 2446 where = self.sql(expression, "where") 2447 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2448 using = self.expressions(expression, key="using", flat=True) 2449 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2450 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2451 on_conflict = self.sql(expression, "conflict") 2452 on_conflict = f" {on_conflict}" if on_conflict else "" 2453 by_name = " BY NAME" if expression.args.get("by_name") else "" 2454 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2455 returning = self.sql(expression, "returning") 2456 2457 if self.RETURNING_END: 2458 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2459 else: 2460 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2461 2462 partition_by = self.sql(expression, "partition") 2463 partition_by = f" {partition_by}" if partition_by else "" 2464 settings = self.sql(expression, "settings") 2465 settings = f" {settings}" if settings else "" 2466 2467 source = self.sql(expression, "source") 2468 source = f"TABLE {source}" if source else "" 2469 2470 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2471 return self.prepend_ctes(expression, sql)
2489 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2490 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2491 2492 constraint = self.sql(expression, "constraint") 2493 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2494 2495 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2496 if conflict_keys: 2497 conflict_keys = f"({conflict_keys})" 2498 2499 index_predicate = self.sql(expression, "index_predicate") 2500 conflict_keys = f"{conflict_keys}{index_predicate} " 2501 2502 action = self.sql(expression, "action") 2503 2504 expressions = self.expressions(expression, flat=True) 2505 if expressions: 2506 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2507 expressions = f" {set_keyword}{expressions}" 2508 2509 where = self.sql(expression, "where") 2510 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2515 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2516 fields = self.sql(expression, "fields") 2517 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2518 escaped = self.sql(expression, "escaped") 2519 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2520 items = self.sql(expression, "collection_items") 2521 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2522 keys = self.sql(expression, "map_keys") 2523 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2524 lines = self.sql(expression, "lines") 2525 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2526 null = self.sql(expression, "null") 2527 null = f" NULL DEFINED AS {null}" if null else "" 2528 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2556 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2557 table = self.table_parts(expression) 2558 only = "ONLY " if expression.args.get("only") else "" 2559 partition = self.sql(expression, "partition") 2560 partition = f" {partition}" if partition else "" 2561 version = self.sql(expression, "version") 2562 version = f" {version}" if version else "" 2563 alias = self.sql(expression, "alias") 2564 alias = f"{sep}{alias}" if alias else "" 2565 2566 sample = self.sql(expression, "sample") 2567 post_alias = "" 2568 pre_alias = "" 2569 2570 if self.dialect.ALIAS_POST_TABLESAMPLE: 2571 pre_alias = sample 2572 else: 2573 post_alias = sample 2574 2575 if self.dialect.ALIAS_POST_VERSION: 2576 pre_alias = f"{pre_alias}{version}" 2577 else: 2578 post_alias = f"{post_alias}{version}" 2579 2580 hints = self.expressions(expression, key="hints", sep=" ") 2581 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2582 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2583 joins = self.indent( 2584 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2585 ) 2586 laterals = self.expressions(expression, key="laterals", sep="") 2587 2588 file_format = self.sql(expression, "format") 2589 pattern = self.sql(expression, "pattern") 2590 if file_format: 2591 pattern = f", PATTERN => {pattern}" if pattern else "" 2592 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2593 elif pattern: 2594 file_format = f" (PATTERN => {pattern})" 2595 2596 ordinality = expression.args.get("ordinality") or "" 2597 if ordinality: 2598 ordinality = f" WITH ORDINALITY{alias}" 2599 alias = "" 2600 2601 when = self.sql(expression, "when") 2602 if when: 2603 if self.HISTORICAL_DATA_POST_ALIAS: 2604 alias = f"{alias} {when}" 2605 else: 2606 table = f"{table} {when}" 2607 2608 changes = self.sql(expression, "changes") 2609 changes = f" {changes}" if changes else "" 2610 2611 rows_from = self.expressions(expression, key="rows_from") 2612 if rows_from: 2613 table = f"ROWS FROM {self.wrap(rows_from)}" 2614 2615 indexed = expression.args.get("indexed") 2616 if indexed is not None: 2617 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2618 else: 2619 indexed = "" 2620 2621 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2623 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2624 table = self.func("TABLE", expression.this) 2625 alias = self.sql(expression, "alias") 2626 alias = f" AS {alias}" if alias else "" 2627 sample = self.sql(expression, "sample") 2628 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2629 joins = self.indent( 2630 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2631 ) 2632 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2634 def tablesample_sql( 2635 self, 2636 expression: exp.TableSample, 2637 tablesample_keyword: str | None = None, 2638 ) -> str: 2639 if expression.expressions: 2640 self.unsupported("Multiple sample sets are not supported") 2641 return "" 2642 2643 method = self.sql(expression, "method") 2644 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2645 numerator = self.sql(expression, "bucket_numerator") 2646 denominator = self.sql(expression, "bucket_denominator") 2647 field = self.sql(expression, "bucket_field") 2648 field = f" ON {field}" if field else "" 2649 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2650 seed = self.sql(expression, "seed") 2651 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2652 2653 size = self.sql(expression, "size") 2654 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2655 size = f"{size} ROWS" 2656 2657 percent = self.sql(expression, "percent") 2658 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2659 percent = f"{percent} PERCENT" 2660 2661 expr = f"{bucket}{percent}{size}" 2662 if self.TABLESAMPLE_REQUIRES_PARENS: 2663 expr = f"({expr})" 2664 2665 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2742 def pivot_sql(self, expression: exp.Pivot) -> str: 2743 expressions = self.expressions(expression, flat=True) 2744 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2745 2746 group = self.sql(expression, "group") 2747 2748 if expression.this: 2749 this = self.sql(expression, "this") 2750 if not expressions: 2751 sql = f"UNPIVOT {this}" 2752 else: 2753 on = f"{self.seg('ON')} {expressions}" 2754 into = self.sql(expression, "into") 2755 into = f"{self.seg('INTO')} {into}" if into else "" 2756 using = self.expressions(expression, key="using", flat=True) 2757 using = f"{self.seg('USING')} {using}" if using else "" 2758 sql = f"{direction} {this}{on}{into}{using}{group}" 2759 return self.prepend_ctes(expression, sql) 2760 2761 if not expression.unpivot: 2762 # Wrap IN-list values with explicit aliases where the target dialect would differ 2763 new_field_exprs = self._pivot_in_value_aliases(expression) 2764 if new_field_exprs is not None: 2765 expression.fields[0].set("expressions", new_field_exprs) 2766 2767 alias = self.sql(expression, "alias") 2768 if alias: 2769 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2770 2771 fields = self.expressions( 2772 expression, 2773 "fields", 2774 sep=" ", 2775 dynamic=True, 2776 new_line=True, 2777 skip_first=True, 2778 skip_last=True, 2779 ) 2780 2781 include_nulls = expression.args.get("include_nulls") 2782 if include_nulls is not None: 2783 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2784 else: 2785 nulls = "" 2786 2787 default_on_null = self.sql(expression, "default_on_null") 2788 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2789 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2790 return self.prepend_ctes(expression, sql)
2833 def update_sql(self, expression: exp.Update) -> str: 2834 hint = self.sql(expression, "hint") 2835 this = self.sql(expression, "this") 2836 join_sql, from_sql = self._update_from_joins_sql(expression) 2837 set_sql = self.expressions(expression, flat=True) 2838 where_sql = self.sql(expression, "where") 2839 returning = self.sql(expression, "returning") 2840 order = self.sql(expression, "order") 2841 limit = self.sql(expression, "limit") 2842 if self.RETURNING_END: 2843 expression_sql = f"{from_sql}{where_sql}{returning}" 2844 else: 2845 expression_sql = f"{returning}{from_sql}{where_sql}" 2846 options = self.expressions(expression, key="options") 2847 options = f" OPTION({options})" if options else "" 2848 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2849 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2851 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2852 values_as_table = values_as_table and self.VALUES_AS_TABLE 2853 2854 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2855 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2856 args = self.expressions(expression) 2857 alias = self.sql(expression, "alias") 2858 values = f"VALUES{self.seg('')}{args}" 2859 values = ( 2860 f"({values})" 2861 if self.WRAP_DERIVED_VALUES 2862 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2863 else values 2864 ) 2865 values = self.query_modifiers(expression, values) 2866 return f"{values} AS {alias}" if alias else values 2867 2868 # Converts `VALUES...` expression into a series of select unions. 2869 alias_node = expression.args.get("alias") 2870 column_names = alias_node and alias_node.columns 2871 2872 selects: list[exp.Query] = [] 2873 2874 for i, tup in enumerate(expression.expressions): 2875 row = tup.expressions 2876 2877 if i == 0 and column_names: 2878 row = [ 2879 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2880 ] 2881 2882 selects.append(exp.Select(expressions=row)) 2883 2884 if self.pretty: 2885 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2886 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2887 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2888 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2889 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2890 2891 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2892 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2893 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2898 @unsupported_args("expressions") 2899 def into_sql(self, expression: exp.Into) -> str: 2900 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2901 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2902 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2915 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2916 this = self.sql(expression, "this") 2917 2918 columns = self.expressions(expression, flat=True) 2919 2920 from_sql = self.sql(expression, "from_index") 2921 from_sql = f" FROM {from_sql}" if from_sql else "" 2922 2923 properties = expression.args.get("properties") 2924 properties_sql = ( 2925 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2926 ) 2927 2928 return f"{this}({columns}){from_sql}{properties_sql}"
2937 def group_sql(self, expression: exp.Group) -> str: 2938 group_by_all = expression.args.get("all") 2939 if group_by_all is True: 2940 modifier = " ALL" 2941 elif group_by_all is False: 2942 modifier = " DISTINCT" 2943 else: 2944 modifier = "" 2945 2946 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2947 2948 grouping_sets = self.expressions(expression, key="grouping_sets") 2949 cube = self.expressions(expression, key="cube") 2950 rollup = self.expressions(expression, key="rollup") 2951 2952 groupings = csv( 2953 self.seg(grouping_sets) if grouping_sets else "", 2954 self.seg(cube) if cube else "", 2955 self.seg(rollup) if rollup else "", 2956 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2957 sep=self.GROUPINGS_SEP, 2958 ) 2959 2960 if ( 2961 expression.expressions 2962 and groupings 2963 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2964 ): 2965 add_separator = True 2966 2967 if grouping_sets: 2968 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2969 add_separator = False 2970 else: 2971 self.unsupported( 2972 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2973 ) 2974 2975 if add_separator: 2976 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2977 2978 return f"{group_by}{groupings}"
2990 def connect_sql(self, expression: exp.Connect) -> str: 2991 start = self.sql(expression, "start") 2992 start = self.seg(f"START WITH {start}") if start else "" 2993 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2994 connect = self.sql(expression, "connect") 2995 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2996 return start + connect
3001 def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str: 3002 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 3003 side = None 3004 else: 3005 side = expression.side 3006 3007 op_sql = " ".join( 3008 op 3009 for op in ( 3010 expression.method, 3011 "GLOBAL" if expression.args.get("global_") else None, 3012 side, 3013 expression.kind, 3014 expression.hint if self.JOIN_HINTS else None, 3015 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 3016 ) 3017 if op 3018 ) 3019 match_cond = self.sql(expression, "match_condition") 3020 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 3021 nearest = self.sql(expression, "nearest") 3022 nearest = f" {nearest}" if nearest else "" 3023 on_sql = self.sql(expression, "on") 3024 using = expression.args.get("using") 3025 3026 if not on_sql and using: 3027 on_sql = csv(*(self.sql(column) for column in using)) 3028 3029 this = expression.this 3030 this_sql = self.sql(this) 3031 3032 exprs = self.expressions(expression) 3033 if exprs: 3034 this_sql = f"{this_sql},{self.seg(exprs)}" 3035 3036 if on_sql: 3037 on_sql = self.indent(on_sql, skip_first=True) 3038 space = self.seg(" " * self.pad) if self.pretty else " " 3039 if using: 3040 on_sql = f"{space}USING ({on_sql})" 3041 else: 3042 on_sql = f"{space}ON {on_sql}" 3043 elif not op_sql and not nearest: 3044 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 3045 return f" {this_sql}" 3046 3047 return f", {this_sql}" 3048 3049 if join_op: 3050 op_sql = join_op 3051 elif op_sql != "STRAIGHT_JOIN": 3052 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 3053 3054 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3055 return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}"
3057 def nearest_sql(self, expression: exp.Nearest) -> str: 3058 if not self.SUPPORTS_NEAREST_JOIN: 3059 self.unsupported("NEAREST joins are not supported") 3060 3061 approximate = "APPROX " if expression.args.get("approximate") else "" 3062 metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE" 3063 return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}"
def
lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
3070 def lateral_op(self, expression: exp.Lateral) -> str: 3071 cross_apply = expression.args.get("cross_apply") 3072 3073 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 3074 if cross_apply is True: 3075 op = "INNER JOIN " 3076 elif cross_apply is False: 3077 op = "LEFT JOIN " 3078 else: 3079 op = "" 3080 3081 return f"{op}LATERAL"
3083 def lateral_sql(self, expression: exp.Lateral) -> str: 3084 this = self.sql(expression, "this") 3085 3086 if expression.args.get("view"): 3087 alias = expression.args["alias"] 3088 columns = self.expressions(alias, key="columns", flat=True) 3089 table = f" {alias.name}" if alias.name else "" 3090 columns = f" AS {columns}" if columns else "" 3091 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 3092 return f"{op_sql}{self.sep()}{this}{table}{columns}" 3093 3094 table_alias = expression.args.get("alias") 3095 offset = expression.this.args.get("offset") 3096 3097 if ( 3098 self.UNNEST_WITH_ORDINALITY 3099 and table_alias 3100 and isinstance(expression.this, exp.Unnest) 3101 and isinstance(offset, exp.Identifier) 3102 ): 3103 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3104 table_alias = table_alias.copy() 3105 table_alias.append("columns", offset.copy()) 3106 3107 alias = self.sql(table_alias) 3108 alias = f" AS {alias}" if alias else "" 3109 3110 ordinality = expression.args.get("ordinality") or "" 3111 if ordinality: 3112 ordinality = f" WITH ORDINALITY{alias}" 3113 alias = "" 3114 3115 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3117 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3118 this = self.sql(expression, "this") 3119 3120 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3121 return this 3122 3123 args = [ 3124 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3125 for e in (expression.args.get(k) for k in ("offset", "expression")) 3126 if e 3127 ] 3128 3129 args_sql = ", ".join(self.sql(e) for e in args) 3130 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3131 expressions = self.expressions(expression, flat=True) 3132 limit_options = self.sql(expression, "limit_options") 3133 expressions = f" BY {expressions}" if expressions else "" 3134 3135 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3137 def offset_sql(self, expression: exp.Offset) -> str: 3138 this = self.sql(expression, "this") 3139 value = expression.expression 3140 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3141 expressions = self.expressions(expression, flat=True) 3142 expressions = f" BY {expressions}" if expressions else "" 3143 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3145 def setitem_sql(self, expression: exp.SetItem) -> str: 3146 kind = self.sql(expression, "kind") 3147 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3148 kind = "" 3149 else: 3150 kind = f"{kind} " if kind else "" 3151 this = self.sql(expression, "this") 3152 expressions = self.expressions(expression) 3153 collate = self.sql(expression, "collate") 3154 collate = f" COLLATE {collate}" if collate else "" 3155 global_ = "GLOBAL " if expression.args.get("global_") else "" 3156 return f"{global_}{kind}{this}{expressions}{collate}"
3163 def queryband_sql(self, expression: exp.QueryBand) -> str: 3164 this = self.sql(expression, "this") 3165 update = " UPDATE" if expression.args.get("update") else "" 3166 scope = self.sql(expression, "scope") 3167 scope = f" FOR {scope}" if scope else "" 3168 3169 return f"QUERY_BAND = {this}{update}{scope}"
3174 def lock_sql(self, expression: exp.Lock) -> str: 3175 if not self.LOCKING_READS_SUPPORTED: 3176 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3177 return "" 3178 3179 update = expression.args["update"] 3180 key = expression.args.get("key") 3181 if update: 3182 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3183 else: 3184 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3185 expressions = self.expressions(expression, flat=True) 3186 expressions = f" OF {expressions}" if expressions else "" 3187 wait = expression.args.get("wait") 3188 3189 if wait is not None: 3190 if isinstance(wait, exp.Literal): 3191 wait = f" WAIT {self.sql(wait)}" 3192 else: 3193 wait = " NOWAIT" if wait else " SKIP LOCKED" 3194 3195 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, is_bytes: bool = False) -> str:
3203 def escape_str( 3204 self, 3205 text: str, 3206 escape_backslash: bool = True, 3207 delimiter: str | None = None, 3208 escaped_delimiter: str | None = None, 3209 is_byte_string: bool = False, 3210 is_bytes: bool = False, 3211 ) -> str: 3212 if is_byte_string: 3213 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3214 else: 3215 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3216 3217 if supports_escape_sequences: 3218 text = "".join( 3219 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3220 for ch in text 3221 ) 3222 3223 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3224 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3225 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3226 3227 delimiter = delimiter or self.dialect.QUOTE_END 3228 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3229 3230 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3232 def loaddata_sql(self, expression: exp.LoadData) -> str: 3233 is_overwrite = expression.args.get("overwrite") 3234 overwrite = " OVERWRITE" if is_overwrite else "" 3235 this = self.sql(expression, "this") 3236 3237 files = expression.args.get("files") 3238 if files: 3239 files_sql = self.expressions(files, flat=True) 3240 files_sql = f"FILES{self.wrap(files_sql)}" 3241 if is_overwrite: 3242 this = f" {this}" 3243 elif expression.args.get("temp"): 3244 this = f" INTO TEMP TABLE {this}" 3245 else: 3246 this = f" INTO TABLE {this}" 3247 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3248 3249 local = " LOCAL" if expression.args.get("local") else "" 3250 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3251 this = f" INTO TABLE {this}" 3252 partition = self.sql(expression, "partition") 3253 partition = f" {partition}" if partition else "" 3254 input_format = self.sql(expression, "input_format") 3255 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3256 serde = self.sql(expression, "serde") 3257 serde = f" SERDE {serde}" if serde else "" 3258 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3272 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3273 this = self.sql(expression, "this") 3274 this = f"{this} " if this else this 3275 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3276 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3278 def withfill_sql(self, expression: exp.WithFill) -> str: 3279 from_sql = self.sql(expression, "from_") 3280 from_sql = f" FROM {from_sql}" if from_sql else "" 3281 to_sql = self.sql(expression, "to") 3282 to_sql = f" TO {to_sql}" if to_sql else "" 3283 step_sql = self.sql(expression, "step") 3284 step_sql = f" STEP {step_sql}" if step_sql else "" 3285 interpolated_values = [ 3286 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3287 if isinstance(e, exp.Alias) 3288 else self.sql(e, "this") 3289 for e in expression.args.get("interpolate") or [] 3290 ] 3291 interpolate = ( 3292 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3293 ) 3294 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3348 def ordered_sql(self, expression: exp.Ordered) -> str: 3349 desc = expression.args.get("desc") 3350 asc = not desc 3351 3352 nulls_first = expression.args.get("nulls_first") 3353 nulls_last = not nulls_first 3354 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3355 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3356 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3357 3358 this = self.sql(expression, "this") 3359 3360 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3361 nulls_sort_change = "" 3362 if nulls_first and ( 3363 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3364 ): 3365 nulls_sort_change = " NULLS FIRST" 3366 elif ( 3367 nulls_last 3368 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3369 and not nulls_are_last 3370 ): 3371 nulls_sort_change = " NULLS LAST" 3372 3373 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3374 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3375 window = expression.find_ancestor(exp.Window, exp.Select) 3376 3377 if isinstance(window, exp.Window): 3378 window_this = window.this 3379 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3380 window_this = window_this.this 3381 spec = window.args.get("spec") 3382 else: 3383 window_this = None 3384 spec = None 3385 3386 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3387 # without a spec or with a ROWS spec, but not with RANGE 3388 if not ( 3389 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3390 and (not spec or spec.text("kind").upper() == "ROWS") 3391 ): 3392 if window_this and spec: 3393 self.unsupported( 3394 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3395 ) 3396 nulls_sort_change = "" 3397 elif self.NULL_ORDERING_SUPPORTED is False and ( 3398 (asc and nulls_sort_change == " NULLS LAST") 3399 or (desc and nulls_sort_change == " NULLS FIRST") 3400 ): 3401 # BigQuery does not allow these ordering/nulls combinations when used under 3402 # an aggregation func or under a window containing one 3403 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3404 3405 if isinstance(ancestor, exp.Window): 3406 ancestor = ancestor.this 3407 if isinstance(ancestor, exp.AggFunc): 3408 self.unsupported( 3409 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3410 ) 3411 nulls_sort_change = "" 3412 elif self.NULL_ORDERING_SUPPORTED is None: 3413 if expression.this.is_int: 3414 self.unsupported( 3415 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3416 ) 3417 elif not isinstance(expression.this, exp.Rand): 3418 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3419 target = self.sql(resolved) if resolved is not None else this 3420 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3421 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3422 nulls_sort_change = "" 3423 3424 with_fill = self.sql(expression, "with_fill") 3425 with_fill = f" {with_fill}" if with_fill else "" 3426 3427 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3437 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3438 partition = self.partition_by_sql(expression) 3439 order = self.sql(expression, "order") 3440 measures = self.expressions(expression, key="measures") 3441 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3442 rows = self.sql(expression, "rows") 3443 rows = self.seg(rows) if rows else "" 3444 after = self.sql(expression, "after") 3445 after = self.seg(after) if after else "" 3446 pattern = self.sql(expression, "pattern") 3447 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3448 definition_sqls = [ 3449 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3450 for definition in expression.args.get("define", []) 3451 ] 3452 definitions = self.expressions(sqls=definition_sqls) 3453 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3454 body = "".join( 3455 ( 3456 partition, 3457 order, 3458 measures, 3459 rows, 3460 after, 3461 pattern, 3462 define, 3463 ) 3464 ) 3465 alias = self.sql(expression, "alias") 3466 alias = f" {alias}" if alias else "" 3467 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3469 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3470 limit = expression.args.get("limit") 3471 3472 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3473 count = limit.args.get("count") 3474 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3475 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3476 limit = exp.Limit( 3477 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3478 ) 3479 elif ( 3480 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3481 ): 3482 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3483 3484 return csv( 3485 *sqls, 3486 *[self.sql(join) for join in expression.args.get("joins") or []], 3487 self.sql(expression, "match"), 3488 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3489 self.sql(expression, "prewhere"), 3490 self.sql(expression, "where"), 3491 self.sql(expression, "connect"), 3492 self.sql(expression, "group"), 3493 self.sql(expression, "having"), 3494 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3495 self.sql(expression, "order"), 3496 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3497 *self.after_limit_modifiers(expression), 3498 self.sql(expression, "for_"), 3499 self.options_modifier(expression), 3500 sep="", 3501 )
3507 def forclause_sql(self, expression: exp.ForClause) -> str: 3508 kind = expression.args["kind"] 3509 if kind == "BROWSE": 3510 return f"{self.sep()}FOR BROWSE" 3511 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3512 # the target dialect doesn't support QueryOption, so we drop the clause. 3513 options = self.expressions(expression, key="expressions") 3514 if not options: 3515 return "" 3516 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]:
3535 def select_sql(self, expression: exp.Select) -> str: 3536 into = expression.args.get("into") 3537 if not self.SUPPORTS_SELECT_INTO and into: 3538 into.pop() 3539 3540 hint = self.sql(expression, "hint") 3541 distinct = self.sql(expression, "distinct") 3542 distinct = f" {distinct}" if distinct else "" 3543 kind = self.sql(expression, "kind") 3544 3545 limit = expression.args.get("limit") 3546 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3547 top = self.limit_sql(limit, top=True) 3548 limit.pop() 3549 else: 3550 top = "" 3551 3552 expressions = self.expressions(expression) 3553 3554 if kind: 3555 if kind in self.SELECT_KINDS: 3556 kind = f" AS {kind}" 3557 else: 3558 if kind == "STRUCT": 3559 expressions = self.expressions( 3560 sqls=[ 3561 self.sql( 3562 exp.Struct( 3563 expressions=[ 3564 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3565 if isinstance(e, exp.Alias) 3566 else e 3567 for e in expression.expressions 3568 ] 3569 ) 3570 ) 3571 ] 3572 ) 3573 kind = "" 3574 3575 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3576 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3577 3578 exclude = expression.args.get("exclude") 3579 3580 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3581 exclude_sql = self.expressions(sqls=exclude, flat=True) 3582 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3583 3584 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3585 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3586 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3587 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3588 sql = self.query_modifiers( 3589 expression, 3590 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3591 self.sql(expression, "into", comment=False), 3592 self.sql(expression, "from_", comment=False), 3593 ) 3594 3595 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3596 if expression.args.get("with_"): 3597 sql = self.maybe_comment(sql, expression) 3598 expression.pop_comments() 3599 3600 sql = self.prepend_ctes(expression, sql) 3601 3602 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3603 expression.set("exclude", None) 3604 subquery = expression.subquery(copy=False) 3605 star = exp.Star(except_=exclude) 3606 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3607 3608 if not self.SUPPORTS_SELECT_INTO and into: 3609 if into.args.get("temporary"): 3610 table_kind = " TEMPORARY" 3611 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3612 table_kind = " UNLOGGED" 3613 else: 3614 table_kind = "" 3615 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3616 3617 return sql
3629 def star_sql(self, expression: exp.Star) -> str: 3630 except_ = self.expressions(expression, key="except_", flat=True) 3631 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3632 replace = self.expressions(expression, key="replace", flat=True) 3633 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3634 rename = self.expressions(expression, key="rename", flat=True) 3635 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3636 ilike = self.sql(expression, "ilike") 3637 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3638 return f"*{ilike}{except_}{replace}{rename}"
3654 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3655 alias = self.sql(expression, "alias") 3656 alias = f"{sep}{alias}" if alias else "" 3657 sample = self.sql(expression, "sample") 3658 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3659 alias = f"{sample}{alias}" 3660 3661 # Set to None so it's not generated again by self.query_modifiers() 3662 expression.set("sample", None) 3663 3664 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3665 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3666 return self.prepend_ctes(expression, sql)
3672 def unnest_sql(self, expression: exp.Unnest) -> str: 3673 args = self.expressions(expression, flat=True) 3674 3675 alias = expression.args.get("alias") 3676 offset = expression.args.get("offset") 3677 3678 if self.UNNEST_WITH_ORDINALITY: 3679 if alias and isinstance(offset, exp.Expr): 3680 alias.append("columns", offset) 3681 expression.set("offset", None) 3682 3683 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3684 columns = alias.columns 3685 alias = self.sql(columns[0]) if columns else "" 3686 else: 3687 alias = self.sql(alias) 3688 3689 alias = f" AS {alias}" if alias else alias 3690 if self.UNNEST_WITH_ORDINALITY: 3691 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3692 else: 3693 if isinstance(offset, exp.Expr): 3694 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3695 elif offset: 3696 suffix = f"{alias} WITH OFFSET" 3697 else: 3698 suffix = alias 3699 3700 return f"UNNEST({args}){suffix}"
3709 def window_sql(self, expression: exp.Window) -> str: 3710 this = self.sql(expression, "this") 3711 partition = self.partition_by_sql(expression) 3712 order = expression.args.get("order") 3713 order = self.order_sql(order, flat=True) if order else "" 3714 spec = self.sql(expression, "spec") 3715 alias = self.sql(expression, "alias") 3716 over = self.sql(expression, "over") or "OVER" 3717 3718 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3719 3720 first = expression.args.get("first") 3721 if first is None: 3722 first = "" 3723 else: 3724 first = "FIRST" if first else "LAST" 3725 3726 if not partition and not order and not spec and alias: 3727 # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required. 3728 if expression.arg_key == "windows": 3729 return f"{this} ({alias})" 3730 return f"{this} {alias}" 3731 3732 args = self.format_args( 3733 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3734 ) 3735 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
def
partitionbyexcluding_sql(self, expression: sqlglot.expressions.query.PartitionByExcluding) -> str:
3744 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3745 kind = self.sql(expression, "kind") 3746 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3747 end = ( 3748 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3749 or "CURRENT ROW" 3750 ) 3751 3752 window_spec = f"{kind} BETWEEN {start} AND {end}" 3753 3754 exclude = self.sql(expression, "exclude") 3755 if exclude: 3756 if self.SUPPORTS_WINDOW_EXCLUDE: 3757 window_spec += f" EXCLUDE {exclude}" 3758 else: 3759 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3760 3761 return window_spec
3768 def between_sql(self, expression: exp.Between) -> str: 3769 this = self.sql(expression, "this") 3770 low = self.sql(expression, "low") 3771 high = self.sql(expression, "high") 3772 symmetric = expression.args.get("symmetric") 3773 3774 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3775 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3776 3777 flag = ( 3778 " SYMMETRIC" 3779 if symmetric 3780 else " ASYMMETRIC" 3781 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3782 else "" # silently drop ASYMMETRIC – semantics identical 3783 ) 3784 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]:
3786 def bracket_offset_expressions( 3787 self, expression: exp.Bracket, index_offset: int | None = None 3788 ) -> list[exp.Expr]: 3789 if expression.args.get("json_access"): 3790 return expression.expressions 3791 3792 return apply_index_offset( 3793 expression.this, 3794 expression.expressions, 3795 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3796 dialect=self.dialect, 3797 )
3810 def any_sql(self, expression: exp.Any) -> str: 3811 this = self.sql(expression, "this") 3812 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3813 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3814 this = self.wrap(this) 3815 return f"ANY{this}" 3816 return f"ANY {this}"
3821 def case_sql(self, expression: exp.Case) -> str: 3822 this = self.sql(expression, "this") 3823 statements = [f"CASE {this}" if this else "CASE"] 3824 3825 for e in expression.args["ifs"]: 3826 statements.append(f"WHEN {self.sql(e, 'this')}") 3827 statements.append(f"THEN {self.sql(e, 'true')}") 3828 3829 default = self.sql(expression, "default") 3830 3831 if default: 3832 statements.append(f"ELSE {default}") 3833 3834 statements.append("END") 3835 3836 if self.pretty and self.too_wide(statements): 3837 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3838 3839 return " ".join(statements)
3851 def extract_sql(self, expression: exp.Extract) -> str: 3852 import sqlglot.dialects.dialect 3853 3854 this = ( 3855 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3856 if self.NORMALIZE_EXTRACT_DATE_PARTS 3857 else expression.this 3858 ) 3859 if self.EXTRACT_ALLOWS_QUOTES: 3860 this_sql = self.sql(this) 3861 elif isinstance(this, exp.WeekStart): 3862 this_sql = self.weekstart_name(this) 3863 else: 3864 this_sql = this.name 3865 expression_sql = self.sql(expression, "expression") 3866 3867 return f"EXTRACT({this_sql} FROM {expression_sql})"
3869 def trim_sql(self, expression: exp.Trim) -> str: 3870 trim_type = self.sql(expression, "position") 3871 3872 if trim_type == "LEADING": 3873 func_name = "LTRIM" 3874 elif trim_type == "TRAILING": 3875 func_name = "RTRIM" 3876 else: 3877 func_name = "TRIM" 3878 3879 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3881 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3882 args = expression.expressions 3883 if isinstance(expression, exp.ConcatWs): 3884 args = args[1:] # Skip the delimiter 3885 3886 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3887 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3888 3889 concat_coalesce = ( 3890 self.dialect.CONCAT_WS_COALESCE 3891 if isinstance(expression, exp.ConcatWs) 3892 else self.dialect.CONCAT_COALESCE 3893 ) 3894 3895 if not concat_coalesce and expression.args.get("coalesce"): 3896 3897 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3898 if not e.type: 3899 import sqlglot.optimizer.annotate_types 3900 3901 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3902 3903 if e.is_string or e.is_type(exp.DType.ARRAY): 3904 return e 3905 3906 return exp.func("coalesce", e, exp.Literal.string("")) 3907 3908 args = [_wrap_with_coalesce(e) for e in args] 3909 3910 return args
3912 def concat_sql(self, expression: exp.Concat) -> str: 3913 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3914 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3915 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3916 # instead of coalescing them to empty string. 3917 import sqlglot.dialects.dialect 3918 3919 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3920 3921 expressions = self.convert_concat_args(expression) 3922 3923 # Some dialects don't allow a single-argument CONCAT call 3924 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3925 return self.sql(expressions[0]) 3926 3927 return self.func("CONCAT", *expressions)
3929 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3930 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3931 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3932 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3933 all_args = expression.expressions 3934 expression.set("coalesce", True) 3935 return self.sql( 3936 exp.case() 3937 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3938 .else_(expression) 3939 ) 3940 3941 return self.func( 3942 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3943 )
3949 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3950 this = self.sql(expression, "this") 3951 this = f" {this}" if this else "" 3952 expressions = self.expressions(expression, flat=True) 3953 expressions = f" ({expressions})" if expressions else "" 3954 reference = self.sql(expression, "reference") 3955 reference = f" {reference}" if reference else "" 3956 delete = self.sql(expression, "delete") 3957 delete = f" ON DELETE {delete}" if delete else "" 3958 update = self.sql(expression, "update") 3959 update = f" ON UPDATE {update}" if update else "" 3960 options = self.expressions(expression, key="options", flat=True, sep=" ") 3961 options = f" {options}" if options else "" 3962 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3964 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3965 this = self.sql(expression, "this") 3966 this = f" {this}" if this else "" 3967 expressions = self.expressions(expression, flat=True) 3968 include = self.sql(expression, "include") 3969 options = self.expressions(expression, key="options", flat=True, sep=" ") 3970 options = f" {options}" if options else "" 3971 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3980 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3981 if self.MATCH_AGAINST_TABLE_PREFIX: 3982 expressions = [] 3983 for expr in expression.expressions: 3984 if isinstance(expr, exp.Table): 3985 expressions.append(f"TABLE {self.sql(expr)}") 3986 else: 3987 expressions.append(expr) 3988 else: 3989 expressions = expression.expressions 3990 3991 modifier = expression.args.get("modifier") 3992 modifier = f" {modifier}" if modifier else "" 3993 return ( 3994 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3995 )
4009 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 4010 if isinstance(expression, exp.JSONPathPart): 4011 transform = self.TRANSFORMS.get(expression.__class__) 4012 if not callable(transform): 4013 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 4014 return "" 4015 4016 return transform(self, expression) 4017 4018 if isinstance(expression, int): 4019 return str(expression) 4020 4021 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 4022 escaped = expression.replace("'", "\\'") 4023 escaped = f"'{escaped}'" 4024 else: 4025 escaped = expression.replace('"', '\\"') 4026 escaped = f'"{escaped}"' 4027 4028 return escaped
4033 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 4034 # Output the Teradata column FORMAT override. 4035 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 4036 this = self.sql(expression, "this") 4037 fmt = self.sql(expression, "format") 4038 return f"{this} (FORMAT {fmt})"
4066 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 4067 null_handling = expression.args.get("null_handling") 4068 null_handling = f" {null_handling}" if null_handling else "" 4069 return_type = self.sql(expression, "return_type") 4070 return_type = f" RETURNING {return_type}" if return_type else "" 4071 strict = " STRICT" if expression.args.get("strict") else "" 4072 return self.func( 4073 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 4074 )
4076 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 4077 this = self.sql(expression, "this") 4078 order = self.sql(expression, "order") 4079 null_handling = expression.args.get("null_handling") 4080 null_handling = f" {null_handling}" if null_handling else "" 4081 return_type = self.sql(expression, "return_type") 4082 return_type = f" RETURNING {return_type}" if return_type else "" 4083 strict = " STRICT" if expression.args.get("strict") else "" 4084 return self.func( 4085 "JSON_ARRAYAGG", 4086 this, 4087 suffix=f"{order}{null_handling}{return_type}{strict})", 4088 )
4090 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 4091 path = self.sql(expression, "path") 4092 path = f" PATH {path}" if path else "" 4093 nested_schema = self.sql(expression, "nested_schema") 4094 4095 if nested_schema: 4096 return f"NESTED{path} {nested_schema}" 4097 4098 this = self.sql(expression, "this") 4099 kind = self.sql(expression, "kind") 4100 kind = f" {kind}" if kind else "" 4101 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4102 4103 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4104 return f"{this}{kind}{format_json}{path}{ordinality}"
4109 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4110 this = self.sql(expression, "this") 4111 path = self.sql(expression, "path") 4112 path = f", {path}" if path else "" 4113 error_handling = expression.args.get("error_handling") 4114 error_handling = f" {error_handling}" if error_handling else "" 4115 empty_handling = expression.args.get("empty_handling") 4116 empty_handling = f" {empty_handling}" if empty_handling else "" 4117 schema = self.sql(expression, "schema") 4118 return self.func( 4119 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4120 )
4122 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4123 this = self.sql(expression, "this") 4124 kind = self.sql(expression, "kind") 4125 path = self.sql(expression, "path") 4126 path = f" {path}" if path else "" 4127 as_json = " AS JSON" if expression.args.get("as_json") else "" 4128 return f"{this} {kind}{path}{as_json}"
4130 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4131 this = self.sql(expression, "this") 4132 path = self.sql(expression, "path") 4133 path = f", {path}" if path else "" 4134 expressions = self.expressions(expression) 4135 with_ = ( 4136 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4137 if expressions 4138 else "" 4139 ) 4140 return f"OPENJSON({this}{path}){with_}"
4142 def in_sql(self, expression: exp.In) -> str: 4143 query = expression.args.get("query") 4144 unnest = expression.args.get("unnest") 4145 field = expression.args.get("field") 4146 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4147 4148 if query: 4149 in_sql = self.sql(query) 4150 elif unnest: 4151 in_sql = self.in_unnest_op(unnest) 4152 elif field: 4153 in_sql = self.sql(field) 4154 else: 4155 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4156 4157 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4162 def interval_sql(self, expression: exp.Interval) -> str: 4163 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4164 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4165 exp.AutoRefreshProperty, 4166 ) 4167 interval_keyword = "INTERVAL" if include_keyword else "" 4168 unit_expression = expression.args.get("unit") 4169 unit = self.sql(unit_expression) if unit_expression else "" 4170 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4171 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4172 unit = f" {unit}" if unit else "" 4173 4174 if self.SINGLE_STRING_INTERVAL: 4175 this = expression.this.name if expression.this else "" 4176 if this: 4177 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4178 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4179 return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}" 4180 return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}" 4181 return f"{interval_keyword}{unit}" 4182 4183 this = self.sql(expression, "this") 4184 if this: 4185 if not include_keyword and expression.this.is_string: 4186 this = expression.this.name 4187 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4188 this = f"({this})" 4189 if include_keyword: 4190 this = f" {this}" 4191 4192 return f"{interval_keyword}{this}{unit}"
4197 def reference_sql(self, expression: exp.Reference) -> str: 4198 this = self.sql(expression, "this") 4199 expressions = self.expressions(expression, flat=True) 4200 expressions = f"({expressions})" if expressions else "" 4201 options = self.expressions(expression, key="options", flat=True, sep=" ") 4202 options = f" {options}" if options else "" 4203 return f"REFERENCES {this}{expressions}{options}"
4205 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4206 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4207 parent = expression.parent 4208 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4209 4210 return self.func( 4211 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4212 )
4232 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4233 alias = expression.args["alias"] 4234 4235 parent = expression.parent 4236 pivot = parent and parent.parent 4237 4238 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4239 identifier_alias = isinstance(alias, exp.Identifier) 4240 literal_alias = isinstance(alias, exp.Literal) 4241 4242 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4243 alias.replace(exp.Literal.string(alias.output_name)) 4244 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4245 alias.replace(exp.to_identifier(alias.output_name)) 4246 4247 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:
4288 def connector_sql( 4289 self, 4290 expression: exp.Connector, 4291 op: str, 4292 stack: list[str | exp.Expr] | None = None, 4293 ) -> str: 4294 if stack is not None: 4295 stack.append(expression.right) 4296 if expression.comments and self.comments: 4297 op = self.maybe_comment(op, comments=expression.comments) 4298 4299 stack.extend((op, expression.left)) 4300 return op 4301 4302 stack = [expression] 4303 sqls: list[str] = [] 4304 ops = set() 4305 4306 while stack: 4307 node = stack.pop() 4308 if isinstance(node, exp.Connector): 4309 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4310 else: 4311 sql = self.sql(node) 4312 if sqls and sqls[-1] in ops: 4313 sqls[-1] += f" {sql}" 4314 else: 4315 sqls.append(sql) 4316 4317 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4318 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4338 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4339 format_sql = self.sql(expression, "format") 4340 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4341 to_sql = self.sql(expression, "to") 4342 to_sql = f" {to_sql}" if to_sql else "" 4343 action = self.sql(expression, "action") 4344 action = f" {action}" if action else "" 4345 default = self.sql(expression, "default") 4346 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4347 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4377 def comment_sql(self, expression: exp.Comment) -> str: 4378 this = self.sql(expression, "this") 4379 kind = expression.args["kind"] 4380 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4381 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4382 expression_sql = self.sql(expression, "expression") 4383 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4385 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4386 this = self.sql(expression, "this") 4387 delete = " DELETE" if expression.args.get("delete") else "" 4388 recompress = self.sql(expression, "recompress") 4389 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4390 to_disk = self.sql(expression, "to_disk") 4391 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4392 to_volume = self.sql(expression, "to_volume") 4393 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4394 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4396 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4397 where = self.sql(expression, "where") 4398 group = self.sql(expression, "group") 4399 aggregates = self.expressions(expression, key="aggregates") 4400 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4401 4402 if not (where or group or aggregates) and len(expression.expressions) == 1: 4403 return f"TTL {self.expressions(expression, flat=True)}" 4404 4405 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4424 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4425 this = self.sql(expression, "this") 4426 4427 exists = "" 4428 if expression.args.get("exists"): 4429 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4430 exists = " IF EXISTS" 4431 else: 4432 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4433 4434 dtype = self.sql(expression, "dtype") 4435 if dtype: 4436 collate = self.sql(expression, "collate") 4437 collate = f" COLLATE {collate}" if collate else "" 4438 using = self.sql(expression, "using") 4439 using = f" USING {using}" if using else "" 4440 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4441 null_constraint = self._alter_column_null_constraint_sql(expression) 4442 4443 return ( 4444 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4445 f"{collate}{using}{null_constraint}" 4446 ) 4447 4448 default = self.sql(expression, "default") 4449 if default: 4450 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4451 4452 comment = self.sql(expression, "comment") 4453 if comment: 4454 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4455 4456 visible = expression.args.get("visible") 4457 if visible: 4458 return f"ALTER COLUMN{exists} {this} SET {visible}" 4459 4460 allow_null = expression.args.get("allow_null") 4461 drop = expression.args.get("drop") 4462 4463 if not drop and not allow_null: 4464 self.unsupported("Unsupported ALTER COLUMN syntax") 4465 4466 if allow_null is not None: 4467 keyword = "DROP" if drop else "SET" 4468 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4469 4470 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4483 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4484 this = self.sql(expression, "this") 4485 rename_from = self.sql(expression, "rename_from") 4486 if rename_from: 4487 if not self.SUPPORTS_CHANGE_COLUMN: 4488 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4489 return f"CHANGE COLUMN {rename_from} {this}" 4490 if not self.SUPPORTS_MODIFY_COLUMN: 4491 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4492 return f"MODIFY COLUMN {this}"
4508 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4509 compound = " COMPOUND" if expression.args.get("compound") else "" 4510 this = self.sql(expression, "this") 4511 expressions = self.expressions(expression, flat=True) 4512 expressions = f"({expressions})" if expressions else "" 4513 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4515 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4516 if not self.RENAME_TABLE_WITH_DB: 4517 # Remove db from tables 4518 expression = expression.transform( 4519 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4520 ).assert_is(exp.AlterRename) 4521 this = self.sql(expression, "this") 4522 to_kw = " TO" if include_to else "" 4523 return f"RENAME{to_kw} {this}"
4538 def alter_sql(self, expression: exp.Alter) -> str: 4539 actions = expression.actions 4540 4541 if ( 4542 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4543 and actions 4544 and isinstance(actions[0], exp.ColumnDef) 4545 ): 4546 actions_sql = self.expressions(expression, key="actions", flat=True) 4547 actions_sql = f"ADD {actions_sql}" 4548 else: 4549 actions_list = [] 4550 for action in actions: 4551 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4552 action_sql = self.add_column_sql(action) 4553 else: 4554 action_sql = self.sql(action) 4555 if isinstance(action, exp.Query): 4556 action_sql = f"AS {action_sql}" 4557 4558 actions_list.append(action_sql) 4559 4560 actions_sql = self.format_args(*actions_list).lstrip("\n") 4561 4562 iceberg = ( 4563 "ICEBERG " 4564 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4565 else "" 4566 ) 4567 exists = " IF EXISTS" if expression.args.get("exists") else "" 4568 on_cluster = self.sql(expression, "cluster") 4569 on_cluster = f" {on_cluster}" if on_cluster else "" 4570 only = " ONLY" if expression.args.get("only") else "" 4571 options = expression.args.get("options") or [] 4572 options_sql = self.expressions( 4573 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4574 ) 4575 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4576 partitions_sql = self.expressions( 4577 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4578 sep=" ", 4579 flat=True, 4580 ) 4581 if partitions_sql and (actions_sql or options_sql): 4582 partitions_sql = f" {partitions_sql}" 4583 kind = self.sql(expression, "kind") 4584 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4585 check = " WITH CHECK" if expression.args.get("check") else "" 4586 cascade = ( 4587 " CASCADE" 4588 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4589 else "" 4590 ) 4591 this = self.sql(expression, "this") 4592 this = f" {this}" if this else "" 4593 4594 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4601 def add_column_sql(self, expression: exp.Expr) -> str: 4602 column_text = "" 4603 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4604 if isinstance(expression, exp.ColumnDef): 4605 column_text = " COLUMN" 4606 elif isinstance(expression, exp.Schema): 4607 column_text = " COLUMNS" 4608 4609 if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED: 4610 sql = self.expressions(expression, flat=True) 4611 else: 4612 sql = self.sql(expression) 4613 4614 return f"ADD{column_text} {sql}"
4630 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4631 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4632 location = self.sql(expression, "location") 4633 location = f" {location}" if location else "" 4634 return f"ADD {exists}{self.sql(expression.this)}{location}"
4636 def distinct_sql(self, expression: exp.Distinct) -> str: 4637 this = self.expressions(expression, flat=True) 4638 4639 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4640 case = exp.case() 4641 for arg in expression.expressions: 4642 case = case.when(arg.is_(exp.null()), exp.null()) 4643 this = self.sql(case.else_(f"({this})")) 4644 4645 this = f" {this}" if this else "" 4646 4647 on = self.sql(expression, "on") 4648 on = f" ON {on}" if on else "" 4649 return f"DISTINCT{this}{on}"
4678 def div_sql(self, expression: exp.Div) -> str: 4679 l, r = expression.left, expression.right 4680 4681 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4682 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4683 4684 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4685 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4686 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4687 4688 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4689 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4690 # Native integer division depends on the target's operand types, which may differ 4691 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4692 # integer literals or casts 4693 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4694 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4695 ): 4696 return self.sql(exp.IntDiv(this=l, expression=r)) 4697 return self._truncated_div_sql(l, r) 4698 4699 return self.binary(expression, "/")
4724 def escape_sql(self, expression: exp.Escape) -> str: 4725 this = expression.this 4726 if ( 4727 isinstance(this, (exp.Like, exp.ILike)) 4728 and isinstance(this.expression, (exp.All, exp.Any)) 4729 and not self.SUPPORTS_LIKE_QUANTIFIERS 4730 ): 4731 return self._like_sql(this, escape=expression) 4732 return self.binary(expression, "ESCAPE")
4743 def is_sql(self, expression: exp.Is) -> str: 4744 negate = expression.args.get("negate") 4745 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4746 positive = bool(expression.expression.this) != bool(negate) 4747 return self.sql(expression.this if positive else exp.not_(expression.this)) 4748 return self.binary(expression, "IS NOT" if negate else "IS")
4820 def mod_sql(self, expression: exp.Mod) -> str: 4821 this = self.sql(expression, "this") 4822 expr = self.sql(expression, "expression") 4823 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4824 4825 parent = expression.parent 4826 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4827 return f"({sql})" 4828 4829 return sql
4859 def log_sql(self, expression: exp.Log) -> str: 4860 this = expression.this 4861 expr = expression.expression 4862 4863 if self.dialect.LOG_BASE_FIRST is False: 4864 this, expr = expr, this 4865 elif self.dialect.LOG_BASE_FIRST is None and expr: 4866 if this.name in ("2", "10"): 4867 return self.func(f"LOG{this.name}", expr) 4868 4869 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4870 4871 return self.func("LOG", this, expr)
4880 def binary(self, expression: exp.Binary, op: str) -> str: 4881 sqls: list[str] = [] 4882 stack: list[None | str | exp.Expr] = [expression] 4883 binary_type = type(expression) 4884 is_is = binary_type is exp.Is and op in ("IS", "IS NOT") 4885 negate = expression.args.get("negate") 4886 4887 while stack: 4888 node = stack.pop() 4889 4890 if type(node) is binary_type and ( 4891 node is expression 4892 or (is_is and not isinstance(node.expression, exp.Boolean)) 4893 or node.args.get("negate") == negate 4894 ): 4895 op_func = node.args.get("operator") 4896 if op_func: 4897 op = f"OPERATOR({self.sql(op_func)})" 4898 elif is_is: 4899 op = "IS NOT" if node.args.get("negate") else "IS" 4900 4901 stack.append(node.args.get("expression")) 4902 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4903 stack.append(node.args.get("this")) 4904 else: 4905 sqls.append(self.sql(node)) 4906 4907 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4916 def function_fallback_sql(self, expression: exp.Func) -> str: 4917 args = [] 4918 4919 for key in expression.arg_types: 4920 arg_value = expression.args.get(key) 4921 4922 if isinstance(arg_value, list): 4923 for value in arg_value: 4924 args.append(value) 4925 elif arg_value is not None: 4926 args.append(arg_value) 4927 4928 return self.func(self.function_name(expression), *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:
4945 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4946 arg_sqls = tuple( 4947 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4948 ) 4949 if self.pretty and self.too_wide(arg_sqls): 4950 return self.indent( 4951 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4952 ) 4953 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:
4958 def format_time( 4959 self, 4960 expression: exp.Expr, 4961 inverse_time_mapping: dict[str, str] | None = None, 4962 inverse_time_trie: dict | None = None, 4963 ) -> str | None: 4964 return format_time( 4965 self.sql(expression, "format"), 4966 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4967 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4968 )
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:
4970 def expressions( 4971 self, 4972 expression: exp.Expr | None = None, 4973 key: str | None = None, 4974 sqls: t.Collection[str | exp.Expr] | None = None, 4975 flat: bool = False, 4976 indent: bool = True, 4977 skip_first: bool = False, 4978 skip_last: bool = False, 4979 sep: str = ", ", 4980 prefix: str = "", 4981 dynamic: bool = False, 4982 new_line: bool = False, 4983 ) -> str: 4984 expressions = expression.args.get(key or "expressions") if expression else sqls 4985 4986 if not expressions: 4987 return "" 4988 4989 if flat: 4990 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4991 4992 num_sqls = len(expressions) 4993 result_sqls = [] 4994 4995 for i, e in enumerate(expressions): 4996 sql = self.sql(e, comment=False) 4997 if not sql: 4998 continue 4999 5000 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 5001 5002 if self.pretty: 5003 if self.leading_comma: 5004 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 5005 else: 5006 result_sqls.append( 5007 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 5008 ) 5009 else: 5010 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 5011 5012 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 5013 if new_line: 5014 result_sqls.insert(0, "") 5015 result_sqls.append("") 5016 result_sql = "\n".join(s.rstrip() for s in result_sqls) 5017 else: 5018 result_sql = "".join(result_sqls) 5019 5020 return ( 5021 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 5022 if indent 5023 else result_sql 5024 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
5026 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 5027 flat = flat or isinstance(expression.parent, exp.Properties) 5028 expressions_sql = self.expressions(expression, flat=flat) 5029 if flat: 5030 return f"{op} {expressions_sql}" 5031 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
5033 def naked_property(self, expression: exp.Property) -> str: 5034 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 5035 if not property_name: 5036 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 5037 return f"{property_name} {self.sql(expression, 'this')}"
5045 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 5046 this = self.sql(expression, "this") 5047 expressions = self.no_identify(self.expressions, expression) 5048 expressions = ( 5049 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 5050 ) 5051 return f"{this}{expressions}" if expressions.strip() != "" else this
5070 def when_sql(self, expression: exp.When) -> str: 5071 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 5072 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 5073 condition = self.sql(expression, "condition") 5074 condition = f" AND {condition}" if condition else "" 5075 5076 then_expression = expression.args.get("then") 5077 if isinstance(then_expression, exp.Insert): 5078 this = ( 5079 "ALL BY NAME" 5080 if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star) 5081 else self.sql(then_expression, "this") 5082 ) 5083 this = f"INSERT {this}" if this else "INSERT" 5084 then = self.sql(then_expression, "expression") 5085 then = f"{this} VALUES {then}" if then else this 5086 elif isinstance(then_expression, exp.Update): 5087 if isinstance(then_expression.args.get("expressions"), exp.Star): 5088 then = ( 5089 "UPDATE ALL BY NAME" 5090 if self.MERGE_ALL_BY_NAME 5091 else f"UPDATE {self.sql(then_expression, 'expressions')}" 5092 ) 5093 elif ( 5094 self.MERGE_ALL_BY_NAME 5095 and len(then_expression.expressions) == 1 5096 and isinstance(then_expression.expressions[0], exp.Star) 5097 ): 5098 then = "UPDATE ALL BY NAME" 5099 else: 5100 expressions_sql = self.expressions(then_expression) 5101 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 5102 else: 5103 then = self.sql(then_expression) 5104 5105 if isinstance(then_expression, (exp.Insert, exp.Update)): 5106 where = self.sql(then_expression, "where") 5107 if where and not self.SUPPORTS_MERGE_WHERE: 5108 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 5109 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 5110 where = "" 5111 then = f"{then}{where}" 5112 return f"WHEN {matched}{source}{condition} THEN {then}"
5117 def merge_sql(self, expression: exp.Merge) -> str: 5118 table = expression.this 5119 table_alias = "" 5120 5121 hints = table.args.get("hints") 5122 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 5123 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 5124 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 5125 5126 this = self.sql(table) 5127 using = f"USING {self.sql(expression, 'using')}" 5128 whens = self.sql(expression, "whens") 5129 5130 on = self.sql(expression, "on") 5131 on = f"ON {on}" if on else "" 5132 5133 if not on: 5134 on = self.expressions(expression, key="using_cond") 5135 on = f"USING ({on})" if on else "" 5136 5137 returning = self.sql(expression, "returning") 5138 if returning: 5139 whens = f"{whens}{returning}" 5140 5141 sep = self.sep() 5142 5143 return self.prepend_ctes( 5144 expression, 5145 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5146 )
@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:
5152 @unsupported_args("default") 5153 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5154 if not self.SUPPORTS_TO_NUMBER: 5155 self.unsupported("Unsupported TO_NUMBER function") 5156 return self._tonumber_cast_sql(expression) 5157 5158 fmt = expression.args.get("format") 5159 if not fmt: 5160 self.unsupported("Conversion format is required for TO_NUMBER") 5161 return self._tonumber_cast_sql(expression) 5162 5163 return self.func("TO_NUMBER", expression.this, fmt)
5170 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5171 this = self.sql(expression, "this") 5172 kind = self.sql(expression, "kind") 5173 settings_sql = self.expressions(expression, key="settings", sep=" ") 5174 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5175 return f"{this}({kind}{args})"
def
aggregatekeyproperty_sql( self, expression: sqlglot.expressions.properties.AggregateKeyProperty) -> str:
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:
5199 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5200 expressions = self.expressions(expression, flat=True) 5201 expressions = f" {self.wrap(expressions)}" if expressions else "" 5202 buckets = self.sql(expression, "buckets") 5203 kind = self.sql(expression, "kind") 5204 buckets = f" BUCKETS {buckets}" if buckets else "" 5205 order = self.sql(expression, "order") 5206 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
5211 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5212 expressions = self.expressions(expression, key="expressions", flat=True) 5213 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5214 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5215 buckets = self.sql(expression, "buckets") 5216 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5218 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5219 this = self.sql(expression, "this") 5220 having = self.sql(expression, "having") 5221 5222 if having: 5223 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5224 5225 return self.func("ANY_VALUE", this)
5227 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5228 transform = self.func("TRANSFORM", *expression.expressions) 5229 row_format_before = self.sql(expression, "row_format_before") 5230 row_format_before = f" {row_format_before}" if row_format_before else "" 5231 record_writer = self.sql(expression, "record_writer") 5232 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5233 using = f" USING {self.sql(expression, 'command_script')}" 5234 schema = self.sql(expression, "schema") 5235 schema = f" AS {schema}" if schema else "" 5236 row_format_after = self.sql(expression, "row_format_after") 5237 row_format_after = f" {row_format_after}" if row_format_after else "" 5238 record_reader = self.sql(expression, "record_reader") 5239 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5240 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:
5242 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5243 key_block_size = self.sql(expression, "key_block_size") 5244 if key_block_size: 5245 return f"KEY_BLOCK_SIZE = {key_block_size}" 5246 5247 using = self.sql(expression, "using") 5248 if using: 5249 return f"USING {using}" 5250 5251 parser = self.sql(expression, "parser") 5252 if parser: 5253 return f"WITH PARSER {parser}" 5254 5255 comment = self.sql(expression, "comment") 5256 if comment: 5257 return f"COMMENT {comment}" 5258 5259 visible = expression.args.get("visible") 5260 if visible is not None: 5261 return "VISIBLE" if visible else "INVISIBLE" 5262 5263 engine_attr = self.sql(expression, "engine_attr") 5264 if engine_attr: 5265 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5266 5267 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5268 if secondary_engine_attr: 5269 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5270 5271 self.unsupported("Unsupported index constraint option.") 5272 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5278 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5279 kind = self.sql(expression, "kind") 5280 kind = f"{kind} INDEX" if kind else "INDEX" 5281 this = self.sql(expression, "this") 5282 this = f" {this}" if this else "" 5283 index_type = self.sql(expression, "index_type") 5284 index_type = f" USING {index_type}" if index_type else "" 5285 expressions = self.expressions(expression, flat=True) 5286 expressions = f" ({expressions})" if expressions else "" 5287 options = self.expressions(expression, key="options", sep=" ") 5288 options = f" {options}" if options else "" 5289 return f"{kind}{this}{index_type}{expressions}{options}"
5291 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5292 if self.NVL2_SUPPORTED: 5293 return self.function_fallback_sql(expression) 5294 5295 case = exp.Case().when( 5296 expression.this.is_(exp.null()).not_(copy=False), 5297 expression.args["true"], 5298 copy=False, 5299 ) 5300 else_cond = expression.args.get("false") 5301 if else_cond: 5302 case.else_(else_cond, copy=False) 5303 5304 return self.sql(case)
5312 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5313 this = self.sql(expression, "this") 5314 expr = self.sql(expression, "expression") 5315 position = self.sql(expression, "position") 5316 position = f", {position}" if position else "" 5317 iterator = self.sql(expression, "iterator") 5318 condition = self.sql(expression, "condition") 5319 condition = f" IF {condition}" if condition else "" 5320 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5370 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5371 this_sql = self.sql(expression, "this") 5372 if isinstance(expression.this, exp.Table): 5373 this_sql = f"TABLE {this_sql}" 5374 5375 return self.func( 5376 "FORECAST", 5377 this_sql, 5378 expression.args.get("data_col"), 5379 expression.args.get("timestamp_col"), 5380 expression.args.get("model"), 5381 expression.args.get("id_cols"), 5382 expression.args.get("horizon"), 5383 expression.args.get("forecast_end_timestamp"), 5384 expression.args.get("confidence_level"), 5385 expression.args.get("output_historical_time_series"), 5386 expression.args.get("context_window"), 5387 )
5389 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5390 this_sql = self.sql(expression, "this") 5391 if isinstance(expression.this, exp.Table): 5392 this_sql = f"TABLE {this_sql}" 5393 5394 return self.func( 5395 "FEATURES_AT_TIME", 5396 this_sql, 5397 expression.args.get("time"), 5398 expression.args.get("num_rows"), 5399 expression.args.get("ignore_feature_nulls"), 5400 )
5402 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5403 this_sql = self.sql(expression, "this") 5404 if isinstance(expression.this, exp.Table): 5405 this_sql = f"TABLE {this_sql}" 5406 5407 query_table = self.sql(expression, "query_table") 5408 if isinstance(expression.args["query_table"], exp.Table): 5409 query_table = f"TABLE {query_table}" 5410 5411 return self.func( 5412 "VECTOR_SEARCH", 5413 this_sql, 5414 expression.args.get("column_to_search"), 5415 query_table, 5416 expression.args.get("query_column_to_search"), 5417 expression.args.get("top_k"), 5418 expression.args.get("distance_type"), 5419 expression.args.get("options"), 5420 )
@unsupported_args('partition_start', 'partition_end', 'force', 'mode')
def
refresh_sql(self, expression: sqlglot.expressions.core.Refresh) -> str:
5427 @unsupported_args("partition_start", "partition_end", "force", "mode") 5428 def refresh_sql(self, expression: exp.Refresh) -> str: 5429 this = self.sql(expression, "this") 5430 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5431 return f"REFRESH {kind}{this}"
5433 def toarray_sql(self, expression: exp.ToArray) -> str: 5434 arg = expression.this 5435 if not arg.type: 5436 import sqlglot.optimizer.annotate_types 5437 5438 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5439 5440 if arg.is_type(exp.DType.ARRAY): 5441 return self.sql(arg) 5442 5443 cond_for_null = arg.is_(exp.null()) 5444 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5446 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5447 this = expression.this 5448 time_format = self.format_time(expression) 5449 5450 if time_format: 5451 return self.sql( 5452 exp.cast( 5453 exp.StrToTime(this=this, format=expression.args["format"]), 5454 exp.DType.TIME, 5455 ) 5456 ) 5457 5458 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5459 return self.sql(this) 5460 5461 return self.sql(exp.cast(this, exp.DType.TIME))
5463 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5464 this = expression.this 5465 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5466 return self.sql(this) 5467 5468 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5470 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5471 this = expression.this 5472 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5473 return self.sql(this) 5474 5475 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5477 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5478 this = expression.this 5479 time_format = self.format_time(expression) 5480 safe = expression.args.get("safe") 5481 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5482 return self.sql( 5483 exp.cast( 5484 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5485 exp.DType.DATE, 5486 ) 5487 ) 5488 5489 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5490 return self.sql(this) 5491 5492 if safe: 5493 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5494 5495 return self.sql(exp.cast(this, exp.DType.DATE))
5507 def lastday_sql(self, expression: exp.LastDay) -> str: 5508 if self.LAST_DAY_SUPPORTS_DATE_PART: 5509 return self.function_fallback_sql(expression) 5510 5511 unit = expression.args.get("unit") 5512 if unit and unit.name.upper() != "MONTH": 5513 self.unsupported("Date parts are not supported in LAST_DAY.") 5514 5515 return self.func("LAST_DAY", expression.this)
def
arrayinsert_sql( self, expression: sqlglot.expressions.array.ArrayInsert, index_offset: int = 0) -> str:
5527 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5528 this = expression.this 5529 position = expression.args["position"] 5530 offset = index_offset - (expression.args.get("offset") or 0) 5531 5532 if offset: 5533 if position.is_int: 5534 value = position.to_py() 5535 if value >= 0: 5536 position = exp.Literal.number(value + offset) 5537 elif offset < 0 and value == -1: 5538 # 1-based -1 appends, which a 0-based position can only express as the size 5539 position = exp.ArraySize(this=this.copy()) 5540 else: 5541 # Negative positions count from the end, so they shift in the opposite 5542 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5543 position = exp.Literal.number(value - offset) 5544 else: 5545 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5546 5547 return self.func("ARRAY_INSERT", this, position, expression.expression)
5549 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5550 if self.CAN_IMPLEMENT_ARRAY_ANY: 5551 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5552 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5553 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5554 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5555 5556 import sqlglot.dialects.dialect 5557 5558 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5559 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5560 self.unsupported("ARRAY_ANY is unsupported") 5561 5562 return self.function_fallback_sql(expression)
5564 def struct_sql(self, expression: exp.Struct) -> str: 5565 expression.set( 5566 "expressions", 5567 [ 5568 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5569 if isinstance(e, exp.PropertyEQ) 5570 else e 5571 for e in expression.expressions 5572 ], 5573 ) 5574 5575 return self.function_fallback_sql(expression)
5583 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5584 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5585 tables = f" {self.expressions(expression)}" 5586 5587 exists = " IF EXISTS" if expression.args.get("exists") else "" 5588 5589 on_cluster = self.sql(expression, "cluster") 5590 on_cluster = f" {on_cluster}" if on_cluster else "" 5591 5592 identity = self.sql(expression, "identity") 5593 identity = f" {identity} IDENTITY" if identity else "" 5594 5595 option = self.sql(expression, "option") 5596 option = f" {option}" if option else "" 5597 5598 partition = self.sql(expression, "partition") 5599 partition = f" {partition}" if partition else "" 5600 5601 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5605 def convert_sql(self, expression: exp.Convert) -> str: 5606 to = expression.this 5607 value = expression.expression 5608 style = expression.args.get("style") 5609 safe = expression.args.get("safe") 5610 strict = expression.args.get("strict") 5611 5612 if not to or not value: 5613 return "" 5614 5615 # Retrieve length of datatype and override to default if not specified 5616 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5617 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5618 5619 transformed: exp.Expr | None = None 5620 cast = exp.Cast if strict else exp.TryCast 5621 5622 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5623 if isinstance(style, exp.Literal) and style.is_int: 5624 import sqlglot.dialects.tsql 5625 5626 style_value = style.name 5627 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5628 if not converted_style: 5629 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5630 5631 fmt = exp.Literal.string(converted_style) 5632 5633 if to.this == exp.DType.DATE: 5634 transformed = exp.StrToDate(this=value, format=fmt) 5635 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5636 transformed = exp.StrToTime(this=value, format=fmt) 5637 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5638 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5639 elif to.this == exp.DType.TEXT: 5640 transformed = exp.TimeToStr(this=value, format=fmt) 5641 5642 if not transformed: 5643 transformed = cast(this=value, to=to, safe=safe) 5644 5645 return self.sql(transformed)
5722 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5723 option = self.sql(expression, "this") 5724 5725 if expression.expressions: 5726 upper = option.upper() 5727 5728 # Snowflake FILE_FORMAT options are separated by whitespace 5729 sep = " " if upper == "FILE_FORMAT" else ", " 5730 5731 # Databricks copy/format options do not set their list of values with EQ 5732 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5733 values = self.expressions(expression, flat=True, sep=sep) 5734 return f"{option}{op}({values})" 5735 5736 value = self.sql(expression, "expression") 5737 5738 if not value: 5739 return option 5740 5741 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5742 5743 return f"{option}{op}{value}"
5745 def credentials_sql(self, expression: exp.Credentials) -> str: 5746 cred_expr = expression.args.get("credentials") 5747 if isinstance(cred_expr, exp.Literal): 5748 # Redshift case: CREDENTIALS <string> 5749 credentials = self.sql(expression, "credentials") 5750 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5751 else: 5752 # Snowflake case: CREDENTIALS = (...) 5753 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5754 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5755 5756 storage = self.sql(expression, "storage") 5757 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5758 5759 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5760 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5761 5762 iam_role = self.sql(expression, "iam_role") 5763 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5764 5765 region = self.sql(expression, "region") 5766 region = f" REGION {region}" if region else "" 5767 5768 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5770 def copy_sql(self, expression: exp.Copy) -> str: 5771 this = self.sql(expression, "this") 5772 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5773 5774 credentials = self.sql(expression, "credentials") 5775 credentials = self.seg(credentials) if credentials else "" 5776 files = self.expressions(expression, key="files", flat=True) 5777 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5778 5779 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5780 params = self.expressions( 5781 expression, 5782 key="params", 5783 sep=sep, 5784 new_line=True, 5785 skip_last=True, 5786 skip_first=True, 5787 indent=self.COPY_PARAMS_ARE_WRAPPED, 5788 ) 5789 5790 if params: 5791 if self.COPY_PARAMS_ARE_WRAPPED: 5792 params = f" WITH ({params})" 5793 elif not self.pretty and (files or credentials): 5794 params = f" {params}" 5795 5796 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5801 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5802 on_sql = "ON" if expression.args.get("on") else "OFF" 5803 filter_col: str | None = self.sql(expression, "filter_column") 5804 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5805 retention_period: str | None = self.sql(expression, "retention_period") 5806 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5807 5808 if filter_col or retention_period: 5809 on_sql = self.func("ON", filter_col, retention_period) 5810 5811 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5813 def maskingpolicycolumnconstraint_sql( 5814 self, expression: exp.MaskingPolicyColumnConstraint 5815 ) -> str: 5816 this = self.sql(expression, "this") 5817 expressions = self.expressions(expression, flat=True) 5818 expressions = f" USING ({expressions})" if expressions else "" 5819 return f"MASKING POLICY {this}{expressions}"
5829 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5830 this = self.sql(expression, "this") 5831 expr = expression.expression 5832 5833 if isinstance(expr, exp.Func): 5834 # T-SQL's CLR functions are case sensitive 5835 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5836 else: 5837 expr = self.sql(expression, "expression") 5838 5839 return self.scope_resolution(expr, this)
5847 def rand_sql(self, expression: exp.Rand) -> str: 5848 lower = self.sql(expression, "lower") 5849 upper = self.sql(expression, "upper") 5850 5851 if lower and upper: 5852 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5853 return self.func("RAND", expression.this)
5855 def changes_sql(self, expression: exp.Changes) -> str: 5856 information = self.sql(expression, "information") 5857 information = f"INFORMATION => {information}" 5858 at_before = self.sql(expression, "at_before") 5859 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5860 end = self.sql(expression, "end") 5861 end = f"{self.seg('')}{end}" if end else "" 5862 5863 return f"CHANGES ({information}){at_before}{end}"
5865 def pad_sql(self, expression: exp.Pad) -> str: 5866 prefix = "L" if expression.args.get("is_left") else "R" 5867 5868 fill_pattern = self.sql(expression, "fill_pattern") or None 5869 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5870 fill_pattern = "' '" 5871 5872 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5878 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5879 generate_series = exp.GenerateSeries(**expression.args) 5880 5881 parent = expression.parent 5882 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5883 parent = parent.parent 5884 5885 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5886 return self.sql(exp.Unnest(expressions=[generate_series])) 5887 5888 if isinstance(parent, exp.Select): 5889 self.unsupported("GenerateSeries projection unnesting is not supported.") 5890 5891 return self.sql(generate_series)
5893 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5894 if self.SUPPORTS_CONVERT_TIMEZONE: 5895 return self.function_fallback_sql(expression) 5896 5897 source_tz = expression.args.get("source_tz") 5898 target_tz = expression.args.get("target_tz") 5899 timestamp = expression.args.get("timestamp") 5900 5901 if source_tz and timestamp: 5902 timestamp = exp.AtTimeZone( 5903 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5904 ) 5905 5906 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5907 5908 return self.sql(expr)
5910 def json_sql(self, expression: exp.JSON) -> str: 5911 this = self.sql(expression, "this") 5912 this = f" {this}" if this else "" 5913 5914 _with = expression.args.get("with_") 5915 5916 if _with is None: 5917 with_sql = "" 5918 elif not _with: 5919 with_sql = " WITHOUT" 5920 else: 5921 with_sql = " WITH" 5922 5923 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5924 5925 return f"JSON{this}{with_sql}{unique_sql}"
5927 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5928 path = self.sql(expression, "path") 5929 returning = self.sql(expression, "returning") 5930 returning = f" RETURNING {returning}" if returning else "" 5931 5932 on_condition = self.sql(expression, "on_condition") 5933 on_condition = f" {on_condition}" if on_condition else "" 5934 5935 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5941 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5942 else_ = "ELSE " if expression.args.get("else_") else "" 5943 condition = self.sql(expression, "expression") 5944 condition = f"WHEN {condition} THEN " if condition else else_ 5945 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5946 return f"{condition}{insert}"
5948 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5949 kind = self.sql(expression, "kind") 5950 source = self.sql(expression, "source") 5951 5952 if kind: 5953 expressions = self.seg(self.expressions(expression, sep=" ")) 5954 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5955 else: 5956 # If kind isn't present, we have Hive's FROM-first form: 5957 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5958 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5959 sql = f"FROM {source}{self.seg(inserts)}" 5960 5961 return self.prepend_ctes(expression, sql)
5963 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5964 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5965 empty = expression.args.get("empty") 5966 empty = ( 5967 f"DEFAULT {empty} ON EMPTY" 5968 if isinstance(empty, exp.Expr) 5969 else self.sql(expression, "empty") 5970 ) 5971 5972 error = expression.args.get("error") 5973 error = ( 5974 f"DEFAULT {error} ON ERROR" 5975 if isinstance(error, exp.Expr) 5976 else self.sql(expression, "error") 5977 ) 5978 5979 if error and empty: 5980 error = ( 5981 f"{empty} {error}" 5982 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5983 else f"{error} {empty}" 5984 ) 5985 empty = "" 5986 5987 null = self.sql(expression, "null") 5988 5989 return f"{empty}{error}{null}"
5995 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5996 this = self.sql(expression, "this") 5997 path = self.sql(expression, "path") 5998 5999 passing = self.expressions(expression, "passing") 6000 passing = f" PASSING {passing}" if passing else "" 6001 6002 on_condition = self.sql(expression, "on_condition") 6003 on_condition = f" {on_condition}" if on_condition else "" 6004 6005 path = f"{path}{passing}{on_condition}" 6006 6007 return self.func("JSON_EXISTS", this, path)
6049 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 6050 array_agg = self.function_fallback_sql(expression) 6051 column_expr = expression.this 6052 if isinstance(column_expr, exp.Order): 6053 column_expr = column_expr.this 6054 6055 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
6057 def slice_sql(self, expression: exp.Slice) -> str: 6058 step = self.sql(expression, "step") 6059 # An omitted end is written as `-` before a step, since `::` is the cast operator 6060 end = self.sql(expression.expression) or ("-" if step else "") 6061 begin = self.sql(expression.this) 6062 6063 sql = f"{end}:{step}" if step else end 6064 return f"{begin}:{sql}" if sql else f"{begin}:"
6137 def overlay_sql(self, expression: exp.Overlay) -> str: 6138 this = self.sql(expression, "this") 6139 expr = self.sql(expression, "expression") 6140 from_sql = self.sql(expression, "from_") 6141 for_sql = self.sql(expression, "for_") 6142 for_sql = f" FOR {for_sql}" if for_sql else "" 6143 6144 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
6151 def string_sql(self, expression: exp.String) -> str: 6152 this = expression.this 6153 zone = expression.args.get("zone") 6154 6155 if zone: 6156 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6157 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6158 # set for source_tz to transpile the time conversion before the STRING cast 6159 this = exp.ConvertTimezone( 6160 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6161 ) 6162 6163 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
6173 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6174 filler = self.sql(expression, "this") 6175 filler = f" {filler}" if filler else "" 6176 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6177 return f"TRUNCATE{filler} {with_count}"
6179 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6180 if self.SUPPORTS_UNIX_SECONDS: 6181 return self.function_fallback_sql(expression) 6182 6183 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6184 6185 return self.sql( 6186 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6187 )
6189 def arraysort_sql(self, expression: exp.ArraySort) -> str: 6190 asc = expression.args.get("asc") 6191 nulls_first = expression.args.get("nulls_first") 6192 if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()): 6193 self.unsupported("ARRAY_SORT only sorts ascending with NULLs last") 6194 return self.func("ARRAY_SORT", expression.this, expression.expression)
6196 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6197 dim = expression.expression 6198 6199 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6200 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6201 if not (dim.is_int and dim.name == "1"): 6202 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6203 dim = None 6204 6205 # If dimension is required but not specified, default initialize it 6206 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6207 dim = exp.Literal.number(1) 6208 6209 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6211 def attach_sql(self, expression: exp.Attach) -> str: 6212 this = self.sql(expression, "this") 6213 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6214 expressions = self.expressions(expression) 6215 expressions = f" ({expressions})" if expressions else "" 6216 6217 return f"ATTACH{exists_sql} {this}{expressions}"
6219 def detach_sql(self, expression: exp.Detach) -> str: 6220 kind = self.sql(expression, "kind") 6221 kind = f" {kind}" if kind else "" 6222 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6223 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6224 exists = " IF EXISTS" if expression.args.get("exists") else "" 6225 if exists: 6226 kind = kind or " DATABASE" 6227 6228 this = self.sql(expression, "this") 6229 this = f" {this}" if this else "" 6230 cluster = self.sql(expression, "cluster") 6231 cluster = f" {cluster}" if cluster else "" 6232 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6233 sync = " SYNC" if expression.args.get("sync") else "" 6234 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
6247 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6248 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6249 encode = f"{encode} {self.sql(expression, 'this')}" 6250 6251 properties = expression.args.get("properties") 6252 if properties: 6253 encode = f"{encode} {self.properties(properties)}" 6254 6255 return encode
6257 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6258 this = self.sql(expression, "this") 6259 include = f"INCLUDE {this}" 6260 6261 column_def = self.sql(expression, "column_def") 6262 if column_def: 6263 include = f"{include} {column_def}" 6264 6265 alias = self.sql(expression, "alias") 6266 if alias: 6267 include = f"{include} AS {alias}" 6268 6269 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6282 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6283 partitions = self.expressions(expression, "partition_expressions") 6284 create = self.expressions(expression, "create_expressions") 6285 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6287 def partitionbyrangepropertydynamic_sql( 6288 self, expression: exp.PartitionByRangePropertyDynamic 6289 ) -> str: 6290 start = self.sql(expression, "start") 6291 end = self.sql(expression, "end") 6292 6293 every = expression.args["every"] 6294 if isinstance(every, exp.Interval) and every.this.is_string: 6295 every.this.replace(exp.Literal.number(every.name)) 6296 6297 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6310 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6311 kind = self.sql(expression, "kind") 6312 option = self.sql(expression, "option") 6313 option = f" {option}" if option else "" 6314 this = self.sql(expression, "this") 6315 this = f" {this}" if this else "" 6316 columns = self.expressions(expression) 6317 columns = f" {columns}" if columns else "" 6318 return f"{kind}{option} STATISTICS{this}{columns}"
6320 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6321 this = self.sql(expression, "this") 6322 columns = self.expressions(expression) 6323 inner_expression = self.sql(expression, "expression") 6324 inner_expression = f" {inner_expression}" if inner_expression else "" 6325 update_options = self.sql(expression, "update_options") 6326 update_options = f" {update_options} UPDATE" if update_options else "" 6327 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6338 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6339 kind = self.sql(expression, "kind") 6340 this = self.sql(expression, "this") 6341 this = f" {this}" if this else "" 6342 inner_expression = self.sql(expression, "expression") 6343 return f"VALIDATE {kind}{this}{inner_expression}"
6345 def analyze_sql(self, expression: exp.Analyze) -> str: 6346 options = self.expressions(expression, key="options", sep=" ") 6347 options = f" {options}" if options else "" 6348 kind = self.sql(expression, "kind") 6349 kind = f" {kind}" if kind else "" 6350 tables = self.expressions(expression, key="tables", flat=True) 6351 tables = f" {tables}" if tables else "" 6352 mode = self.sql(expression, "mode") 6353 mode = f" {mode}" if mode else "" 6354 properties = self.sql(expression, "properties") 6355 properties = f" {properties}" if properties else "" 6356 partition = self.sql(expression, "partition") 6357 partition = f" {partition}" if partition else "" 6358 inner_expression = self.sql(expression, "expression") 6359 inner_expression = f" {inner_expression}" if inner_expression else "" 6360 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6362 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6363 this = self.sql(expression, "this") 6364 namespaces = self.expressions(expression, key="namespaces") 6365 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6366 passing = self.expressions(expression, key="passing") 6367 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6368 columns = self.expressions(expression, key="columns") 6369 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6370 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6371 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6377 def export_sql(self, expression: exp.Export) -> str: 6378 this = self.sql(expression, "this") 6379 connection = self.sql(expression, "connection") 6380 connection = f"WITH CONNECTION {connection} " if connection else "" 6381 options = self.sql(expression, "options") 6382 return f"EXPORT DATA {connection}{options} AS {this}"
6388 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6389 variables = self.expressions(expression, "this") 6390 default = self.sql(expression, "default") 6391 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6392 6393 kind = self.sql(expression, "kind") 6394 if isinstance(expression.args.get("kind"), exp.Schema): 6395 kind = f"TABLE {kind}" 6396 6397 kind = f" {kind}" if kind else "" 6398 6399 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6401 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6402 kind = self.sql(expression, "kind") 6403 this = self.sql(expression, "this") 6404 set = self.sql(expression, "expression") 6405 to = self.sql(expression, "to") 6406 to = f" TO {to}" if to else "" 6407 default = self.sql(expression, "default") 6408 default = f" DEFAULT {default}" if default else "" 6409 using = self.sql(expression, "using") 6410 using = f" USING {using}" if using else "" 6411 6412 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6413 6414 return f"{kind_sql} {this} SET {set}{to}{default}{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:
6442 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6443 # Snowflake GET/PUT statements: 6444 # PUT <file> <internalStage> <properties> 6445 # GET <internalStage> <file> <properties> 6446 props = expression.args.get("properties") 6447 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6448 this = self.sql(expression, "this") 6449 target = self.sql(expression, "target") 6450 6451 if isinstance(expression, exp.Put): 6452 return f"PUT {this} {target}{props_sql}" 6453 else: 6454 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6456 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6457 this = self.sql(expression, "this") 6458 expr = self.sql(expression, "expression") 6459 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6460 return f"TRANSLATE({this} USING {expr}{with_error})"
6462 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6463 if self.SUPPORTS_DECODE_CASE: 6464 return self.func("DECODE", *expression.expressions) 6465 6466 decode_expr, *expressions = expression.expressions 6467 6468 ifs = [] 6469 for search, result in zip(expressions[::2], expressions[1::2]): 6470 if isinstance(search, exp.Literal): 6471 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6472 elif isinstance(search, exp.Null): 6473 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6474 else: 6475 if isinstance(search, exp.Binary): 6476 search = exp.paren(search) 6477 6478 cond = exp.or_( 6479 decode_expr.eq(search), 6480 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6481 copy=False, 6482 ) 6483 ifs.append(exp.If(this=cond, true=result)) 6484 6485 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6486 return self.sql(case)
6488 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6489 this = self.sql(expression, "this") 6490 this = self.seg(this, sep="") 6491 dimensions = self.expressions( 6492 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6493 ) 6494 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6495 metrics = self.expressions( 6496 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6497 ) 6498 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6499 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6500 facts = self.seg(f"FACTS {facts}") if facts else "" 6501 where = self.sql(expression, "where") 6502 where = self.seg(f"WHERE {where}") if where else "" 6503 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6504 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6506 def getextract_sql(self, expression: exp.GetExtract) -> str: 6507 this = expression.this 6508 expr = expression.expression 6509 6510 if not this.type or not expression.type: 6511 import sqlglot.optimizer.annotate_types 6512 6513 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6514 6515 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6516 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6517 6518 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6535 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6536 method = self.sql(expression, "method") 6537 kind = expression.args.get("kind") 6538 if not kind: 6539 return f"REFRESH {method}" 6540 6541 every = self.sql(expression, "every") 6542 unit = self.sql(expression, "unit") 6543 every = f" EVERY {every} {unit}" if every else "" 6544 starts = self.sql(expression, "starts") 6545 starts = f" STARTS {starts}" if starts else "" 6546 6547 return f"REFRESH {method} ON {kind}{every}{starts}"
6556 def uuid_sql(self, expression: exp.Uuid) -> str: 6557 is_string = expression.args.get("is_string", False) 6558 uuid_func_sql = self.func("UUID") 6559 6560 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6561 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6562 6563 return uuid_func_sql
6565 def initcap_sql(self, expression: exp.Initcap) -> str: 6566 delimiters = expression.expression 6567 6568 if delimiters: 6569 # do not generate delimiters arg if we are round-tripping from default delimiters 6570 if ( 6571 delimiters.is_string 6572 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6573 ): 6574 delimiters = None 6575 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6576 self.unsupported("INITCAP does not support custom delimiters") 6577 delimiters = None 6578 6579 return self.func("INITCAP", expression.this, delimiters)
6589 def weekstart_name(self, expression: exp.WeekStart) -> str: 6590 import sqlglot.dialects.dialect 6591 6592 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6593 this = expression.this.name.upper() 6594 6595 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6596 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6597 6598 if dow_from_week_start_day != dow_from_week_offset: 6599 self.unsupported( 6600 f"WEEK({this}) is not supported; falling back to the default week start day" 6601 ) 6602 6603 return "WEEK"
6605 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6606 name = self.weekstart_name(expression) 6607 6608 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6609 if isinstance(expression.parent, exp.DateTrunc): 6610 return self.sql(exp.Literal.string(name)) 6611 6612 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: