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.CredentialsProperty: lambda self, e: ( 173 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 174 ), 175 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 176 exp.SessionUser: lambda *_: "SESSION_USER", 177 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 178 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 179 exp.ApiProperty: lambda *_: "API", 180 exp.ApplicationProperty: lambda *_: "APPLICATION", 181 exp.CatalogProperty: lambda *_: "CATALOG", 182 exp.ComputeProperty: lambda *_: "COMPUTE", 183 exp.DatabaseProperty: lambda *_: "DATABASE", 184 exp.DynamicProperty: lambda *_: "DYNAMIC", 185 exp.EmptyProperty: lambda *_: "EMPTY", 186 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 187 exp.EndStatement: lambda *_: "END", 188 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 189 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 190 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 191 exp.EphemeralColumnConstraint: lambda self, e: ( 192 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 193 ), 194 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 195 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 196 exp.Except: lambda self, e: self.set_operations(e), 197 exp.ExternalProperty: lambda *_: "EXTERNAL", 198 exp.Floor: lambda self, e: self.ceil_floor(e), 199 exp.Get: lambda self, e: self.get_put_sql(e), 200 exp.GlobalProperty: lambda *_: "GLOBAL", 201 exp.HeapProperty: lambda *_: "HEAP", 202 exp.HybridProperty: lambda *_: "HYBRID", 203 exp.IcebergProperty: lambda *_: "ICEBERG", 204 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 205 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 206 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 207 exp.Intersect: lambda self, e: self.set_operations(e), 208 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 209 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 210 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 211 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 212 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 213 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 214 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 215 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 216 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 217 exp.LanguageProperty: lambda self, e: self.naked_property(e), 218 exp.LocationProperty: lambda self, e: self.naked_property(e), 219 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 220 exp.MaskingProperty: lambda *_: "MASKING", 221 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 222 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 223 exp.NetworkProperty: lambda *_: "NETWORK", 224 exp.NonClusteredColumnConstraint: lambda self, e: ( 225 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 226 ), 227 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 228 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 229 exp.OnCommitProperty: lambda _, e: ( 230 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 231 ), 232 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 233 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 234 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 235 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 236 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 237 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 238 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 239 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 240 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 241 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 242 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 243 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 244 f"PROJECTION POLICY {self.sql(e, 'this')}" 245 ), 246 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 247 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 248 exp.Put: lambda self, e: self.get_put_sql(e), 249 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 250 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 251 ), 252 exp.ReturnsProperty: lambda self, e: ( 253 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 254 ), 255 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 256 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 257 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 258 exp.SecureProperty: lambda *_: "SECURE", 259 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 260 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 261 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 262 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 263 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 264 exp.SqlReadWriteProperty: lambda _, e: e.name, 265 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 266 exp.StabilityProperty: lambda _, e: e.name, 267 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 268 exp.StreamingTableProperty: lambda *_: "STREAMING", 269 exp.StrictProperty: lambda *_: "STRICT", 270 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 271 exp.TableColumn: lambda self, e: self.sql(e.this), 272 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 273 exp.TemporaryProperty: lambda *_: "TEMPORARY", 274 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 275 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 276 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 277 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 278 exp.TransientProperty: lambda *_: "TRANSIENT", 279 exp.VirtualProperty: lambda *_: "VIRTUAL", 280 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 281 exp.Union: lambda self, e: self.set_operations(e), 282 exp.UnloggedProperty: lambda *_: "UNLOGGED", 283 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 284 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 285 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 286 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 287 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 288 exp.UtcTimestamp: lambda self, e: self.sql( 289 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 290 ), 291 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 292 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 293 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 294 exp.VolatileProperty: lambda *_: "VOLATILE", 295 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 296 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 297 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 298 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 299 exp.ForceProperty: lambda *_: "FORCE", 300 } 301 302 # Whether null ordering is supported in order by 303 # True: Full Support, None: No support, False: No support for certain cases 304 # such as window specifications, aggregate functions etc 305 NULL_ORDERING_SUPPORTED: bool | None = True 306 307 # Window functions that support NULLS FIRST/LAST 308 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 309 310 # Whether ignore nulls is inside the agg or outside. 311 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 312 IGNORE_NULLS_IN_FUNC = False 313 314 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 315 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 316 IGNORE_NULLS_BEFORE_ORDER = True 317 318 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 319 LOCKING_READS_SUPPORTED = False 320 321 # Whether the EXCEPT and INTERSECT operations can return duplicates 322 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 323 324 # Wrap derived values in parens, usually standard but spark doesn't support it 325 WRAP_DERIVED_VALUES = True 326 327 # Whether create function uses an AS before the RETURN 328 CREATE_FUNCTION_RETURN_AS = True 329 330 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 331 MATCHED_BY_SOURCE = True 332 333 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 334 SUPPORTS_MERGE_WHERE = False 335 336 # Whether the INTERVAL expression works only with values like '1 day' 337 SINGLE_STRING_INTERVAL = False 338 339 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 340 INTERVAL_ALLOWS_PLURAL_FORM = True 341 342 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 343 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 344 AUTO_REFRESH_BARE_INTERVALS = False 345 346 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 347 LIMIT_FETCH = "ALL" 348 349 # Whether limit and fetch allows expresions or just limits 350 LIMIT_ONLY_LITERALS = False 351 352 # Whether a table is allowed to be renamed with a db 353 RENAME_TABLE_WITH_DB = True 354 355 # The separator for grouping sets and rollups 356 GROUPINGS_SEP = "," 357 358 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 359 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 360 361 # The string used for creating an index on a table 362 INDEX_ON = "ON" 363 364 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 365 INOUT_SEPARATOR = " " 366 367 # Whether join hints should be generated 368 JOIN_HINTS = True 369 370 # Whether directed joins are supported 371 DIRECTED_JOINS = False 372 373 # Whether table hints should be generated 374 TABLE_HINTS = True 375 376 # Whether query hints should be generated 377 QUERY_HINTS = True 378 379 # What kind of separator to use for query hints 380 QUERY_HINT_SEP = ", " 381 382 # Whether comparing against booleans (e.g. x IS TRUE) is supported 383 IS_BOOL_ALLOWED = True 384 385 # Whether casting a fractional number to an integer rounds it instead of truncating it 386 CAST_TO_INT_ROUNDS = False 387 388 # Whether the native integer division operator returns NULL for a zero divisor instead of raising 389 SAFE_INT_DIVISION = False 390 391 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 392 DUPLICATE_KEY_UPDATE_WITH_SET = True 393 394 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 395 LIMIT_IS_TOP = False 396 397 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 398 RETURNING_END = True 399 400 # Whether to generate an unquoted value for EXTRACT's date part argument 401 EXTRACT_ALLOWS_QUOTES = True 402 403 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 404 TZ_TO_WITH_TIME_ZONE = False 405 406 # Whether the NVL2 function is supported 407 NVL2_SUPPORTED = True 408 409 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 410 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 411 412 # Whether VALUES statements can be used as derived tables. 413 # MySQL 5 and Redshift do not allow this, so when False, it will convert 414 # SELECT * VALUES into SELECT UNION 415 VALUES_AS_TABLE = True 416 417 # Whether the word COLUMN is included when adding a column with ALTER TABLE 418 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 419 420 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 421 UNNEST_WITH_ORDINALITY = True 422 423 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 424 SEMI_ANTI_JOIN_WITH_SIDE = True 425 426 # Whether to include the type of a computed column in the CREATE DDL 427 COMPUTED_COLUMN_WITH_TYPE = True 428 429 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 430 SUPPORTS_TABLE_COPY = True 431 432 # Whether parentheses are required around the table sample's expression 433 TABLESAMPLE_REQUIRES_PARENS = True 434 435 # Whether a table sample clause's size needs to be followed by the ROWS keyword 436 TABLESAMPLE_SIZE_IS_ROWS = True 437 438 # The keyword(s) to use when generating a sample clause 439 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 440 441 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 442 TABLESAMPLE_WITH_METHOD = True 443 444 # The keyword to use when specifying the seed of a sample clause 445 TABLESAMPLE_SEED_KEYWORD = "SEED" 446 447 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 448 HISTORICAL_DATA_POST_ALIAS = False 449 450 # Whether COLLATE is a function instead of a binary operator 451 COLLATE_IS_FUNC = False 452 453 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 454 DATA_TYPE_SPECIFIERS_ALLOWED = False 455 456 # Whether conditions require booleans WHERE x = 0 vs WHERE x 457 ENSURE_BOOLS = False 458 459 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 460 CTE_RECURSIVE_KEYWORD_REQUIRED = True 461 462 # Whether CONCAT requires >1 arguments 463 SUPPORTS_SINGLE_ARG_CONCAT = True 464 465 # Whether LAST_DAY function supports a date part argument 466 LAST_DAY_SUPPORTS_DATE_PART = True 467 468 # Whether named columns are allowed in table aliases 469 SUPPORTS_TABLE_ALIAS_COLUMNS = True 470 471 # Whether named columns are allowed in CTE definitions 472 SUPPORTS_NAMED_CTE_COLUMNS = True 473 474 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 475 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 476 477 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 478 PIVOT_ALIAS_WITH_AS = True 479 480 # What delimiter to use for separating JSON key/value pairs 481 JSON_KEY_VALUE_PAIR_SEP = ":" 482 483 # INSERT OVERWRITE TABLE x override 484 INSERT_OVERWRITE = " OVERWRITE TABLE" 485 486 # Whether the SELECT .. INTO syntax is used instead of CTAS 487 SUPPORTS_SELECT_INTO = False 488 489 # Whether UNLOGGED tables can be created 490 SUPPORTS_UNLOGGED_TABLES = False 491 492 # Whether the CREATE TABLE LIKE statement is supported 493 SUPPORTS_CREATE_TABLE_LIKE = True 494 495 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 496 SUPPORTS_MODIFY_COLUMN = False 497 498 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 499 SUPPORTS_CHANGE_COLUMN = False 500 501 # Whether ALTER COLUMN can set a column's nullability together with its type 502 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 503 504 # Whether ALTER COLUMN IF EXISTS is supported 505 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 506 507 # Whether the LikeProperty needs to be specified inside of the schema clause 508 LIKE_PROPERTY_INSIDE_SCHEMA = False 509 510 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 511 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 512 MULTI_ARG_DISTINCT = True 513 514 # Whether the JSON extraction operators expect a value of type JSON 515 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 516 517 # Whether bracketed keys like ["foo"] are supported in JSON paths 518 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 519 520 # Whether to escape keys using single quotes in JSON paths 521 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 522 523 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 524 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 525 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 526 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 527 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 528 529 # The JSONPathPart expressions supported by this dialect 530 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 531 532 # Whether any(f(x) for x in array) can be implemented by this dialect 533 CAN_IMPLEMENT_ARRAY_ANY = False 534 535 # Whether the function TO_NUMBER is supported 536 SUPPORTS_TO_NUMBER = True 537 538 # Whether EXCLUDE in window specification is supported 539 SUPPORTS_WINDOW_EXCLUDE = False 540 541 # Whether or not set op modifiers apply to the outer set op or select. 542 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 543 # True means limit 1 happens after the set op, False means it it happens on y. 544 SET_OP_MODIFIERS = True 545 546 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 547 SET_OP_LIMITS = False 548 549 # Whether set operation operands can be parenthesized without a SELECT wrapper. 550 SET_OP_PARENTHESIZED_OPERANDS = True 551 552 # Whether parameters from COPY statement are wrapped in parentheses 553 COPY_PARAMS_ARE_WRAPPED = True 554 555 # Whether values of params are set with "=" token or empty space 556 COPY_PARAMS_EQ_REQUIRED = False 557 558 # Whether COPY statement has INTO keyword 559 COPY_HAS_INTO_KEYWORD = True 560 561 # Whether the conditional TRY(expression) function is supported 562 TRY_SUPPORTED = True 563 564 # Whether the UESCAPE syntax in unicode strings is supported 565 SUPPORTS_UESCAPE = True 566 567 # Function used to replace escaped unicode codes in unicode strings 568 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 569 570 # The keyword to use when generating a star projection with excluded columns 571 STAR_EXCEPT = "EXCEPT" 572 573 # The HEX function name 574 HEX_FUNC = "HEX" 575 576 # Function that constructs a binary value from a hex-encoded byte string. 577 BYTE_STRING_FUNCTION = "" 578 579 # The keywords to use when prefixing & separating WITH based properties 580 WITH_PROPERTIES_PREFIX = "WITH" 581 582 # Whether to quote the generated expression of exp.JsonPath 583 QUOTE_JSON_PATH = True 584 585 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 586 PAD_FILL_PATTERN_IS_REQUIRED = False 587 588 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 589 SUPPORTS_EXPLODING_PROJECTIONS = True 590 591 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 592 ARRAY_CONCAT_IS_VAR_LEN = True 593 594 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 595 SUPPORTS_CONVERT_TIMEZONE = False 596 597 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 598 SUPPORTS_MEDIAN = True 599 600 # Whether UNIX_SECONDS(timestamp) is supported 601 SUPPORTS_UNIX_SECONDS = False 602 603 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 604 ALTER_SET_WRAPPED = False 605 606 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 607 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 608 # TODO: The normalization should be done by default once we've tested it across all dialects. 609 NORMALIZE_EXTRACT_DATE_PARTS = False 610 611 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 612 PARSE_JSON_NAME: str | None = "PARSE_JSON" 613 614 # The function name of the exp.ArraySize expression 615 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 616 617 # The syntax to use when altering the type of a column 618 ALTER_SET_TYPE = "SET DATA TYPE" 619 620 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 621 # None -> Doesn't support it at all 622 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 623 # True (Postgres) -> Explicitly requires it 624 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 625 626 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 627 SUPPORTS_DECODE_CASE = True 628 629 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 630 SUPPORTS_BETWEEN_FLAGS = False 631 632 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 633 SUPPORTS_LIKE_QUANTIFIERS = True 634 635 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 636 MATCH_AGAINST_TABLE_PREFIX: str | None = None 637 638 # Whether to include the VARIABLE keyword for SET assignments 639 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 640 641 # The keyword to use for default value assignment in DECLARE statements 642 DECLARE_DEFAULT_ASSIGNMENT = "=" 643 644 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 645 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 646 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 647 UPDATE_STATEMENT_SUPPORTS_FROM = True 648 649 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 650 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 651 652 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 653 # - Snowflake: DROP ICEBERG TABLE a.b; 654 # - DuckDB: DROP TABLE a.b; 655 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 656 657 TYPE_MAPPING: t.ClassVar = { 658 exp.DType.DATETIME2: "TIMESTAMP", 659 exp.DType.NCHAR: "CHAR", 660 exp.DType.NVARCHAR: "VARCHAR", 661 exp.DType.MEDIUMTEXT: "TEXT", 662 exp.DType.LONGTEXT: "TEXT", 663 exp.DType.TINYTEXT: "TEXT", 664 exp.DType.BLOB: "VARBINARY", 665 exp.DType.MEDIUMBLOB: "BLOB", 666 exp.DType.LONGBLOB: "BLOB", 667 exp.DType.TINYBLOB: "BLOB", 668 exp.DType.INET: "INET", 669 exp.DType.ROWVERSION: "VARBINARY", 670 exp.DType.SMALLDATETIME: "TIMESTAMP", 671 } 672 673 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 674 675 # mapping of DType to its default parameters, bounds 676 TYPE_PARAM_SETTINGS: t.ClassVar[ 677 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 678 ] = {} 679 680 TIME_PART_SINGULARS: t.ClassVar = { 681 "MICROSECONDS": "MICROSECOND", 682 "SECONDS": "SECOND", 683 "MINUTES": "MINUTE", 684 "HOURS": "HOUR", 685 "DAYS": "DAY", 686 "WEEKS": "WEEK", 687 "MONTHS": "MONTH", 688 "QUARTERS": "QUARTER", 689 "YEARS": "YEAR", 690 } 691 692 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 693 "cluster": lambda self, e: self.sql(e, "cluster"), 694 "distribute": lambda self, e: self.sql(e, "distribute"), 695 "sort": lambda self, e: self.sql(e, "sort"), 696 **AFTER_HAVING_MODIFIER_TRANSFORMS, 697 } 698 699 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 700 701 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 702 703 PARAMETER_TOKEN = "@" 704 NAMED_PLACEHOLDER_TOKEN = ":" 705 706 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 707 708 PROPERTIES_LOCATION: t.ClassVar = { 709 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 711 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 712 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 713 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 717 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 719 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 721 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 723 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 725 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 730 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 731 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 733 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 734 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 736 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 740 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 746 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 747 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 748 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 749 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 750 exp.HeapProperty: exp.Properties.Location.POST_WITH, 751 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 752 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 754 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 757 exp.JournalProperty: exp.Properties.Location.POST_NAME, 758 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 761 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 763 exp.LogProperty: exp.Properties.Location.POST_NAME, 764 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 765 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 766 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 767 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 769 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 770 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 772 exp.Order: exp.Properties.Location.POST_SCHEMA, 773 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 775 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 777 exp.Property: exp.Properties.Location.POST_WITH, 778 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 782 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 783 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 784 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 785 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 789 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 790 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 791 exp.Set: exp.Properties.Location.POST_SCHEMA, 792 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.SetProperty: exp.Properties.Location.POST_CREATE, 794 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 795 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 796 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 797 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 798 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 801 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 802 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 804 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 805 exp.Tags: exp.Properties.Location.POST_WITH, 806 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 807 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 808 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 809 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 810 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 811 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 812 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 813 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 814 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 815 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 816 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 817 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 818 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 819 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 820 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 821 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 822 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 823 } 824 825 # Keywords that can't be used as unquoted identifier names 826 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 827 828 # Exprs whose comments are separated from them for better formatting 829 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 830 exp.Command, 831 exp.Create, 832 exp.Describe, 833 exp.Delete, 834 exp.Drop, 835 exp.From, 836 exp.Insert, 837 exp.Join, 838 exp.MultitableInserts, 839 exp.Order, 840 exp.Group, 841 exp.Having, 842 exp.Select, 843 exp.SetOperation, 844 exp.Update, 845 exp.Where, 846 exp.With, 847 ) 848 849 # Exprs that should not have their comments generated in maybe_comment 850 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 851 exp.Binary, 852 exp.SetOperation, 853 ) 854 855 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 856 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 857 exp.Column, 858 exp.Literal, 859 exp.Neg, 860 exp.Paren, 861 ) 862 863 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 864 exp.DType.NVARCHAR, 865 exp.DType.VARCHAR, 866 exp.DType.CHAR, 867 exp.DType.NCHAR, 868 } 869 870 # Exprs that need to have all CTEs under them bubbled up to them 871 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 872 873 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 874 875 MOD_OPERATOR = "%" 876 877 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 878 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 879 exp.Mul, 880 exp.Div, 881 exp.IntDiv, 882 exp.Mod, 883 ) 884 885 # ALTER options that follow the others without a comma, e.g. MySQL's 886 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 887 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 888 889 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 890 891 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 892 893 __slots__ = ( 894 "pretty", 895 "identify", 896 "normalize", 897 "pad", 898 "_indent", 899 "normalize_functions", 900 "unsupported_level", 901 "max_unsupported", 902 "leading_comma", 903 "max_text_width", 904 "comments", 905 "dialect", 906 "unsupported_messages", 907 "_escaped_quote_end", 908 "_escaped_byte_quote_end", 909 "_escaped_identifier_end", 910 "_identifier_escaped_sequences", 911 "_next_name", 912 "_identifier_start", 913 "_identifier_end", 914 "_quote_json_path_key_using_brackets", 915 "_dispatch", 916 ) 917 918 def __init__( 919 self, 920 pretty: bool | int | None = None, 921 identify: str | bool = False, 922 normalize: bool = False, 923 pad: int = 2, 924 indent: int = 2, 925 normalize_functions: str | bool | None = None, 926 unsupported_level: ErrorLevel = ErrorLevel.WARN, 927 max_unsupported: int = 3, 928 leading_comma: bool = False, 929 max_text_width: int = 80, 930 comments: bool = True, 931 dialect: DialectType = None, 932 ): 933 import sqlglot 934 import sqlglot.dialects.dialect 935 936 self.pretty = pretty if pretty is not None else sqlglot.pretty 937 self.identify = identify 938 self.normalize = normalize 939 self.pad = pad 940 self._indent = indent 941 self.unsupported_level = unsupported_level 942 self.max_unsupported = max_unsupported 943 self.leading_comma = leading_comma 944 self.max_text_width = max_text_width 945 self.comments = comments 946 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 947 948 # This is both a Dialect property and a Generator argument, so we prioritize the latter 949 self.normalize_functions = ( 950 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 951 ) 952 953 self.unsupported_messages: list[str] = [] 954 self._escaped_quote_end: str = ( 955 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 956 ) 957 self._escaped_byte_quote_end: str = ( 958 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 959 if self.dialect.BYTE_END 960 else "" 961 ) 962 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 963 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 964 965 self._next_name = name_sequence("_t") 966 967 self._identifier_start = self.dialect.IDENTIFIER_START 968 self._identifier_end = self.dialect.IDENTIFIER_END 969 970 self._quote_json_path_key_using_brackets = True 971 972 cls = type(self) 973 dispatch = _DISPATCH_CACHE.get(cls) 974 if dispatch is None: 975 dispatch = _build_dispatch(cls) 976 _DISPATCH_CACHE[cls] = dispatch 977 self._dispatch = dispatch 978 979 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 980 """ 981 Generates the SQL string corresponding to the given syntax tree. 982 983 Args: 984 expression: The syntax tree. 985 copy: Whether to copy the expression. The generator performs mutations so 986 it is safer to copy. 987 988 Returns: 989 The SQL string corresponding to `expression`. 990 """ 991 if copy: 992 expression = expression.copy() 993 994 expression = self.preprocess(expression) 995 996 self.unsupported_messages = [] 997 sql = self.sql(expression).strip() 998 999 if self.pretty: 1000 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1001 1002 if self.unsupported_level == ErrorLevel.IGNORE: 1003 return sql 1004 1005 if self.unsupported_level == ErrorLevel.WARN: 1006 for msg in self.unsupported_messages: 1007 logger.warning(msg) 1008 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1009 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1010 1011 return sql 1012 1013 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1014 """Apply generic preprocessing transformations to a given expression.""" 1015 expression = self._move_ctes_to_top_level(expression) 1016 1017 if self.ENSURE_BOOLS: 1018 import sqlglot.transforms 1019 1020 expression = sqlglot.transforms.ensure_bools(expression) 1021 1022 return expression 1023 1024 def _move_ctes_to_top_level(self, expression: E) -> E: 1025 if ( 1026 not expression.parent 1027 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1028 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1029 ): 1030 import sqlglot.transforms 1031 1032 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1033 return expression 1034 1035 def unsupported(self, message: str) -> None: 1036 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1037 raise UnsupportedError(message) 1038 self.unsupported_messages.append(message) 1039 1040 def sep(self, sep: str = " ") -> str: 1041 return f"{sep.strip()}\n" if self.pretty else sep 1042 1043 def seg(self, sql: str, sep: str = " ") -> str: 1044 return f"{self.sep(sep)}{sql}" 1045 1046 def sanitize_comment(self, comment: str) -> str: 1047 comment = " " + comment if comment[0].strip() else comment 1048 comment = comment + " " if comment[-1].strip() else comment 1049 1050 # Escape block comment markers to prevent premature closure or unintended nesting. 1051 # This is necessary because single-line comments (--) are converted to block comments 1052 # (/* */) on output, and any */ in the original text would close the comment early. 1053 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1054 1055 return comment 1056 1057 def maybe_comment( 1058 self, 1059 sql: str, 1060 expression: exp.Expr | None = None, 1061 comments: list[str] | None = None, 1062 separated: bool = False, 1063 ) -> str: 1064 comments = ( 1065 ((expression and expression.comments) if comments is None else comments) # type: ignore 1066 if self.comments 1067 else None 1068 ) 1069 1070 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1071 return sql 1072 1073 comments_list = [ 1074 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1075 for comment in comments 1076 if comment 1077 ] 1078 1079 if not comments_list: 1080 return sql 1081 1082 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1083 comments_sql = self.sep().join(comments_list) 1084 return ( 1085 f"{self.sep()}{comments_sql}{sql}" 1086 if not sql or sql[0].isspace() 1087 else f"{comments_sql}{self.sep()}{sql}" 1088 ) 1089 1090 return f"{sql} {' '.join(comments_list)}" 1091 1092 def wrap(self, expression: exp.Expr | str) -> str: 1093 this_sql = ( 1094 self.sql(expression) 1095 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1096 else self.sql(expression, "this") 1097 ) 1098 if not this_sql: 1099 return "()" 1100 1101 this_sql = self.indent(this_sql, level=1, pad=0) 1102 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1103 1104 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1105 original = self.identify 1106 self.identify = False 1107 result = func(*args, **kwargs) 1108 self.identify = original 1109 return result 1110 1111 def normalize_func(self, name: str) -> str: 1112 if self.normalize_functions == "upper" or self.normalize_functions is True: 1113 return name.upper() 1114 if self.normalize_functions == "lower": 1115 return name.lower() 1116 return name 1117 1118 def indent( 1119 self, 1120 sql: str, 1121 level: int = 0, 1122 pad: int | None = None, 1123 skip_first: bool = False, 1124 skip_last: bool = False, 1125 ) -> str: 1126 if not self.pretty or not sql: 1127 return sql 1128 1129 pad = self.pad if pad is None else pad 1130 lines = sql.split("\n") 1131 1132 return "\n".join( 1133 ( 1134 line 1135 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1136 else f"{' ' * (level * self._indent + pad)}{line}" 1137 ) 1138 for i, line in enumerate(lines) 1139 ) 1140 1141 def sql( 1142 self, 1143 expression: str | exp.Expr | None, 1144 key: str | None = None, 1145 comment: bool = True, 1146 ) -> str: 1147 if not expression: 1148 return "" 1149 1150 if isinstance(expression, str): 1151 return expression 1152 1153 if key: 1154 value = expression.args.get(key) 1155 if value: 1156 return self.sql(value) 1157 return "" 1158 1159 handler = self._dispatch.get(expression.__class__) 1160 1161 if handler: 1162 sql = handler(self, expression) 1163 elif isinstance(expression, exp.Func): 1164 sql = self.function_fallback_sql(expression) 1165 elif isinstance(expression, exp.Property): 1166 sql = self.property_sql(expression) 1167 else: 1168 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1169 1170 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1171 1172 def uncache_sql(self, expression: exp.Uncache) -> str: 1173 table = self.sql(expression, "this") 1174 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1175 return f"UNCACHE TABLE{exists_sql} {table}" 1176 1177 def cache_sql(self, expression: exp.Cache) -> str: 1178 lazy = " LAZY" if expression.args.get("lazy") else "" 1179 table = self.sql(expression, "this") 1180 options = expression.args.get("options") 1181 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1182 sql = self.sql(expression, "expression") 1183 sql = f" AS{self.sep()}{sql}" if sql else "" 1184 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1185 return self.prepend_ctes(expression, sql) 1186 1187 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1188 default = "DEFAULT " if expression.args.get("default") else "" 1189 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1190 1191 def column_parts(self, expression: exp.Column) -> str: 1192 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1193 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1194 return self.sql(expression, "this") 1195 1196 return ".".join( 1197 self.sql(part) 1198 for part in ( 1199 expression.args.get("catalog"), 1200 expression.args.get("db"), 1201 expression.args.get("table"), 1202 expression.args.get("this"), 1203 ) 1204 if part 1205 ) 1206 1207 def column_sql(self, expression: exp.Column) -> str: 1208 join_mark = " (+)" if expression.args.get("join_mark") else "" 1209 1210 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1211 join_mark = "" 1212 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1213 1214 return f"{self.column_parts(expression)}{join_mark}" 1215 1216 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1217 return self.column_sql(expression) 1218 1219 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1220 this = self.sql(expression, "this") 1221 this = f" {this}" if this else "" 1222 position = self.sql(expression, "position") 1223 return f"{position}{this}" 1224 1225 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1226 column = self.sql(expression, "this") 1227 kind = self.sql(expression, "kind") 1228 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1229 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1230 kind = f"{sep}{kind}" if kind else "" 1231 constraints = f" {constraints}" if constraints else "" 1232 position = self.sql(expression, "position") 1233 position = f" {position}" if position else "" 1234 1235 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1236 kind = "" 1237 1238 return f"{exists}{column}{kind}{constraints}{position}" 1239 1240 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1241 this = self.sql(expression, "this") 1242 kind_sql = self.sql(expression, "kind").strip() 1243 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1244 1245 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1246 this = self.sql(expression, "this") 1247 if expression.args.get("not_null"): 1248 persisted = " PERSISTED NOT NULL" 1249 elif expression.args.get("persisted"): 1250 persisted = " PERSISTED" 1251 else: 1252 persisted = "" 1253 1254 return f"AS {this}{persisted}" 1255 1256 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1257 return self.token_sql(TokenType.AUTO_INCREMENT) 1258 1259 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1260 if isinstance(expression.this, list): 1261 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1262 else: 1263 this = self.sql(expression, "this") 1264 1265 return f"COMPRESS {this}" 1266 1267 def generatedasidentitycolumnconstraint_sql( 1268 self, expression: exp.GeneratedAsIdentityColumnConstraint 1269 ) -> str: 1270 this = "" 1271 if expression.this is not None: 1272 on_null = " ON NULL" if expression.args.get("on_null") else "" 1273 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1274 1275 start = expression.args.get("start") 1276 start = f"START WITH {start}" if start else "" 1277 increment = expression.args.get("increment") 1278 increment = f" INCREMENT BY {increment}" if increment else "" 1279 minvalue = expression.args.get("minvalue") 1280 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1281 maxvalue = expression.args.get("maxvalue") 1282 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1283 cycle = expression.args.get("cycle") 1284 cycle_sql = "" 1285 1286 if cycle is not None: 1287 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1288 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1289 1290 sequence_opts = "" 1291 if start or increment or cycle_sql: 1292 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1293 sequence_opts = f" ({sequence_opts.strip()})" 1294 1295 expr = self.sql(expression, "expression") 1296 expr = f"({expr})" if expr else "IDENTITY" 1297 1298 return f"GENERATED{this} AS {expr}{sequence_opts}" 1299 1300 def generatedasrowcolumnconstraint_sql( 1301 self, expression: exp.GeneratedAsRowColumnConstraint 1302 ) -> str: 1303 start = "START" if expression.args.get("start") else "END" 1304 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1305 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1306 1307 def periodforsystemtimeconstraint_sql( 1308 self, expression: exp.PeriodForSystemTimeConstraint 1309 ) -> str: 1310 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1311 1312 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1313 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1314 1315 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1316 desc = expression.args.get("desc") 1317 if desc is not None: 1318 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1319 options = self.expressions(expression, key="options", flat=True, sep=" ") 1320 options = f" {options}" if options else "" 1321 return f"PRIMARY KEY{options}" 1322 1323 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1324 this = self.sql(expression, "this") 1325 this = f" {this}" if this else "" 1326 index_type = expression.args.get("index_type") 1327 index_type = f" USING {index_type}" if index_type else "" 1328 on_conflict = self.sql(expression, "on_conflict") 1329 on_conflict = f" {on_conflict}" if on_conflict else "" 1330 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1331 options = self.expressions(expression, key="options", flat=True, sep=" ") 1332 options = f" {options}" if options else "" 1333 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1334 1335 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1336 input_ = expression.args.get("input_") 1337 output = expression.args.get("output") 1338 variadic = expression.args.get("variadic") 1339 1340 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1341 if variadic: 1342 return "VARIADIC" 1343 1344 if input_ and output: 1345 return f"IN{self.INOUT_SEPARATOR}OUT" 1346 if input_: 1347 return "IN" 1348 if output: 1349 return "OUT" 1350 1351 return "" 1352 1353 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1354 return self.sql(expression, "this") 1355 1356 def create_sql(self, expression: exp.Create) -> str: 1357 kind = self.sql(expression, "kind") 1358 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1359 1360 properties = expression.args.get("properties") 1361 1362 if ( 1363 kind == "TRIGGER" 1364 and properties 1365 and properties.expressions 1366 and isinstance(properties.expressions[0], exp.TriggerProperties) 1367 and properties.expressions[0].args.get("constraint") 1368 ): 1369 kind = f"CONSTRAINT {kind}" 1370 1371 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1372 1373 this = self.createable_sql(expression, properties_locs) 1374 1375 properties_sql = "" 1376 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1377 exp.Properties.Location.POST_WITH 1378 ): 1379 props_ast = exp.Properties( 1380 expressions=[ 1381 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1382 *properties_locs[exp.Properties.Location.POST_WITH], 1383 ] 1384 ) 1385 props_ast.parent = expression 1386 properties_sql = self.sql(props_ast) 1387 1388 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1389 properties_sql = self.sep() + properties_sql 1390 elif not self.pretty: 1391 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1392 properties_sql = f" {properties_sql}" 1393 1394 begin = " BEGIN" if expression.args.get("begin") else "" 1395 1396 expression_sql = self.sql(expression, "expression") 1397 if expression_sql: 1398 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1399 1400 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1401 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1402 ): 1403 postalias_props_sql = "" 1404 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1405 postalias_props_sql = self.properties( 1406 exp.Properties( 1407 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1408 ), 1409 wrapped=False, 1410 ) 1411 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1412 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1413 1414 postindex_props_sql = "" 1415 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1416 postindex_props_sql = self.properties( 1417 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1418 wrapped=False, 1419 prefix=" ", 1420 ) 1421 1422 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1423 indexes = f" {indexes}" if indexes else "" 1424 index_sql = indexes + postindex_props_sql 1425 1426 replace = " OR REPLACE" if expression.args.get("replace") else "" 1427 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1428 unique = " UNIQUE" if expression.args.get("unique") else "" 1429 1430 clustered = expression.args.get("clustered") 1431 if clustered is None: 1432 clustered_sql = "" 1433 elif clustered: 1434 clustered_sql = " CLUSTERED COLUMNSTORE" 1435 else: 1436 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1437 1438 postcreate_props_sql = "" 1439 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1440 postcreate_props_sql = self.properties( 1441 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1442 sep=" ", 1443 prefix=" ", 1444 wrapped=False, 1445 ) 1446 1447 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1448 1449 postexpression_props_sql = "" 1450 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1451 postexpression_props_sql = self.properties( 1452 exp.Properties( 1453 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1454 ), 1455 sep=" ", 1456 prefix=" ", 1457 wrapped=False, 1458 ) 1459 1460 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1461 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1462 no_schema_binding = ( 1463 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1464 ) 1465 1466 clone = self.sql(expression, "clone") 1467 clone = f" {clone}" if clone else "" 1468 1469 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1470 properties_expression = f"{expression_sql}{properties_sql}" 1471 else: 1472 properties_expression = f"{properties_sql}{expression_sql}" 1473 1474 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1475 return self.prepend_ctes(expression, expression_sql) 1476 1477 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1478 start = self.sql(expression, "start") 1479 start = f"START WITH {start}" if start else "" 1480 increment = self.sql(expression, "increment") 1481 increment = f" INCREMENT BY {increment}" if increment else "" 1482 minvalue = self.sql(expression, "minvalue") 1483 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1484 maxvalue = self.sql(expression, "maxvalue") 1485 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1486 owned = self.sql(expression, "owned") 1487 owned = f" OWNED BY {owned}" if owned else "" 1488 1489 cache = expression.args.get("cache") 1490 if cache is None: 1491 cache_str = "" 1492 elif cache is True: 1493 cache_str = " CACHE" 1494 else: 1495 cache_str = f" CACHE {cache}" 1496 1497 options = self.expressions(expression, key="options", flat=True, sep=" ") 1498 options = f" {options}" if options else "" 1499 1500 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1501 1502 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1503 timing = expression.args.get("timing", "") 1504 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1505 timing_events = f"{timing} {events}".strip() if timing or events else "" 1506 1507 parts = [timing_events, "ON", self.sql(expression, "table")] 1508 1509 if referenced_table := expression.args.get("referenced_table"): 1510 parts.extend(["FROM", self.sql(referenced_table)]) 1511 1512 if deferrable := expression.args.get("deferrable"): 1513 parts.append(deferrable) 1514 1515 if initially := expression.args.get("initially"): 1516 parts.append(f"INITIALLY {initially}") 1517 1518 if referencing := expression.args.get("referencing"): 1519 parts.append(self.sql(referencing)) 1520 1521 if for_each := expression.args.get("for_each"): 1522 parts.append(f"FOR EACH {for_each}") 1523 1524 if when := expression.args.get("when"): 1525 parts.append(f"WHEN ({self.sql(when)})") 1526 1527 parts.append(self.sql(expression, "execute")) 1528 1529 return self.sep().join(parts) 1530 1531 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1532 parts = [] 1533 1534 if old_alias := expression.args.get("old"): 1535 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1536 1537 if new_alias := expression.args.get("new"): 1538 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1539 1540 return f"REFERENCING {' '.join(parts)}" 1541 1542 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1543 columns = expression.args.get("columns") 1544 if columns: 1545 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1546 1547 return self.sql(expression, "this") 1548 1549 def clone_sql(self, expression: exp.Clone) -> str: 1550 this = self.sql(expression, "this") 1551 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1552 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1553 return f"{shallow}{keyword} {this}" 1554 1555 def describe_sql(self, expression: exp.Describe) -> str: 1556 style = expression.args.get("style") 1557 style = f" {style}" if style else "" 1558 partition = self.sql(expression, "partition") 1559 partition = f" {partition}" if partition else "" 1560 format = self.sql(expression, "format") 1561 format = f" {format}" if format else "" 1562 as_json = " AS JSON" if expression.args.get("as_json") else "" 1563 1564 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1565 1566 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1567 tag = self.sql(expression, "tag") 1568 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1569 1570 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1571 with_ = self.sql(expression, "with_") 1572 if with_: 1573 sql = f"{with_}{self.sep()}{sql}" 1574 return sql 1575 1576 def with_sql(self, expression: exp.With) -> str: 1577 udfs = self.expressions(expression, key="udfs", flat=True) 1578 udfs = f"WITH {udfs}" if udfs else "" 1579 1580 sql = self.expressions(expression, flat=True) 1581 1582 recursive = ( 1583 "RECURSIVE " 1584 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1585 else "" 1586 ) 1587 search = self.sql(expression, "search") 1588 search = f" {search}" if search else "" 1589 cycle = self.sql(expression, "cycle") 1590 cycle = f" {cycle}" if cycle else "" 1591 1592 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1593 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1594 1595 def cte_sql(self, expression: exp.CTE) -> str: 1596 alias = expression.args.get("alias") 1597 if alias: 1598 alias.add_comments(expression.pop_comments()) 1599 1600 alias_sql = self.sql(expression, "alias") 1601 1602 materialized = expression.args.get("materialized") 1603 if materialized is False: 1604 materialized = "NOT MATERIALIZED " 1605 elif materialized: 1606 materialized = "MATERIALIZED " 1607 1608 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1609 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1610 1611 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1612 1613 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1614 alias = self.sql(expression, "this") 1615 columns = self.expressions(expression, key="columns", flat=True) 1616 columns = f"({columns})" if columns else "" 1617 1618 if ( 1619 columns 1620 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1621 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1622 ): 1623 columns = "" 1624 self.unsupported("Named columns are not supported in table alias.") 1625 1626 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1627 alias = self._next_name() 1628 1629 return f"{alias}{columns}" 1630 1631 def bitstring_sql(self, expression: exp.BitString) -> str: 1632 this = self.sql(expression, "this") 1633 if self.dialect.BIT_START: 1634 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1635 return f"{int(this, 2)}" 1636 1637 def hexstring_sql( 1638 self, expression: exp.HexString, binary_function_repr: str | None = None 1639 ) -> str: 1640 this = self.sql(expression, "this") 1641 is_integer_type = expression.args.get("is_integer") 1642 1643 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1644 not self.dialect.HEX_START and not binary_function_repr 1645 ): 1646 # Integer representation will be returned if: 1647 # - The read dialect treats the hex value as integer literal but not the write 1648 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1649 return f"{int(this, 16)}" 1650 1651 if not is_integer_type: 1652 # Read dialect treats the hex value as BINARY/BLOB 1653 if binary_function_repr: 1654 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1655 return self.func(binary_function_repr, exp.Literal.string(this)) 1656 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1657 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1658 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1659 1660 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1661 1662 def bytestring_sql(self, expression: exp.ByteString) -> str: 1663 this = self.sql(expression, "this") 1664 is_bytes = bool(expression.args.get("is_bytes")) 1665 if is_bytes and self.BYTE_STRING_FUNCTION: 1666 # Hex digits can be quoted directly without escaping. 1667 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1668 1669 if self.dialect.BYTE_START: 1670 escaped_byte_string = self.escape_str( 1671 this, 1672 escape_backslash=bool( 1673 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1674 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1675 ), 1676 delimiter=self.dialect.BYTE_END, 1677 escaped_delimiter=self._escaped_byte_quote_end, 1678 is_byte_string=True, 1679 is_bytes=is_bytes, 1680 ) 1681 delimited_byte_string = ( 1682 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1683 ) 1684 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1685 return self.sql( 1686 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1687 ) 1688 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1689 return self.sql( 1690 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1691 ) 1692 1693 return delimited_byte_string 1694 1695 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1696 return self.sql(exp.Literal.string(this)) 1697 1698 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1699 return "" 1700 1701 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1702 this = self.sql(expression, "this") 1703 escape = expression.args.get("escape") 1704 unicode_start = self.dialect.UNICODE_START 1705 1706 if unicode_start: 1707 escape_substitute = r"\\\1" 1708 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1709 else: 1710 escape_substitute = r"\\u\1" 1711 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1712 1713 if escape: 1714 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1715 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1716 else: 1717 escape_pattern = ESCAPED_UNICODE_RE 1718 escape_sql = "" 1719 1720 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1721 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1722 1723 if unicode_start: 1724 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1725 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1726 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1727 else: 1728 this = self.escape_str(this, escape_backslash=False) 1729 1730 return f"{left_quote}{this}{right_quote}{escape_sql}" 1731 1732 def rawstring_sql(self, expression: exp.RawString) -> str: 1733 string = expression.this 1734 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1735 string = string.replace("\\", "\\\\") 1736 1737 string = self.escape_str(string, escape_backslash=False) 1738 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1739 1740 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1741 this = self.sql(expression, "this") 1742 specifier = self.sql(expression, "expression") 1743 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1744 return f"{this}{specifier}" 1745 1746 def datatype_param_bound_limiter( 1747 self, 1748 expression: exp.DataType, 1749 type_value: exp.DType, 1750 defaults: tuple[int, ...], 1751 bounds: tuple[int | None, ...], 1752 ) -> exp.DataType: 1753 params = expression.expressions 1754 1755 if not params: 1756 if defaults: 1757 expression.set( 1758 "expressions", 1759 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1760 ) 1761 return expression 1762 1763 if not bounds: 1764 return expression 1765 1766 for i, param in enumerate(params): 1767 bound = bounds[i] if i < len(bounds) else None 1768 if bound is None: 1769 continue 1770 1771 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1772 value = ( 1773 param_value.to_py() 1774 if isinstance(param_value, exp.Literal) and param_value.is_number 1775 else None 1776 ) 1777 if isinstance(value, (int, Decimal)) and value > bound: 1778 self.unsupported( 1779 f"{type_value.value} parameter {param_value.name} exceeds " 1780 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1781 ) 1782 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1783 1784 return expression 1785 1786 def datatype_sql(self, expression: exp.DataType) -> str: 1787 nested = "" 1788 values = "" 1789 1790 expr_nested = expression.args.get("nested") 1791 type_value = expression.this 1792 1793 if ( 1794 not expr_nested 1795 and isinstance(type_value, exp.DType) 1796 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1797 ): 1798 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1799 1800 interior = ( 1801 self.expressions( 1802 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1803 ) 1804 if expr_nested and self.pretty 1805 else self.expressions(expression, flat=True) 1806 ) 1807 1808 if type_value in self.UNSUPPORTED_TYPES: 1809 self.unsupported( 1810 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1811 ) 1812 1813 type_sql: t.Any = "" 1814 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1815 type_sql = self.sql(expression, "kind") 1816 elif type_value == exp.DType.CHARACTER_SET: 1817 char = f"CHAR({interior})" if interior else "CHAR" 1818 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1819 else: 1820 type_sql = ( 1821 self.TYPE_MAPPING.get(type_value, type_value.value) 1822 if isinstance(type_value, exp.DType) 1823 else type_value 1824 ) 1825 1826 if interior: 1827 if expr_nested: 1828 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1829 if expression.args.get("values") is not None: 1830 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1831 values = self.expressions(expression, key="values", flat=True) 1832 values = f"{delimiters[0]}{values}{delimiters[1]}" 1833 elif type_value == exp.DType.INTERVAL: 1834 nested = f" {interior}" 1835 else: 1836 nested = f"({interior})" 1837 1838 type_sql = f"{type_sql}{nested}{values}" 1839 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1840 exp.DType.TIMETZ, 1841 exp.DType.TIMESTAMPTZ, 1842 ): 1843 type_sql = f"{type_sql} WITH TIME ZONE" 1844 1845 collate = self.sql(expression, "collate") 1846 if collate: 1847 type_sql = f"{type_sql} COLLATE {collate}" 1848 1849 return type_sql 1850 1851 def directory_sql(self, expression: exp.Directory) -> str: 1852 local = "LOCAL " if expression.args.get("local") else "" 1853 row_format = self.sql(expression, "row_format") 1854 row_format = f" {row_format}" if row_format else "" 1855 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1856 1857 def delete_sql(self, expression: exp.Delete) -> str: 1858 hint = self.sql(expression, "hint") 1859 this = self.sql(expression, "this") 1860 this = f" FROM {this}" if this else "" 1861 using = self.expressions(expression, key="using") 1862 using = f" USING {using}" if using else "" 1863 cluster = self.sql(expression, "cluster") 1864 cluster = f" {cluster}" if cluster else "" 1865 where = self.sql(expression, "where") 1866 returning = self.sql(expression, "returning") 1867 order = self.sql(expression, "order") 1868 limit = self.sql(expression, "limit") 1869 tables = self.expressions(expression, key="tables") 1870 tables = f" {tables}" if tables else "" 1871 if self.RETURNING_END: 1872 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1873 else: 1874 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1875 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1876 1877 def drop_sql(self, expression: exp.Drop) -> str: 1878 tables = self.expressions(expression, key="tables", flat=True) 1879 expressions = self.expressions(expression, flat=True) 1880 expressions = f" ({expressions})" if expressions else "" 1881 kind = expression.args["kind"] 1882 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1883 iceberg = ( 1884 " ICEBERG" 1885 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1886 else "" 1887 ) 1888 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1889 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1890 on_cluster = self.sql(expression, "cluster") 1891 on_cluster = f" {on_cluster}" if on_cluster else "" 1892 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1893 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1894 cascade = " CASCADE" if expression.args.get("cascade") else "" 1895 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1896 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1897 purge = " PURGE" if expression.args.get("purge") else "" 1898 sync = " SYNC" if expression.args.get("sync") else "" 1899 force = " FORCE" if expression.args.get("force") else "" 1900 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1901 1902 def set_operation(self, expression: exp.SetOperation) -> str: 1903 op_type = type(expression) 1904 op_name = op_type.key.upper() 1905 1906 distinct = expression.args.get("distinct") 1907 if ( 1908 distinct is False 1909 and op_type in (exp.Except, exp.Intersect) 1910 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1911 ): 1912 self.unsupported(f"{op_name} ALL is not supported") 1913 1914 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1915 1916 if distinct is None: 1917 distinct = default_distinct 1918 if distinct is None: 1919 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1920 1921 if distinct is default_distinct: 1922 distinct_or_all = "" 1923 else: 1924 distinct_or_all = " DISTINCT" if distinct else " ALL" 1925 1926 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1927 side_kind = f"{side_kind} " if side_kind else "" 1928 1929 by_name = " BY NAME" if expression.args.get("by_name") else "" 1930 on = self.expressions(expression, key="on", flat=True) 1931 on = f" ON ({on})" if on else "" 1932 1933 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1934 1935 def set_operations(self, expression: exp.SetOperation) -> str: 1936 if not self.SET_OP_MODIFIERS: 1937 limit = expression.args.get("limit") 1938 order = expression.args.get("order") 1939 offset = expression.args.get("offset") 1940 1941 if limit or order or offset: 1942 select = self._move_ctes_to_top_level( 1943 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1944 ) 1945 1946 for arg in ("limit", "order", "offset"): 1947 if value := expression.args.get(arg): 1948 select.set(arg, value.pop()) 1949 return self.sql(select) 1950 1951 sqls: list[str] = [] 1952 stack: list[str | exp.Expr] = [expression] 1953 1954 while stack: 1955 node = stack.pop() 1956 1957 if isinstance(node, exp.SetOperation): 1958 stack.append(node.expression) 1959 stack.append( 1960 self.maybe_comment( 1961 self.set_operation(node), comments=node.comments, separated=True 1962 ) 1963 ) 1964 stack.append(node.this) 1965 else: 1966 if ( 1967 not self.SET_OP_LIMITS 1968 and isinstance(node, exp.Select) 1969 and node.args.get("limit") 1970 ): 1971 node = node.subquery(copy=False) 1972 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1973 node = exp.select("*").from_(node, copy=False) 1974 sqls.append(self.sql(node)) 1975 1976 this = self.sep().join(sqls) 1977 this = self.query_modifiers(expression, this) 1978 return self.prepend_ctes(expression, this) 1979 1980 def fetch_sql(self, expression: exp.Fetch) -> str: 1981 direction = expression.args.get("direction") 1982 direction = f" {direction}" if direction else "" 1983 count = self.sql(expression, "count") 1984 count = f" {count}" if count else "" 1985 limit_options = self.sql(expression, "limit_options") 1986 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1987 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1988 1989 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1990 percent = " PERCENT" if expression.args.get("percent") else "" 1991 rows = " ROWS" if expression.args.get("rows") else "" 1992 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1993 if not with_ties and rows: 1994 with_ties = " ONLY" 1995 return f"{percent}{rows}{with_ties}" 1996 1997 def filter_sql(self, expression: exp.Filter) -> str: 1998 this = self.sql(expression, "this") 1999 where = self.sql(expression, "expression").strip() 2000 return f"{this} FILTER({where})" 2001 2002 def hint_sql(self, expression: exp.Hint) -> str: 2003 if not self.QUERY_HINTS: 2004 self.unsupported("Hints are not supported") 2005 return "" 2006 2007 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2008 2009 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2010 using = self.sql(expression, "using") 2011 using = f" USING {using}" if using else "" 2012 columns = self.expressions(expression, key="columns", flat=True) 2013 columns = f"({columns})" if columns else "" 2014 partition_by = self.expressions(expression, key="partition_by", flat=True) 2015 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2016 where = self.sql(expression, "where") 2017 include = self.expressions(expression, key="include", flat=True) 2018 if include: 2019 include = f" INCLUDE ({include})" 2020 with_storage = self.expressions(expression, key="with_storage", flat=True) 2021 with_storage = f" WITH ({with_storage})" if with_storage else "" 2022 tablespace = self.sql(expression, "tablespace") 2023 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 2024 on = self.sql(expression, "on") 2025 on = f" ON {on}" if on else "" 2026 2027 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2028 2029 def index_sql(self, expression: exp.Index) -> str: 2030 unique = "UNIQUE " if expression.args.get("unique") else "" 2031 primary = "PRIMARY " if expression.args.get("primary") else "" 2032 amp = "AMP " if expression.args.get("amp") else "" 2033 name = self.sql(expression, "this") 2034 name = f"{name} " if name else "" 2035 table = self.sql(expression, "table") 2036 table = f"{self.INDEX_ON} {table}" if table else "" 2037 2038 index = "INDEX " if not table else "" 2039 2040 params = self.sql(expression, "params") 2041 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2042 2043 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2044 this = expression.this 2045 if this and this.is_string: 2046 resolved = maybe_parse(this.name).sql(self.dialect) 2047 if "expressions" in expression.args: 2048 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2049 # We can't safely emit the call to other dialects since name/arg semantics may differ 2050 self.unsupported( 2051 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2052 ) 2053 return resolved 2054 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2055 return self.func("IDENTIFIER", this) 2056 2057 def identifier_sql(self, expression: exp.Identifier) -> str: 2058 text = expression.name 2059 lower = text.lower() 2060 quoted = expression.quoted 2061 text = lower if self.normalize and not quoted else text 2062 if self._identifier_escaped_sequences: 2063 for char, escaped in self._identifier_escaped_sequences.items(): 2064 text = text.replace(char, escaped) 2065 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2066 if ( 2067 quoted 2068 or self.dialect.can_quote(expression, self.identify) 2069 or lower in self.RESERVED_KEYWORDS 2070 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2071 ): 2072 text = ( 2073 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2074 ) 2075 return text 2076 2077 def hex_sql(self, expression: exp.Hex) -> str: 2078 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2079 if self.dialect.HEX_LOWERCASE: 2080 text = self.func("LOWER", text) 2081 2082 return text 2083 2084 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2085 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2086 if not self.dialect.HEX_LOWERCASE: 2087 text = self.func("LOWER", text) 2088 return text 2089 2090 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2091 input_format = self.sql(expression, "input_format") 2092 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2093 output_format = self.sql(expression, "output_format") 2094 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2095 return self.sep().join((input_format, output_format)) 2096 2097 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2098 string = self.sql(exp.Literal.string(expression.name)) 2099 return f"{prefix}{string}" 2100 2101 def partition_sql(self, expression: exp.Partition) -> str: 2102 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2103 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2104 2105 def properties_sql(self, expression: exp.Properties) -> str: 2106 root_properties = [] 2107 with_properties = [] 2108 2109 for p in expression.expressions: 2110 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2111 if p_loc == exp.Properties.Location.POST_WITH: 2112 with_properties.append(p) 2113 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2114 root_properties.append(p) 2115 2116 root_props_ast = exp.Properties(expressions=root_properties) 2117 root_props_ast.parent = expression.parent 2118 2119 with_props_ast = exp.Properties(expressions=with_properties) 2120 with_props_ast.parent = expression.parent 2121 2122 root_props = self.root_properties(root_props_ast) 2123 with_props = self.with_properties(with_props_ast) 2124 2125 if root_props and with_props and not self.pretty: 2126 with_props = " " + with_props 2127 2128 return root_props + with_props 2129 2130 def root_properties(self, properties: exp.Properties) -> str: 2131 if properties.expressions: 2132 return self.expressions(properties, indent=False, sep=" ") 2133 return "" 2134 2135 def properties( 2136 self, 2137 properties: exp.Properties, 2138 prefix: str = "", 2139 sep: str = ", ", 2140 suffix: str = "", 2141 wrapped: bool = True, 2142 ) -> str: 2143 if properties.expressions: 2144 expressions = self.expressions(properties, sep=sep, indent=False) 2145 if expressions: 2146 expressions = self.wrap(expressions) if wrapped else expressions 2147 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2148 return "" 2149 2150 def with_properties(self, properties: exp.Properties) -> str: 2151 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2152 2153 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2154 properties_locs = defaultdict(list) 2155 for p in properties.expressions: 2156 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2157 if p_loc != exp.Properties.Location.UNSUPPORTED: 2158 properties_locs[p_loc].append(p) 2159 else: 2160 self.unsupported(f"Unsupported property {p.key}") 2161 2162 return properties_locs 2163 2164 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2165 if isinstance(expression.this, exp.Dot): 2166 return self.sql(expression, "this") 2167 return f"'{expression.name}'" if string_key else expression.name 2168 2169 def property_sql(self, expression: exp.Property) -> str: 2170 property_cls = expression.__class__ 2171 if property_cls == exp.Property: 2172 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2173 2174 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2175 if not property_name: 2176 self.unsupported(f"Unsupported property {expression.key}") 2177 2178 return f"{property_name}={self.sql(expression, 'this')}" 2179 2180 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2181 return f"UUID {self.sql(expression, 'this')}" 2182 2183 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2184 if self.SUPPORTS_CREATE_TABLE_LIKE: 2185 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2186 options = f" {options}" if options else "" 2187 2188 like = f"LIKE {self.sql(expression, 'this')}{options}" 2189 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2190 like = f"({like})" 2191 2192 return like 2193 2194 if expression.expressions: 2195 self.unsupported("Transpilation of LIKE property options is unsupported") 2196 2197 select = exp.select("*").from_(expression.this).limit(0) 2198 return f"AS {self.sql(select)}" 2199 2200 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2201 no = "NO " if expression.args.get("no") else "" 2202 protection = " PROTECTION" if expression.args.get("protection") else "" 2203 return f"{no}FALLBACK{protection}" 2204 2205 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2206 no = "NO " if expression.args.get("no") else "" 2207 local = expression.args.get("local") 2208 local = f"{local} " if local else "" 2209 dual = "DUAL " if expression.args.get("dual") else "" 2210 before = "BEFORE " if expression.args.get("before") else "" 2211 after = "AFTER " if expression.args.get("after") else "" 2212 return f"{no}{local}{dual}{before}{after}JOURNAL" 2213 2214 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2215 freespace = self.sql(expression, "this") 2216 percent = " PERCENT" if expression.args.get("percent") else "" 2217 return f"FREESPACE={freespace}{percent}" 2218 2219 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2220 if expression.args.get("default"): 2221 property = "DEFAULT" 2222 elif expression.args.get("on"): 2223 property = "ON" 2224 else: 2225 property = "OFF" 2226 return f"CHECKSUM={property}" 2227 2228 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2229 if expression.args.get("no"): 2230 return "NO MERGEBLOCKRATIO" 2231 if expression.args.get("default"): 2232 return "DEFAULT MERGEBLOCKRATIO" 2233 2234 percent = " PERCENT" if expression.args.get("percent") else "" 2235 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2236 2237 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2238 expressions = self.expressions(expression, flat=True) 2239 expressions = f"({expressions})" if expressions else "" 2240 return f"USING {self.sql(expression, 'this')}{expressions}" 2241 2242 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2243 default = expression.args.get("default") 2244 minimum = expression.args.get("minimum") 2245 maximum = expression.args.get("maximum") 2246 if default or minimum or maximum: 2247 if default: 2248 prop = "DEFAULT" 2249 elif minimum: 2250 prop = "MINIMUM" 2251 else: 2252 prop = "MAXIMUM" 2253 return f"{prop} DATABLOCKSIZE" 2254 units = expression.args.get("units") 2255 units = f" {units}" if units else "" 2256 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2257 2258 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2259 autotemp = expression.args.get("autotemp") 2260 always = expression.args.get("always") 2261 default = expression.args.get("default") 2262 manual = expression.args.get("manual") 2263 never = expression.args.get("never") 2264 2265 if autotemp is not None: 2266 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2267 elif always: 2268 prop = "ALWAYS" 2269 elif default: 2270 prop = "DEFAULT" 2271 elif manual: 2272 prop = "MANUAL" 2273 elif never: 2274 prop = "NEVER" 2275 return f"BLOCKCOMPRESSION={prop}" 2276 2277 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2278 no = expression.args.get("no") 2279 no = " NO" if no else "" 2280 concurrent = expression.args.get("concurrent") 2281 concurrent = " CONCURRENT" if concurrent else "" 2282 target = self.sql(expression, "target") 2283 target = f" {target}" if target else "" 2284 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2285 2286 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2287 if isinstance(expression.this, list): 2288 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2289 if expression.this: 2290 modulus = self.sql(expression, "this") 2291 remainder = self.sql(expression, "expression") 2292 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2293 2294 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2295 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2296 return f"FROM ({from_expressions}) TO ({to_expressions})" 2297 2298 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2299 this = self.sql(expression, "this") 2300 2301 for_values_or_default = expression.expression 2302 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2303 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2304 else: 2305 for_values_or_default = " DEFAULT" 2306 2307 return f"PARTITION OF {this}{for_values_or_default}" 2308 2309 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2310 kind = expression.args.get("kind") 2311 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2312 for_or_in = expression.args.get("for_or_in") 2313 for_or_in = f" {for_or_in}" if for_or_in else "" 2314 lock_type = expression.args.get("lock_type") 2315 override = " OVERRIDE" if expression.args.get("override") else "" 2316 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2317 2318 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2319 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2320 statistics = expression.args.get("statistics") 2321 statistics_sql = "" 2322 if statistics is not None: 2323 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2324 return f"{data_sql}{statistics_sql}" 2325 2326 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2327 this = self.sql(expression, "this") 2328 this = f"HISTORY_TABLE={this}" if this else "" 2329 data_consistency: str | None = self.sql(expression, "data_consistency") 2330 data_consistency = ( 2331 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2332 ) 2333 retention_period: str | None = self.sql(expression, "retention_period") 2334 retention_period = ( 2335 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2336 ) 2337 2338 if this: 2339 on_sql = self.func("ON", this, data_consistency, retention_period) 2340 else: 2341 on_sql = "ON" if expression.args.get("on") else "OFF" 2342 2343 sql = f"SYSTEM_VERSIONING={on_sql}" 2344 2345 return f"WITH({sql})" if expression.args.get("with_") else sql 2346 2347 def insert_sql(self, expression: exp.Insert) -> str: 2348 hint = self.sql(expression, "hint") 2349 overwrite = expression.args.get("overwrite") 2350 2351 if isinstance(expression.this, exp.Directory): 2352 this = " OVERWRITE" if overwrite else " INTO" 2353 else: 2354 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2355 2356 stored = self.sql(expression, "stored") 2357 stored = f" {stored}" if stored else "" 2358 alternative = expression.args.get("alternative") 2359 alternative = f" OR {alternative}" if alternative else "" 2360 ignore = " IGNORE" if expression.args.get("ignore") else "" 2361 is_function = expression.args.get("is_function") 2362 if is_function: 2363 this = f"{this} FUNCTION" 2364 this = f"{this} {self.sql(expression, 'this')}" 2365 2366 exists = " IF EXISTS" if expression.args.get("exists") else "" 2367 where = self.sql(expression, "where") 2368 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2369 using = self.expressions(expression, key="using", flat=True) 2370 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2371 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2372 on_conflict = self.sql(expression, "conflict") 2373 on_conflict = f" {on_conflict}" if on_conflict else "" 2374 by_name = " BY NAME" if expression.args.get("by_name") else "" 2375 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2376 returning = self.sql(expression, "returning") 2377 2378 if self.RETURNING_END: 2379 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2380 else: 2381 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2382 2383 partition_by = self.sql(expression, "partition") 2384 partition_by = f" {partition_by}" if partition_by else "" 2385 settings = self.sql(expression, "settings") 2386 settings = f" {settings}" if settings else "" 2387 2388 source = self.sql(expression, "source") 2389 source = f"TABLE {source}" if source else "" 2390 2391 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2392 return self.prepend_ctes(expression, sql) 2393 2394 def introducer_sql(self, expression: exp.Introducer) -> str: 2395 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2396 2397 def kill_sql(self, expression: exp.Kill) -> str: 2398 kind = self.sql(expression, "kind") 2399 kind = f" {kind}" if kind else "" 2400 this = self.sql(expression, "this") 2401 this = f" {this}" if this else "" 2402 return f"KILL{kind}{this}" 2403 2404 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2405 return expression.name 2406 2407 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2408 return expression.name 2409 2410 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2411 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2412 2413 constraint = self.sql(expression, "constraint") 2414 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2415 2416 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2417 if conflict_keys: 2418 conflict_keys = f"({conflict_keys})" 2419 2420 index_predicate = self.sql(expression, "index_predicate") 2421 conflict_keys = f"{conflict_keys}{index_predicate} " 2422 2423 action = self.sql(expression, "action") 2424 2425 expressions = self.expressions(expression, flat=True) 2426 if expressions: 2427 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2428 expressions = f" {set_keyword}{expressions}" 2429 2430 where = self.sql(expression, "where") 2431 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2432 2433 def returning_sql(self, expression: exp.Returning) -> str: 2434 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2435 2436 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2437 fields = self.sql(expression, "fields") 2438 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2439 escaped = self.sql(expression, "escaped") 2440 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2441 items = self.sql(expression, "collection_items") 2442 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2443 keys = self.sql(expression, "map_keys") 2444 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2445 lines = self.sql(expression, "lines") 2446 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2447 null = self.sql(expression, "null") 2448 null = f" NULL DEFINED AS {null}" if null else "" 2449 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2450 2451 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2452 return f"WITH ({self.expressions(expression, flat=True)})" 2453 2454 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2455 this = f"{self.sql(expression, 'this')} INDEX" 2456 target = self.sql(expression, "target") 2457 target = f" FOR {target}" if target else "" 2458 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2459 2460 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2461 this = self.sql(expression, "this") 2462 kind = self.sql(expression, "kind") 2463 expr = self.sql(expression, "expression") 2464 return f"{this} ({kind} => {expr})" 2465 2466 def table_parts(self, expression: exp.Table) -> str: 2467 return ".".join( 2468 self.sql(part) 2469 for part in ( 2470 expression.args.get("catalog"), 2471 expression.args.get("db"), 2472 expression.args.get("this"), 2473 ) 2474 if part is not None 2475 ) 2476 2477 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2478 table = self.table_parts(expression) 2479 only = "ONLY " if expression.args.get("only") else "" 2480 partition = self.sql(expression, "partition") 2481 partition = f" {partition}" if partition else "" 2482 version = self.sql(expression, "version") 2483 version = f" {version}" if version else "" 2484 alias = self.sql(expression, "alias") 2485 alias = f"{sep}{alias}" if alias else "" 2486 2487 sample = self.sql(expression, "sample") 2488 post_alias = "" 2489 pre_alias = "" 2490 2491 if self.dialect.ALIAS_POST_TABLESAMPLE: 2492 pre_alias = sample 2493 else: 2494 post_alias = sample 2495 2496 if self.dialect.ALIAS_POST_VERSION: 2497 pre_alias = f"{pre_alias}{version}" 2498 else: 2499 post_alias = f"{post_alias}{version}" 2500 2501 hints = self.expressions(expression, key="hints", sep=" ") 2502 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2503 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2504 joins = self.indent( 2505 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2506 ) 2507 laterals = self.expressions(expression, key="laterals", sep="") 2508 2509 file_format = self.sql(expression, "format") 2510 pattern = self.sql(expression, "pattern") 2511 if file_format: 2512 pattern = f", PATTERN => {pattern}" if pattern else "" 2513 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2514 elif pattern: 2515 file_format = f" (PATTERN => {pattern})" 2516 2517 ordinality = expression.args.get("ordinality") or "" 2518 if ordinality: 2519 ordinality = f" WITH ORDINALITY{alias}" 2520 alias = "" 2521 2522 when = self.sql(expression, "when") 2523 if when: 2524 if self.HISTORICAL_DATA_POST_ALIAS: 2525 alias = f"{alias} {when}" 2526 else: 2527 table = f"{table} {when}" 2528 2529 changes = self.sql(expression, "changes") 2530 changes = f" {changes}" if changes else "" 2531 2532 rows_from = self.expressions(expression, key="rows_from") 2533 if rows_from: 2534 table = f"ROWS FROM {self.wrap(rows_from)}" 2535 2536 indexed = expression.args.get("indexed") 2537 if indexed is not None: 2538 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2539 else: 2540 indexed = "" 2541 2542 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2543 2544 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2545 table = self.func("TABLE", expression.this) 2546 alias = self.sql(expression, "alias") 2547 alias = f" AS {alias}" if alias else "" 2548 sample = self.sql(expression, "sample") 2549 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2550 joins = self.indent( 2551 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2552 ) 2553 return f"{table}{alias}{pivots}{sample}{joins}" 2554 2555 def tablesample_sql( 2556 self, 2557 expression: exp.TableSample, 2558 tablesample_keyword: str | None = None, 2559 ) -> str: 2560 method = self.sql(expression, "method") 2561 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2562 numerator = self.sql(expression, "bucket_numerator") 2563 denominator = self.sql(expression, "bucket_denominator") 2564 field = self.sql(expression, "bucket_field") 2565 field = f" ON {field}" if field else "" 2566 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2567 seed = self.sql(expression, "seed") 2568 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2569 2570 size = self.sql(expression, "size") 2571 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2572 size = f"{size} ROWS" 2573 2574 percent = self.sql(expression, "percent") 2575 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2576 percent = f"{percent} PERCENT" 2577 2578 expr = f"{bucket}{percent}{size}" 2579 if self.TABLESAMPLE_REQUIRES_PARENS: 2580 expr = f"({expr})" 2581 2582 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2583 2584 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2585 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2586 # the stored column name differs from the target dialect's natural output. 2587 columns = expression.args.get("columns") 2588 if not columns or len(expression.fields) != 1: 2589 return None 2590 2591 args = expression.args 2592 parser_cls = self.dialect.parser_class 2593 2594 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2595 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2596 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2597 2598 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2599 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2600 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2601 2602 if ( 2603 src_identify_pivot_strings == tgt_identify_pivot_strings 2604 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2605 and src_pivot_column_naming == tgt_pivot_column_naming 2606 ): 2607 return None 2608 2609 in_exprs = expression.fields[0].expressions 2610 step = len(columns) // len(in_exprs) 2611 2612 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2613 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2614 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2615 first_stored = columns[0].name 2616 2617 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2618 if not first_stored.startswith(first_base): 2619 return None 2620 2621 suffix = first_stored[len(first_base) :] 2622 2623 # Whether the target dialect would append an agg-name suffix for this pivot. 2624 # Spark single-agg uniquely drops the agg alias entirely. 2625 target_has_suffix = ( 2626 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2627 ) and any(a.alias for a in expression.expressions) 2628 source_has_suffix = suffix != "" 2629 2630 new_exprs: list[exp.Expression] = [] 2631 modified = False 2632 for val_idx, e in enumerate(in_exprs): 2633 if isinstance(e, exp.PivotAlias): 2634 new_exprs.append(e) 2635 continue 2636 2637 i = val_idx * step 2638 stored_full = columns[i].name 2639 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2640 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2641 2642 # Source had a suffix, but target won't apply one 2643 if source_has_suffix and not target_has_suffix: 2644 new_exprs.append( 2645 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2646 ) 2647 modified = True 2648 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2649 elif stored_value != target_value: 2650 new_exprs.append( 2651 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2652 ) 2653 modified = True 2654 else: 2655 new_exprs.append(e) 2656 2657 return new_exprs if modified else None 2658 2659 def pivot_sql(self, expression: exp.Pivot) -> str: 2660 expressions = self.expressions(expression, flat=True) 2661 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2662 2663 group = self.sql(expression, "group") 2664 2665 if expression.this: 2666 this = self.sql(expression, "this") 2667 if not expressions: 2668 sql = f"UNPIVOT {this}" 2669 else: 2670 on = f"{self.seg('ON')} {expressions}" 2671 into = self.sql(expression, "into") 2672 into = f"{self.seg('INTO')} {into}" if into else "" 2673 using = self.expressions(expression, key="using", flat=True) 2674 using = f"{self.seg('USING')} {using}" if using else "" 2675 sql = f"{direction} {this}{on}{into}{using}{group}" 2676 return self.prepend_ctes(expression, sql) 2677 2678 if not expression.unpivot: 2679 # Wrap IN-list values with explicit aliases where the target dialect would differ 2680 new_field_exprs = self._pivot_in_value_aliases(expression) 2681 if new_field_exprs is not None: 2682 expression.fields[0].set("expressions", new_field_exprs) 2683 2684 alias = self.sql(expression, "alias") 2685 if alias: 2686 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2687 2688 fields = self.expressions( 2689 expression, 2690 "fields", 2691 sep=" ", 2692 dynamic=True, 2693 new_line=True, 2694 skip_first=True, 2695 skip_last=True, 2696 ) 2697 2698 include_nulls = expression.args.get("include_nulls") 2699 if include_nulls is not None: 2700 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2701 else: 2702 nulls = "" 2703 2704 default_on_null = self.sql(expression, "default_on_null") 2705 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2706 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2707 return self.prepend_ctes(expression, sql) 2708 2709 def version_sql(self, expression: exp.Version) -> str: 2710 this = f"FOR {expression.name}" 2711 kind = expression.text("kind") 2712 expr = self.sql(expression, "expression") 2713 return f"{this} {kind} {expr}" 2714 2715 def tuple_sql(self, expression: exp.Tuple) -> str: 2716 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2717 2718 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2719 """ 2720 Returns (join_sql, from_sql) for UPDATE statements. 2721 - join_sql: placed after UPDATE table, before SET 2722 - from_sql: placed after SET clause (standard position) 2723 Dialects like MySQL need to convert FROM to JOIN syntax. 2724 """ 2725 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2726 return ("", self.sql(expression, "from_")) 2727 2728 # Qualify unqualified columns in SET clause with the target table 2729 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2730 target_table = expression.this 2731 if isinstance(target_table, exp.Table): 2732 target_name = exp.to_identifier(target_table.alias_or_name) 2733 for eq in expression.expressions: 2734 col = eq.this 2735 if isinstance(col, exp.Column) and not col.table: 2736 col.set("table", target_name) 2737 2738 table = from_expr.this 2739 if nested_joins := table.args.get("joins", []): 2740 table.set("joins", None) 2741 2742 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2743 for nested in nested_joins: 2744 if not nested.args.get("on") and not nested.args.get("using"): 2745 nested.set("on", exp.true()) 2746 join_sql += self.sql(nested) 2747 2748 return (join_sql, "") 2749 2750 def update_sql(self, expression: exp.Update) -> str: 2751 hint = self.sql(expression, "hint") 2752 this = self.sql(expression, "this") 2753 join_sql, from_sql = self._update_from_joins_sql(expression) 2754 set_sql = self.expressions(expression, flat=True) 2755 where_sql = self.sql(expression, "where") 2756 returning = self.sql(expression, "returning") 2757 order = self.sql(expression, "order") 2758 limit = self.sql(expression, "limit") 2759 if self.RETURNING_END: 2760 expression_sql = f"{from_sql}{where_sql}{returning}" 2761 else: 2762 expression_sql = f"{returning}{from_sql}{where_sql}" 2763 options = self.expressions(expression, key="options") 2764 options = f" OPTION({options})" if options else "" 2765 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2766 return self.prepend_ctes(expression, sql) 2767 2768 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2769 values_as_table = values_as_table and self.VALUES_AS_TABLE 2770 2771 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2772 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2773 args = self.expressions(expression) 2774 alias = self.sql(expression, "alias") 2775 values = f"VALUES{self.seg('')}{args}" 2776 values = ( 2777 f"({values})" 2778 if self.WRAP_DERIVED_VALUES 2779 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2780 else values 2781 ) 2782 values = self.query_modifiers(expression, values) 2783 return f"{values} AS {alias}" if alias else values 2784 2785 # Converts `VALUES...` expression into a series of select unions. 2786 alias_node = expression.args.get("alias") 2787 column_names = alias_node and alias_node.columns 2788 2789 selects: list[exp.Query] = [] 2790 2791 for i, tup in enumerate(expression.expressions): 2792 row = tup.expressions 2793 2794 if i == 0 and column_names: 2795 row = [ 2796 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2797 ] 2798 2799 selects.append(exp.Select(expressions=row)) 2800 2801 if self.pretty: 2802 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2803 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2804 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2805 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2806 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2807 2808 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2809 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2810 return f"({unions}){alias}" 2811 2812 def var_sql(self, expression: exp.Var) -> str: 2813 return self.sql(expression, "this") 2814 2815 @unsupported_args("expressions") 2816 def into_sql(self, expression: exp.Into) -> str: 2817 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2818 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2819 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2820 2821 def from_sql(self, expression: exp.From) -> str: 2822 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2823 2824 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2825 grouping_sets = self.expressions(expression, indent=False) 2826 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2827 2828 def rollup_sql(self, expression: exp.Rollup) -> str: 2829 expressions = self.expressions(expression, indent=False) 2830 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2831 2832 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2833 this = self.sql(expression, "this") 2834 2835 columns = self.expressions(expression, flat=True) 2836 2837 from_sql = self.sql(expression, "from_index") 2838 from_sql = f" FROM {from_sql}" if from_sql else "" 2839 2840 properties = expression.args.get("properties") 2841 properties_sql = ( 2842 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2843 ) 2844 2845 return f"{this}({columns}){from_sql}{properties_sql}" 2846 2847 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2848 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2849 2850 def cube_sql(self, expression: exp.Cube) -> str: 2851 expressions = self.expressions(expression, indent=False) 2852 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2853 2854 def group_sql(self, expression: exp.Group) -> str: 2855 group_by_all = expression.args.get("all") 2856 if group_by_all is True: 2857 modifier = " ALL" 2858 elif group_by_all is False: 2859 modifier = " DISTINCT" 2860 else: 2861 modifier = "" 2862 2863 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2864 2865 grouping_sets = self.expressions(expression, key="grouping_sets") 2866 cube = self.expressions(expression, key="cube") 2867 rollup = self.expressions(expression, key="rollup") 2868 2869 groupings = csv( 2870 self.seg(grouping_sets) if grouping_sets else "", 2871 self.seg(cube) if cube else "", 2872 self.seg(rollup) if rollup else "", 2873 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2874 sep=self.GROUPINGS_SEP, 2875 ) 2876 2877 if ( 2878 expression.expressions 2879 and groupings 2880 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2881 ): 2882 add_separator = True 2883 2884 if grouping_sets: 2885 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2886 add_separator = False 2887 else: 2888 self.unsupported( 2889 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2890 ) 2891 2892 if add_separator: 2893 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2894 2895 return f"{group_by}{groupings}" 2896 2897 def having_sql(self, expression: exp.Having) -> str: 2898 this = self.indent(self.sql(expression, "this")) 2899 return f"{self.seg('HAVING')}{self.sep()}{this}" 2900 2901 def connect_sql(self, expression: exp.Connect) -> str: 2902 start = self.sql(expression, "start") 2903 start = self.seg(f"START WITH {start}") if start else "" 2904 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2905 connect = self.sql(expression, "connect") 2906 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2907 return start + connect 2908 2909 def prior_sql(self, expression: exp.Prior) -> str: 2910 return f"PRIOR {self.sql(expression, 'this')}" 2911 2912 def join_sql(self, expression: exp.Join) -> str: 2913 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2914 side = None 2915 else: 2916 side = expression.side 2917 2918 op_sql = " ".join( 2919 op 2920 for op in ( 2921 expression.method, 2922 "GLOBAL" if expression.args.get("global_") else None, 2923 side, 2924 expression.kind, 2925 expression.hint if self.JOIN_HINTS else None, 2926 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2927 ) 2928 if op 2929 ) 2930 match_cond = self.sql(expression, "match_condition") 2931 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2932 on_sql = self.sql(expression, "on") 2933 using = expression.args.get("using") 2934 2935 if not on_sql and using: 2936 on_sql = csv(*(self.sql(column) for column in using)) 2937 2938 this = expression.this 2939 this_sql = self.sql(this) 2940 2941 exprs = self.expressions(expression) 2942 if exprs: 2943 this_sql = f"{this_sql},{self.seg(exprs)}" 2944 2945 if on_sql: 2946 on_sql = self.indent(on_sql, skip_first=True) 2947 space = self.seg(" " * self.pad) if self.pretty else " " 2948 if using: 2949 on_sql = f"{space}USING ({on_sql})" 2950 else: 2951 on_sql = f"{space}ON {on_sql}" 2952 elif not op_sql: 2953 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2954 return f" {this_sql}" 2955 2956 return f", {this_sql}" 2957 2958 if op_sql != "STRAIGHT_JOIN": 2959 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2960 2961 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2962 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2963 2964 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2965 args = self.expressions(expression, flat=True) 2966 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2967 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2968 2969 def lateral_op(self, expression: exp.Lateral) -> str: 2970 cross_apply = expression.args.get("cross_apply") 2971 2972 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2973 if cross_apply is True: 2974 op = "INNER JOIN " 2975 elif cross_apply is False: 2976 op = "LEFT JOIN " 2977 else: 2978 op = "" 2979 2980 return f"{op}LATERAL" 2981 2982 def lateral_sql(self, expression: exp.Lateral) -> str: 2983 this = self.sql(expression, "this") 2984 2985 if expression.args.get("view"): 2986 alias = expression.args["alias"] 2987 columns = self.expressions(alias, key="columns", flat=True) 2988 table = f" {alias.name}" if alias.name else "" 2989 columns = f" AS {columns}" if columns else "" 2990 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2991 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2992 2993 table_alias = expression.args.get("alias") 2994 offset = expression.this.args.get("offset") 2995 2996 if ( 2997 self.UNNEST_WITH_ORDINALITY 2998 and table_alias 2999 and isinstance(expression.this, exp.Unnest) 3000 and isinstance(offset, exp.Identifier) 3001 ): 3002 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3003 table_alias = table_alias.copy() 3004 table_alias.append("columns", offset.copy()) 3005 3006 alias = self.sql(table_alias) 3007 alias = f" AS {alias}" if alias else "" 3008 3009 ordinality = expression.args.get("ordinality") or "" 3010 if ordinality: 3011 ordinality = f" WITH ORDINALITY{alias}" 3012 alias = "" 3013 3014 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3015 3016 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3017 this = self.sql(expression, "this") 3018 3019 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3020 return this 3021 3022 args = [ 3023 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3024 for e in (expression.args.get(k) for k in ("offset", "expression")) 3025 if e 3026 ] 3027 3028 args_sql = ", ".join(self.sql(e) for e in args) 3029 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3030 expressions = self.expressions(expression, flat=True) 3031 limit_options = self.sql(expression, "limit_options") 3032 expressions = f" BY {expressions}" if expressions else "" 3033 3034 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3035 3036 def offset_sql(self, expression: exp.Offset) -> str: 3037 this = self.sql(expression, "this") 3038 value = expression.expression 3039 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3040 expressions = self.expressions(expression, flat=True) 3041 expressions = f" BY {expressions}" if expressions else "" 3042 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3043 3044 def setitem_sql(self, expression: exp.SetItem) -> str: 3045 kind = self.sql(expression, "kind") 3046 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3047 kind = "" 3048 else: 3049 kind = f"{kind} " if kind else "" 3050 this = self.sql(expression, "this") 3051 expressions = self.expressions(expression) 3052 collate = self.sql(expression, "collate") 3053 collate = f" COLLATE {collate}" if collate else "" 3054 global_ = "GLOBAL " if expression.args.get("global_") else "" 3055 return f"{global_}{kind}{this}{expressions}{collate}" 3056 3057 def set_sql(self, expression: exp.Set) -> str: 3058 expressions = f" {self.expressions(expression, flat=True)}" 3059 tag = " TAG" if expression.args.get("tag") else "" 3060 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3061 3062 def queryband_sql(self, expression: exp.QueryBand) -> str: 3063 this = self.sql(expression, "this") 3064 update = " UPDATE" if expression.args.get("update") else "" 3065 scope = self.sql(expression, "scope") 3066 scope = f" FOR {scope}" if scope else "" 3067 3068 return f"QUERY_BAND = {this}{update}{scope}" 3069 3070 def pragma_sql(self, expression: exp.Pragma) -> str: 3071 return f"PRAGMA {self.sql(expression, 'this')}" 3072 3073 def lock_sql(self, expression: exp.Lock) -> str: 3074 if not self.LOCKING_READS_SUPPORTED: 3075 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3076 return "" 3077 3078 update = expression.args["update"] 3079 key = expression.args.get("key") 3080 if update: 3081 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3082 else: 3083 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3084 expressions = self.expressions(expression, flat=True) 3085 expressions = f" OF {expressions}" if expressions else "" 3086 wait = expression.args.get("wait") 3087 3088 if wait is not None: 3089 if isinstance(wait, exp.Literal): 3090 wait = f" WAIT {self.sql(wait)}" 3091 else: 3092 wait = " NOWAIT" if wait else " SKIP LOCKED" 3093 3094 return f"{lock_type}{expressions}{wait or ''}" 3095 3096 def literal_sql(self, expression: exp.Literal) -> str: 3097 text = expression.this or "" 3098 if expression.is_string: 3099 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3100 return text 3101 3102 def escape_str( 3103 self, 3104 text: str, 3105 escape_backslash: bool = True, 3106 delimiter: str | None = None, 3107 escaped_delimiter: str | None = None, 3108 is_byte_string: bool = False, 3109 is_bytes: bool = False, 3110 ) -> str: 3111 if is_byte_string: 3112 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3113 else: 3114 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3115 3116 if supports_escape_sequences: 3117 text = "".join( 3118 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3119 for ch in text 3120 ) 3121 3122 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3123 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3124 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3125 3126 delimiter = delimiter or self.dialect.QUOTE_END 3127 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3128 3129 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3130 3131 def loaddata_sql(self, expression: exp.LoadData) -> str: 3132 is_overwrite = expression.args.get("overwrite") 3133 overwrite = " OVERWRITE" if is_overwrite else "" 3134 this = self.sql(expression, "this") 3135 3136 files = expression.args.get("files") 3137 if files: 3138 files_sql = self.expressions(files, flat=True) 3139 files_sql = f"FILES{self.wrap(files_sql)}" 3140 if is_overwrite: 3141 this = f" {this}" 3142 elif expression.args.get("temp"): 3143 this = f" INTO TEMP TABLE {this}" 3144 else: 3145 this = f" INTO TABLE {this}" 3146 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3147 3148 local = " LOCAL" if expression.args.get("local") else "" 3149 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3150 this = f" INTO TABLE {this}" 3151 partition = self.sql(expression, "partition") 3152 partition = f" {partition}" if partition else "" 3153 input_format = self.sql(expression, "input_format") 3154 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3155 serde = self.sql(expression, "serde") 3156 serde = f" SERDE {serde}" if serde else "" 3157 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3158 3159 def null_sql(self, *_) -> str: 3160 return "NULL" 3161 3162 def boolean_sql(self, expression: exp.Boolean) -> str: 3163 return "TRUE" if expression.this else "FALSE" 3164 3165 def booland_sql(self, expression: exp.Booland) -> str: 3166 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3167 3168 def boolor_sql(self, expression: exp.Boolor) -> str: 3169 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3170 3171 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3172 this = self.sql(expression, "this") 3173 this = f"{this} " if this else this 3174 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3175 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3176 3177 def withfill_sql(self, expression: exp.WithFill) -> str: 3178 from_sql = self.sql(expression, "from_") 3179 from_sql = f" FROM {from_sql}" if from_sql else "" 3180 to_sql = self.sql(expression, "to") 3181 to_sql = f" TO {to_sql}" if to_sql else "" 3182 step_sql = self.sql(expression, "step") 3183 step_sql = f" STEP {step_sql}" if step_sql else "" 3184 interpolated_values = [ 3185 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3186 if isinstance(e, exp.Alias) 3187 else self.sql(e, "this") 3188 for e in expression.args.get("interpolate") or [] 3189 ] 3190 interpolate = ( 3191 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3192 ) 3193 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3194 3195 def cluster_sql(self, expression: exp.Cluster) -> str: 3196 return self.op_expressions("CLUSTER BY", expression) 3197 3198 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3199 if expression.this: 3200 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3201 return "" 3202 expressions = self.expressions(expression, flat=True) 3203 return f"CLUSTER BY ({expressions})" 3204 3205 def distribute_sql(self, expression: exp.Distribute) -> str: 3206 return self.op_expressions("DISTRIBUTE BY", expression) 3207 3208 def sort_sql(self, expression: exp.Sort) -> str: 3209 return self.op_expressions("SORT BY", expression) 3210 3211 def _resolve_ordered_for_null_ordering_simulation( 3212 self, expression: exp.Ordered 3213 ) -> exp.Expr | None: 3214 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3215 3216 Returns the underlying expression of the uniquely-matching projection 3217 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3218 simulation, since the CASE is evaluated in FROM-clause scope rather 3219 than alias scope (MySQL error 1052). Returns None if no safe 3220 substitution applies, leaving the original behaviour unchanged. 3221 """ 3222 this = expression.this 3223 if not (isinstance(this, exp.Column) and not this.table): 3224 return None 3225 3226 # Only a query's own ORDER BY can reference its projection aliases 3227 order = expression.parent 3228 ancestor = order.parent if isinstance(order, exp.Order) else None 3229 if not isinstance(ancestor, exp.Select): 3230 return None 3231 3232 column_name = this.name 3233 matched: list[exp.Expr] = [ 3234 p.this if isinstance(p, exp.Alias) else p 3235 for p in ancestor.selects 3236 if p.output_name == column_name 3237 ] 3238 match = matched[0] if len(matched) == 1 else None 3239 3240 # Skip the substitution when it would be identical to the existing 3241 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3242 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3243 return None 3244 3245 return match 3246 3247 def ordered_sql(self, expression: exp.Ordered) -> str: 3248 desc = expression.args.get("desc") 3249 asc = not desc 3250 3251 nulls_first = expression.args.get("nulls_first") 3252 nulls_last = not nulls_first 3253 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3254 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3255 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3256 3257 this = self.sql(expression, "this") 3258 3259 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3260 nulls_sort_change = "" 3261 if nulls_first and ( 3262 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3263 ): 3264 nulls_sort_change = " NULLS FIRST" 3265 elif ( 3266 nulls_last 3267 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3268 and not nulls_are_last 3269 ): 3270 nulls_sort_change = " NULLS LAST" 3271 3272 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3273 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3274 window = expression.find_ancestor(exp.Window, exp.Select) 3275 3276 if isinstance(window, exp.Window): 3277 window_this = window.this 3278 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3279 window_this = window_this.this 3280 spec = window.args.get("spec") 3281 else: 3282 window_this = None 3283 spec = None 3284 3285 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3286 # without a spec or with a ROWS spec, but not with RANGE 3287 if not ( 3288 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3289 and (not spec or spec.text("kind").upper() == "ROWS") 3290 ): 3291 if window_this and spec: 3292 self.unsupported( 3293 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3294 ) 3295 nulls_sort_change = "" 3296 elif self.NULL_ORDERING_SUPPORTED is False and ( 3297 (asc and nulls_sort_change == " NULLS LAST") 3298 or (desc and nulls_sort_change == " NULLS FIRST") 3299 ): 3300 # BigQuery does not allow these ordering/nulls combinations when used under 3301 # an aggregation func or under a window containing one 3302 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3303 3304 if isinstance(ancestor, exp.Window): 3305 ancestor = ancestor.this 3306 if isinstance(ancestor, exp.AggFunc): 3307 self.unsupported( 3308 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3309 ) 3310 nulls_sort_change = "" 3311 elif self.NULL_ORDERING_SUPPORTED is None: 3312 if expression.this.is_int: 3313 self.unsupported( 3314 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3315 ) 3316 elif not isinstance(expression.this, exp.Rand): 3317 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3318 target = self.sql(resolved) if resolved is not None else this 3319 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3320 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3321 nulls_sort_change = "" 3322 3323 with_fill = self.sql(expression, "with_fill") 3324 with_fill = f" {with_fill}" if with_fill else "" 3325 3326 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3327 3328 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3329 window_frame = self.sql(expression, "window_frame") 3330 window_frame = f"{window_frame} " if window_frame else "" 3331 3332 this = self.sql(expression, "this") 3333 3334 return f"{window_frame}{this}" 3335 3336 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3337 partition = self.partition_by_sql(expression) 3338 order = self.sql(expression, "order") 3339 measures = self.expressions(expression, key="measures") 3340 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3341 rows = self.sql(expression, "rows") 3342 rows = self.seg(rows) if rows else "" 3343 after = self.sql(expression, "after") 3344 after = self.seg(after) if after else "" 3345 pattern = self.sql(expression, "pattern") 3346 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3347 definition_sqls = [ 3348 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3349 for definition in expression.args.get("define", []) 3350 ] 3351 definitions = self.expressions(sqls=definition_sqls) 3352 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3353 body = "".join( 3354 ( 3355 partition, 3356 order, 3357 measures, 3358 rows, 3359 after, 3360 pattern, 3361 define, 3362 ) 3363 ) 3364 alias = self.sql(expression, "alias") 3365 alias = f" {alias}" if alias else "" 3366 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3367 3368 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3369 limit = expression.args.get("limit") 3370 3371 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3372 count = limit.args.get("count") 3373 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3374 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3375 limit = exp.Limit( 3376 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3377 ) 3378 elif ( 3379 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3380 ): 3381 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3382 3383 return csv( 3384 *sqls, 3385 *[self.sql(join) for join in expression.args.get("joins") or []], 3386 self.sql(expression, "match"), 3387 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3388 self.sql(expression, "prewhere"), 3389 self.sql(expression, "where"), 3390 self.sql(expression, "connect"), 3391 self.sql(expression, "group"), 3392 self.sql(expression, "having"), 3393 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3394 self.sql(expression, "order"), 3395 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3396 *self.after_limit_modifiers(expression), 3397 self.sql(expression, "for_"), 3398 self.options_modifier(expression), 3399 sep="", 3400 ) 3401 3402 def options_modifier(self, expression: exp.Expr) -> str: 3403 options = self.expressions(expression, key="options") 3404 return f" {options}" if options else "" 3405 3406 def forclause_sql(self, expression: exp.ForClause) -> str: 3407 kind = expression.args["kind"] 3408 if kind == "BROWSE": 3409 return f"{self.sep()}FOR BROWSE" 3410 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3411 # the target dialect doesn't support QueryOption, so we drop the clause. 3412 options = self.expressions(expression, key="expressions") 3413 if not options: 3414 return "" 3415 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3416 3417 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3418 self.unsupported("Unsupported query option.") 3419 return "" 3420 3421 def offset_limit_modifiers( 3422 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3423 ) -> list[str]: 3424 return [ 3425 self.sql(expression, "offset") if fetch else self.sql(limit), 3426 self.sql(limit) if fetch else self.sql(expression, "offset"), 3427 ] 3428 3429 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3430 locks = self.expressions(expression, key="locks", sep=" ") 3431 locks = f" {locks}" if locks else "" 3432 return [locks, self.sql(expression, "sample")] 3433 3434 def select_sql(self, expression: exp.Select) -> str: 3435 into = expression.args.get("into") 3436 if not self.SUPPORTS_SELECT_INTO and into: 3437 into.pop() 3438 3439 hint = self.sql(expression, "hint") 3440 distinct = self.sql(expression, "distinct") 3441 distinct = f" {distinct}" if distinct else "" 3442 kind = self.sql(expression, "kind") 3443 3444 limit = expression.args.get("limit") 3445 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3446 top = self.limit_sql(limit, top=True) 3447 limit.pop() 3448 else: 3449 top = "" 3450 3451 expressions = self.expressions(expression) 3452 3453 if kind: 3454 if kind in self.SELECT_KINDS: 3455 kind = f" AS {kind}" 3456 else: 3457 if kind == "STRUCT": 3458 expressions = self.expressions( 3459 sqls=[ 3460 self.sql( 3461 exp.Struct( 3462 expressions=[ 3463 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3464 if isinstance(e, exp.Alias) 3465 else e 3466 for e in expression.expressions 3467 ] 3468 ) 3469 ) 3470 ] 3471 ) 3472 kind = "" 3473 3474 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3475 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3476 3477 exclude = expression.args.get("exclude") 3478 3479 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3480 exclude_sql = self.expressions(sqls=exclude, flat=True) 3481 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3482 3483 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3484 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3485 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3486 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3487 sql = self.query_modifiers( 3488 expression, 3489 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3490 self.sql(expression, "into", comment=False), 3491 self.sql(expression, "from_", comment=False), 3492 ) 3493 3494 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3495 if expression.args.get("with_"): 3496 sql = self.maybe_comment(sql, expression) 3497 expression.pop_comments() 3498 3499 sql = self.prepend_ctes(expression, sql) 3500 3501 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3502 expression.set("exclude", None) 3503 subquery = expression.subquery(copy=False) 3504 star = exp.Star(except_=exclude) 3505 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3506 3507 if not self.SUPPORTS_SELECT_INTO and into: 3508 if into.args.get("temporary"): 3509 table_kind = " TEMPORARY" 3510 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3511 table_kind = " UNLOGGED" 3512 else: 3513 table_kind = "" 3514 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3515 3516 return sql 3517 3518 def schema_sql(self, expression: exp.Schema) -> str: 3519 this = self.sql(expression, "this") 3520 sql = self.schema_columns_sql(expression) 3521 return f"{this} {sql}" if this and sql else this or sql 3522 3523 def schema_columns_sql(self, expression: exp.Expr) -> str: 3524 if expression.expressions: 3525 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3526 return "" 3527 3528 def star_sql(self, expression: exp.Star) -> str: 3529 except_ = self.expressions(expression, key="except_", flat=True) 3530 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3531 replace = self.expressions(expression, key="replace", flat=True) 3532 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3533 rename = self.expressions(expression, key="rename", flat=True) 3534 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3535 ilike = self.sql(expression, "ilike") 3536 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3537 return f"*{ilike}{except_}{replace}{rename}" 3538 3539 def parameter_sql(self, expression: exp.Parameter) -> str: 3540 this = self.sql(expression, "this") 3541 return f"{self.PARAMETER_TOKEN}{this}" 3542 3543 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3544 this = self.sql(expression, "this") 3545 kind = expression.text("kind") 3546 if kind: 3547 kind = f"{kind}." 3548 return f"@@{kind}{this}" 3549 3550 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3551 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3552 3553 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3554 alias = self.sql(expression, "alias") 3555 alias = f"{sep}{alias}" if alias else "" 3556 sample = self.sql(expression, "sample") 3557 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3558 alias = f"{sample}{alias}" 3559 3560 # Set to None so it's not generated again by self.query_modifiers() 3561 expression.set("sample", None) 3562 3563 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3564 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3565 return self.prepend_ctes(expression, sql) 3566 3567 def qualify_sql(self, expression: exp.Qualify) -> str: 3568 this = self.indent(self.sql(expression, "this")) 3569 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3570 3571 def unnest_sql(self, expression: exp.Unnest) -> str: 3572 args = self.expressions(expression, flat=True) 3573 3574 alias = expression.args.get("alias") 3575 offset = expression.args.get("offset") 3576 3577 if self.UNNEST_WITH_ORDINALITY: 3578 if alias and isinstance(offset, exp.Expr): 3579 alias.append("columns", offset) 3580 expression.set("offset", None) 3581 3582 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3583 columns = alias.columns 3584 alias = self.sql(columns[0]) if columns else "" 3585 else: 3586 alias = self.sql(alias) 3587 3588 alias = f" AS {alias}" if alias else alias 3589 if self.UNNEST_WITH_ORDINALITY: 3590 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3591 else: 3592 if isinstance(offset, exp.Expr): 3593 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3594 elif offset: 3595 suffix = f"{alias} WITH OFFSET" 3596 else: 3597 suffix = alias 3598 3599 return f"UNNEST({args}){suffix}" 3600 3601 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3602 return "" 3603 3604 def where_sql(self, expression: exp.Where) -> str: 3605 this = self.indent(self.sql(expression, "this")) 3606 return f"{self.seg('WHERE')}{self.sep()}{this}" 3607 3608 def window_sql(self, expression: exp.Window) -> str: 3609 this = self.sql(expression, "this") 3610 partition = self.partition_by_sql(expression) 3611 order = expression.args.get("order") 3612 order = self.order_sql(order, flat=True) if order else "" 3613 spec = self.sql(expression, "spec") 3614 alias = self.sql(expression, "alias") 3615 over = self.sql(expression, "over") or "OVER" 3616 3617 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3618 3619 first = expression.args.get("first") 3620 if first is None: 3621 first = "" 3622 else: 3623 first = "FIRST" if first else "LAST" 3624 3625 if not partition and not order and not spec and alias: 3626 return f"{this} {alias}" 3627 3628 args = self.format_args( 3629 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3630 ) 3631 return f"{this} ({args})" 3632 3633 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3634 partition = self.expressions(expression, key="partition_by", flat=True) 3635 return f"PARTITION BY {partition}" if partition else "" 3636 3637 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3638 kind = self.sql(expression, "kind") 3639 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3640 end = ( 3641 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3642 or "CURRENT ROW" 3643 ) 3644 3645 window_spec = f"{kind} BETWEEN {start} AND {end}" 3646 3647 exclude = self.sql(expression, "exclude") 3648 if exclude: 3649 if self.SUPPORTS_WINDOW_EXCLUDE: 3650 window_spec += f" EXCLUDE {exclude}" 3651 else: 3652 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3653 3654 return window_spec 3655 3656 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3657 this = self.sql(expression, "this") 3658 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3659 return f"{this} WITHIN GROUP ({expression_sql})" 3660 3661 def between_sql(self, expression: exp.Between) -> str: 3662 this = self.sql(expression, "this") 3663 low = self.sql(expression, "low") 3664 high = self.sql(expression, "high") 3665 symmetric = expression.args.get("symmetric") 3666 3667 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3668 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3669 3670 flag = ( 3671 " SYMMETRIC" 3672 if symmetric 3673 else " ASYMMETRIC" 3674 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3675 else "" # silently drop ASYMMETRIC – semantics identical 3676 ) 3677 return f"{this} BETWEEN{flag} {low} AND {high}" 3678 3679 def bracket_offset_expressions( 3680 self, expression: exp.Bracket, index_offset: int | None = None 3681 ) -> list[exp.Expr]: 3682 if expression.args.get("json_access"): 3683 return expression.expressions 3684 3685 return apply_index_offset( 3686 expression.this, 3687 expression.expressions, 3688 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3689 dialect=self.dialect, 3690 ) 3691 3692 def bracket_sql(self, expression: exp.Bracket) -> str: 3693 expressions = self.bracket_offset_expressions(expression) 3694 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3695 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3696 3697 def all_sql(self, expression: exp.All) -> str: 3698 this = self.sql(expression, "this") 3699 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3700 this = self.wrap(this) 3701 return f"ALL {this}" 3702 3703 def any_sql(self, expression: exp.Any) -> str: 3704 this = self.sql(expression, "this") 3705 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3706 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3707 this = self.wrap(this) 3708 return f"ANY{this}" 3709 return f"ANY {this}" 3710 3711 def exists_sql(self, expression: exp.Exists) -> str: 3712 return f"EXISTS{self.wrap(expression)}" 3713 3714 def case_sql(self, expression: exp.Case) -> str: 3715 this = self.sql(expression, "this") 3716 statements = [f"CASE {this}" if this else "CASE"] 3717 3718 for e in expression.args["ifs"]: 3719 statements.append(f"WHEN {self.sql(e, 'this')}") 3720 statements.append(f"THEN {self.sql(e, 'true')}") 3721 3722 default = self.sql(expression, "default") 3723 3724 if default: 3725 statements.append(f"ELSE {default}") 3726 3727 statements.append("END") 3728 3729 if self.pretty and self.too_wide(statements): 3730 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3731 3732 return " ".join(statements) 3733 3734 def constraint_sql(self, expression: exp.Constraint) -> str: 3735 this = self.sql(expression, "this") 3736 expressions = self.expressions(expression, flat=True) 3737 return f"CONSTRAINT {this} {expressions}" 3738 3739 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3740 order = expression.args.get("order") 3741 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3742 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3743 3744 def extract_sql(self, expression: exp.Extract) -> str: 3745 import sqlglot.dialects.dialect 3746 3747 this = ( 3748 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3749 if self.NORMALIZE_EXTRACT_DATE_PARTS 3750 else expression.this 3751 ) 3752 if self.EXTRACT_ALLOWS_QUOTES: 3753 this_sql = self.sql(this) 3754 elif isinstance(this, exp.WeekStart): 3755 this_sql = self.weekstart_name(this) 3756 else: 3757 this_sql = this.name 3758 expression_sql = self.sql(expression, "expression") 3759 3760 return f"EXTRACT({this_sql} FROM {expression_sql})" 3761 3762 def trim_sql(self, expression: exp.Trim) -> str: 3763 trim_type = self.sql(expression, "position") 3764 3765 if trim_type == "LEADING": 3766 func_name = "LTRIM" 3767 elif trim_type == "TRAILING": 3768 func_name = "RTRIM" 3769 else: 3770 func_name = "TRIM" 3771 3772 return self.func(func_name, expression.this, expression.expression) 3773 3774 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3775 args = expression.expressions 3776 if isinstance(expression, exp.ConcatWs): 3777 args = args[1:] # Skip the delimiter 3778 3779 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3780 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3781 3782 concat_coalesce = ( 3783 self.dialect.CONCAT_WS_COALESCE 3784 if isinstance(expression, exp.ConcatWs) 3785 else self.dialect.CONCAT_COALESCE 3786 ) 3787 3788 if not concat_coalesce and expression.args.get("coalesce"): 3789 3790 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3791 if not e.type: 3792 import sqlglot.optimizer.annotate_types 3793 3794 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3795 3796 if e.is_string or e.is_type(exp.DType.ARRAY): 3797 return e 3798 3799 return exp.func("coalesce", e, exp.Literal.string("")) 3800 3801 args = [_wrap_with_coalesce(e) for e in args] 3802 3803 return args 3804 3805 def concat_sql(self, expression: exp.Concat) -> str: 3806 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3807 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3808 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3809 # instead of coalescing them to empty string. 3810 import sqlglot.dialects.dialect 3811 3812 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3813 3814 expressions = self.convert_concat_args(expression) 3815 3816 # Some dialects don't allow a single-argument CONCAT call 3817 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3818 return self.sql(expressions[0]) 3819 3820 return self.func("CONCAT", *expressions) 3821 3822 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3823 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3824 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3825 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3826 all_args = expression.expressions 3827 expression.set("coalesce", True) 3828 return self.sql( 3829 exp.case() 3830 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3831 .else_(expression) 3832 ) 3833 3834 return self.func( 3835 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3836 ) 3837 3838 def check_sql(self, expression: exp.Check) -> str: 3839 this = self.sql(expression, key="this") 3840 return f"CHECK ({this})" 3841 3842 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3843 expressions = self.expressions(expression, flat=True) 3844 expressions = f" ({expressions})" if expressions else "" 3845 reference = self.sql(expression, "reference") 3846 reference = f" {reference}" if reference else "" 3847 delete = self.sql(expression, "delete") 3848 delete = f" ON DELETE {delete}" if delete else "" 3849 update = self.sql(expression, "update") 3850 update = f" ON UPDATE {update}" if update else "" 3851 options = self.expressions(expression, key="options", flat=True, sep=" ") 3852 options = f" {options}" if options else "" 3853 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3854 3855 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3856 this = self.sql(expression, "this") 3857 this = f" {this}" if this else "" 3858 expressions = self.expressions(expression, flat=True) 3859 include = self.sql(expression, "include") 3860 options = self.expressions(expression, key="options", flat=True, sep=" ") 3861 options = f" {options}" if options else "" 3862 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3863 3864 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3865 self.unsupported("TIMESERIES primary key columns are not supported") 3866 return self.sql(expression, "this") 3867 3868 def if_sql(self, expression: exp.If) -> str: 3869 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3870 3871 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3872 if self.MATCH_AGAINST_TABLE_PREFIX: 3873 expressions = [] 3874 for expr in expression.expressions: 3875 if isinstance(expr, exp.Table): 3876 expressions.append(f"TABLE {self.sql(expr)}") 3877 else: 3878 expressions.append(expr) 3879 else: 3880 expressions = expression.expressions 3881 3882 modifier = expression.args.get("modifier") 3883 modifier = f" {modifier}" if modifier else "" 3884 return ( 3885 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3886 ) 3887 3888 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3889 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3890 3891 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3892 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3893 3894 if self.QUOTE_JSON_PATH: 3895 path = self.escape_str(path) 3896 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3897 3898 return path 3899 3900 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3901 if isinstance(expression, exp.JSONPathPart): 3902 transform = self.TRANSFORMS.get(expression.__class__) 3903 if not callable(transform): 3904 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3905 return "" 3906 3907 return transform(self, expression) 3908 3909 if isinstance(expression, int): 3910 return str(expression) 3911 3912 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3913 escaped = expression.replace("'", "\\'") 3914 escaped = f"'{escaped}'" 3915 else: 3916 escaped = expression.replace('"', '\\"') 3917 escaped = f'"{escaped}"' 3918 3919 return escaped 3920 3921 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3922 return f"{self.sql(expression, 'this')} FORMAT JSON" 3923 3924 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3925 # Output the Teradata column FORMAT override. 3926 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3927 this = self.sql(expression, "this") 3928 fmt = self.sql(expression, "format") 3929 return f"{this} (FORMAT {fmt})" 3930 3931 def _jsonobject_sql( 3932 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3933 ) -> str: 3934 null_handling = expression.args.get("null_handling") 3935 null_handling = f" {null_handling}" if null_handling else "" 3936 3937 unique_keys = expression.args.get("unique_keys") 3938 if unique_keys is not None: 3939 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3940 else: 3941 unique_keys = "" 3942 3943 return_type = self.sql(expression, "return_type") 3944 return_type = f" RETURNING {return_type}" if return_type else "" 3945 encoding = self.sql(expression, "encoding") 3946 encoding = f" ENCODING {encoding}" if encoding else "" 3947 3948 if not name: 3949 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3950 3951 return self.func( 3952 name, 3953 *expression.expressions, 3954 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3955 ) 3956 3957 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3958 null_handling = expression.args.get("null_handling") 3959 null_handling = f" {null_handling}" if null_handling else "" 3960 return_type = self.sql(expression, "return_type") 3961 return_type = f" RETURNING {return_type}" if return_type else "" 3962 strict = " STRICT" if expression.args.get("strict") else "" 3963 return self.func( 3964 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3965 ) 3966 3967 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3968 this = self.sql(expression, "this") 3969 order = self.sql(expression, "order") 3970 null_handling = expression.args.get("null_handling") 3971 null_handling = f" {null_handling}" if null_handling else "" 3972 return_type = self.sql(expression, "return_type") 3973 return_type = f" RETURNING {return_type}" if return_type else "" 3974 strict = " STRICT" if expression.args.get("strict") else "" 3975 return self.func( 3976 "JSON_ARRAYAGG", 3977 this, 3978 suffix=f"{order}{null_handling}{return_type}{strict})", 3979 ) 3980 3981 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3982 path = self.sql(expression, "path") 3983 path = f" PATH {path}" if path else "" 3984 nested_schema = self.sql(expression, "nested_schema") 3985 3986 if nested_schema: 3987 return f"NESTED{path} {nested_schema}" 3988 3989 this = self.sql(expression, "this") 3990 kind = self.sql(expression, "kind") 3991 kind = f" {kind}" if kind else "" 3992 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3993 3994 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3995 return f"{this}{kind}{format_json}{path}{ordinality}" 3996 3997 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3998 return self.func("COLUMNS", *expression.expressions) 3999 4000 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4001 this = self.sql(expression, "this") 4002 path = self.sql(expression, "path") 4003 path = f", {path}" if path else "" 4004 error_handling = expression.args.get("error_handling") 4005 error_handling = f" {error_handling}" if error_handling else "" 4006 empty_handling = expression.args.get("empty_handling") 4007 empty_handling = f" {empty_handling}" if empty_handling else "" 4008 schema = self.sql(expression, "schema") 4009 return self.func( 4010 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4011 ) 4012 4013 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4014 this = self.sql(expression, "this") 4015 kind = self.sql(expression, "kind") 4016 path = self.sql(expression, "path") 4017 path = f" {path}" if path else "" 4018 as_json = " AS JSON" if expression.args.get("as_json") else "" 4019 return f"{this} {kind}{path}{as_json}" 4020 4021 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4022 this = self.sql(expression, "this") 4023 path = self.sql(expression, "path") 4024 path = f", {path}" if path else "" 4025 expressions = self.expressions(expression) 4026 with_ = ( 4027 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4028 if expressions 4029 else "" 4030 ) 4031 return f"OPENJSON({this}{path}){with_}" 4032 4033 def in_sql(self, expression: exp.In) -> str: 4034 query = expression.args.get("query") 4035 unnest = expression.args.get("unnest") 4036 field = expression.args.get("field") 4037 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4038 4039 if query: 4040 in_sql = self.sql(query) 4041 elif unnest: 4042 in_sql = self.in_unnest_op(unnest) 4043 elif field: 4044 in_sql = self.sql(field) 4045 else: 4046 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4047 4048 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4049 4050 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4051 return f"(SELECT {self.sql(unnest)})" 4052 4053 def interval_sql(self, expression: exp.Interval) -> str: 4054 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4055 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4056 exp.AutoRefreshProperty, 4057 ) 4058 interval_keyword = "INTERVAL" if include_keyword else "" 4059 unit_expression = expression.args.get("unit") 4060 unit = self.sql(unit_expression) if unit_expression else "" 4061 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4062 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4063 unit = f" {unit}" if unit else "" 4064 4065 if self.SINGLE_STRING_INTERVAL: 4066 this = expression.this.name if expression.this else "" 4067 if this: 4068 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4069 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4070 return f"{interval_keyword}'{this}'{unit}" 4071 return f"{interval_keyword}'{this}{unit}'" 4072 return f"{interval_keyword}{unit}" 4073 4074 this = self.sql(expression, "this") 4075 if this: 4076 if not include_keyword and expression.this.is_string: 4077 this = expression.this.name 4078 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4079 this = f"({this})" 4080 if include_keyword: 4081 this = f" {this}" 4082 4083 return f"{interval_keyword}{this}{unit}" 4084 4085 def return_sql(self, expression: exp.Return) -> str: 4086 return f"RETURN {self.sql(expression, 'this')}" 4087 4088 def reference_sql(self, expression: exp.Reference) -> str: 4089 this = self.sql(expression, "this") 4090 expressions = self.expressions(expression, flat=True) 4091 expressions = f"({expressions})" if expressions else "" 4092 options = self.expressions(expression, key="options", flat=True, sep=" ") 4093 options = f" {options}" if options else "" 4094 return f"REFERENCES {this}{expressions}{options}" 4095 4096 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4097 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4098 parent = expression.parent 4099 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4100 4101 return self.func( 4102 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4103 ) 4104 4105 def paren_sql(self, expression: exp.Paren) -> str: 4106 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4107 return f"({sql}{self.seg(')', sep='')}" 4108 4109 def neg_sql(self, expression: exp.Neg) -> str: 4110 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4111 this_sql = self.sql(expression, "this") 4112 sep = " " if this_sql[0] == "-" else "" 4113 return f"-{sep}{this_sql}" 4114 4115 def not_sql(self, expression: exp.Not) -> str: 4116 return f"NOT {self.sql(expression, 'this')}" 4117 4118 def alias_sql(self, expression: exp.Alias) -> str: 4119 alias = self.sql(expression, "alias") 4120 alias = f" AS {alias}" if alias else "" 4121 return f"{self.sql(expression, 'this')}{alias}" 4122 4123 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4124 alias = expression.args["alias"] 4125 4126 parent = expression.parent 4127 pivot = parent and parent.parent 4128 4129 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4130 identifier_alias = isinstance(alias, exp.Identifier) 4131 literal_alias = isinstance(alias, exp.Literal) 4132 4133 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4134 alias.replace(exp.Literal.string(alias.output_name)) 4135 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4136 alias.replace(exp.to_identifier(alias.output_name)) 4137 4138 return self.alias_sql(expression) 4139 4140 def aliases_sql(self, expression: exp.Aliases) -> str: 4141 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4142 4143 def atindex_sql(self, expression: exp.AtIndex) -> str: 4144 this = self.sql(expression, "this") 4145 index = self.sql(expression, "expression") 4146 return f"{this} AT {index}" 4147 4148 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4149 this = self.sql(expression, "this") 4150 zone = self.sql(expression, "zone") 4151 return f"{this} AT TIME ZONE {zone}" 4152 4153 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4154 this = self.sql(expression, "this") 4155 zone = self.sql(expression, "zone") 4156 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4157 4158 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4159 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4160 4161 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4162 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4163 4164 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4165 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4166 4167 def add_sql(self, expression: exp.Add) -> str: 4168 return self.binary(expression, "+") 4169 4170 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4171 return self.connector_sql(expression, "AND", stack) 4172 4173 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4174 return self.connector_sql(expression, "OR", stack) 4175 4176 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4177 return self.connector_sql(expression, "XOR", stack) 4178 4179 def connector_sql( 4180 self, 4181 expression: exp.Connector, 4182 op: str, 4183 stack: list[str | exp.Expr] | None = None, 4184 ) -> str: 4185 if stack is not None: 4186 stack.append(expression.right) 4187 if expression.comments and self.comments: 4188 op = self.maybe_comment(op, comments=expression.comments) 4189 4190 stack.extend((op, expression.left)) 4191 return op 4192 4193 stack = [expression] 4194 sqls: list[str] = [] 4195 ops = set() 4196 4197 while stack: 4198 node = stack.pop() 4199 if isinstance(node, exp.Connector): 4200 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4201 else: 4202 sql = self.sql(node) 4203 if sqls and sqls[-1] in ops: 4204 sqls[-1] += f" {sql}" 4205 else: 4206 sqls.append(sql) 4207 4208 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4209 return sep.join(sqls) 4210 4211 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4212 return self.binary(expression, "&") 4213 4214 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4215 return self.binary(expression, "<<") 4216 4217 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4218 return f"~{self.sql(expression, 'this')}" 4219 4220 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4221 return self.binary(expression, "|") 4222 4223 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4224 return self.binary(expression, ">>") 4225 4226 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4227 return self.binary(expression, "^") 4228 4229 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4230 format_sql = self.sql(expression, "format") 4231 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4232 to_sql = self.sql(expression, "to") 4233 to_sql = f" {to_sql}" if to_sql else "" 4234 action = self.sql(expression, "action") 4235 action = f" {action}" if action else "" 4236 default = self.sql(expression, "default") 4237 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4238 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4239 4240 # Base implementation that excludes safe, zone, and target_type metadata args 4241 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4242 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4243 4244 # Base implementation that excludes the safe and default_year metadata args 4245 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4246 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4247 4248 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4249 return self.func( 4250 "PARSE_DATETIME", 4251 expression.this, 4252 expression.args.get("format"), 4253 expression.args.get("zone"), 4254 ) 4255 4256 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4257 zone = self.sql(expression, "this") 4258 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4259 4260 def collate_sql(self, expression: exp.Collate) -> str: 4261 if self.COLLATE_IS_FUNC: 4262 return self.function_fallback_sql(expression) 4263 return self.binary(expression, "COLLATE") 4264 4265 def command_sql(self, expression: exp.Command) -> str: 4266 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4267 4268 def comment_sql(self, expression: exp.Comment) -> str: 4269 this = self.sql(expression, "this") 4270 kind = expression.args["kind"] 4271 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4272 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4273 expression_sql = self.sql(expression, "expression") 4274 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4275 4276 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4277 this = self.sql(expression, "this") 4278 delete = " DELETE" if expression.args.get("delete") else "" 4279 recompress = self.sql(expression, "recompress") 4280 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4281 to_disk = self.sql(expression, "to_disk") 4282 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4283 to_volume = self.sql(expression, "to_volume") 4284 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4285 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4286 4287 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4288 where = self.sql(expression, "where") 4289 group = self.sql(expression, "group") 4290 aggregates = self.expressions(expression, key="aggregates") 4291 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4292 4293 if not (where or group or aggregates) and len(expression.expressions) == 1: 4294 return f"TTL {self.expressions(expression, flat=True)}" 4295 4296 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4297 4298 def transaction_sql(self, expression: exp.Transaction) -> str: 4299 modes = self.expressions(expression, key="modes") 4300 modes = f" {modes}" if modes else "" 4301 return f"BEGIN{modes}" 4302 4303 def commit_sql(self, expression: exp.Commit) -> str: 4304 chain = expression.args.get("chain") 4305 if chain is not None: 4306 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4307 4308 return f"COMMIT{chain or ''}" 4309 4310 def rollback_sql(self, expression: exp.Rollback) -> str: 4311 savepoint = expression.args.get("savepoint") 4312 savepoint = f" TO {savepoint}" if savepoint else "" 4313 return f"ROLLBACK{savepoint}" 4314 4315 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4316 this = self.sql(expression, "this") 4317 4318 exists = "" 4319 if expression.args.get("exists"): 4320 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4321 exists = " IF EXISTS" 4322 else: 4323 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4324 4325 dtype = self.sql(expression, "dtype") 4326 if dtype: 4327 collate = self.sql(expression, "collate") 4328 collate = f" COLLATE {collate}" if collate else "" 4329 using = self.sql(expression, "using") 4330 using = f" USING {using}" if using else "" 4331 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4332 null_constraint = self._alter_column_null_constraint_sql(expression) 4333 4334 return ( 4335 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4336 f"{collate}{using}{null_constraint}" 4337 ) 4338 4339 default = self.sql(expression, "default") 4340 if default: 4341 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4342 4343 comment = self.sql(expression, "comment") 4344 if comment: 4345 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4346 4347 visible = expression.args.get("visible") 4348 if visible: 4349 return f"ALTER COLUMN{exists} {this} SET {visible}" 4350 4351 allow_null = expression.args.get("allow_null") 4352 drop = expression.args.get("drop") 4353 4354 if not drop and not allow_null: 4355 self.unsupported("Unsupported ALTER COLUMN syntax") 4356 4357 if allow_null is not None: 4358 keyword = "DROP" if drop else "SET" 4359 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4360 4361 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4362 4363 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4364 allow_null = expression.args.get("allow_null") 4365 if allow_null is None: 4366 return "" 4367 4368 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4369 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4370 return "" 4371 4372 return " NULL" if allow_null else " NOT NULL" 4373 4374 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4375 this = self.sql(expression, "this") 4376 rename_from = self.sql(expression, "rename_from") 4377 if rename_from: 4378 if not self.SUPPORTS_CHANGE_COLUMN: 4379 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4380 return f"CHANGE COLUMN {rename_from} {this}" 4381 if not self.SUPPORTS_MODIFY_COLUMN: 4382 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4383 return f"MODIFY COLUMN {this}" 4384 4385 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4386 this = self.sql(expression, "this") 4387 4388 visible = expression.args.get("visible") 4389 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4390 4391 return f"ALTER INDEX {this} {visible_sql}" 4392 4393 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4394 this = self.sql(expression, "this") 4395 if not isinstance(expression.this, exp.Var): 4396 this = f"KEY DISTKEY {this}" 4397 return f"ALTER DISTSTYLE {this}" 4398 4399 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4400 compound = " COMPOUND" if expression.args.get("compound") else "" 4401 this = self.sql(expression, "this") 4402 expressions = self.expressions(expression, flat=True) 4403 expressions = f"({expressions})" if expressions else "" 4404 return f"ALTER{compound} SORTKEY {this or expressions}" 4405 4406 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4407 if not self.RENAME_TABLE_WITH_DB: 4408 # Remove db from tables 4409 expression = expression.transform( 4410 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4411 ).assert_is(exp.AlterRename) 4412 this = self.sql(expression, "this") 4413 to_kw = " TO" if include_to else "" 4414 return f"RENAME{to_kw} {this}" 4415 4416 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4417 exists = " IF EXISTS" if expression.args.get("exists") else "" 4418 old_column = self.sql(expression, "this") 4419 new_column = self.sql(expression, "to") 4420 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4421 4422 def alterset_sql(self, expression: exp.AlterSet) -> str: 4423 exprs = self.expressions(expression, flat=True) 4424 if self.ALTER_SET_WRAPPED: 4425 exprs = f"({exprs})" 4426 4427 return f"SET {exprs}" 4428 4429 def alter_sql(self, expression: exp.Alter) -> str: 4430 actions = expression.actions 4431 4432 if ( 4433 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4434 and actions 4435 and isinstance(actions[0], exp.ColumnDef) 4436 ): 4437 actions_sql = self.expressions(expression, key="actions", flat=True) 4438 actions_sql = f"ADD {actions_sql}" 4439 else: 4440 actions_list = [] 4441 for action in actions: 4442 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4443 action_sql = self.add_column_sql(action) 4444 else: 4445 action_sql = self.sql(action) 4446 if isinstance(action, exp.Query): 4447 action_sql = f"AS {action_sql}" 4448 4449 actions_list.append(action_sql) 4450 4451 actions_sql = self.format_args(*actions_list).lstrip("\n") 4452 4453 iceberg = ( 4454 "ICEBERG " 4455 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4456 else "" 4457 ) 4458 exists = " IF EXISTS" if expression.args.get("exists") else "" 4459 on_cluster = self.sql(expression, "cluster") 4460 on_cluster = f" {on_cluster}" if on_cluster else "" 4461 only = " ONLY" if expression.args.get("only") else "" 4462 options = expression.args.get("options") or [] 4463 options_sql = self.expressions( 4464 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4465 ) 4466 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4467 partitions_sql = self.expressions( 4468 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4469 sep=" ", 4470 flat=True, 4471 ) 4472 if partitions_sql and (actions_sql or options_sql): 4473 partitions_sql = f" {partitions_sql}" 4474 kind = self.sql(expression, "kind") 4475 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4476 check = " WITH CHECK" if expression.args.get("check") else "" 4477 cascade = ( 4478 " CASCADE" 4479 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4480 else "" 4481 ) 4482 this = self.sql(expression, "this") 4483 this = f" {this}" if this else "" 4484 4485 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4486 4487 def altersession_sql(self, expression: exp.AlterSession) -> str: 4488 items_sql = self.expressions(expression, flat=True) 4489 keyword = "UNSET" if expression.args.get("unset") else "SET" 4490 return f"{keyword} {items_sql}" 4491 4492 def add_column_sql(self, expression: exp.Expr) -> str: 4493 sql = self.sql(expression) 4494 if isinstance(expression, exp.Schema): 4495 column_text = " COLUMNS" 4496 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4497 column_text = " COLUMN" 4498 else: 4499 column_text = "" 4500 4501 return f"ADD{column_text} {sql}" 4502 4503 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4504 expressions = self.expressions(expression) 4505 exists = " IF EXISTS " if expression.args.get("exists") else " " 4506 return f"DROP{exists}{expressions}" 4507 4508 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4509 return "DROP PRIMARY KEY" 4510 4511 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4512 return f"ADD {self.expressions(expression, indent=False)}" 4513 4514 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4515 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4516 location = self.sql(expression, "location") 4517 location = f" {location}" if location else "" 4518 return f"ADD {exists}{self.sql(expression.this)}{location}" 4519 4520 def distinct_sql(self, expression: exp.Distinct) -> str: 4521 this = self.expressions(expression, flat=True) 4522 4523 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4524 case = exp.case() 4525 for arg in expression.expressions: 4526 case = case.when(arg.is_(exp.null()), exp.null()) 4527 this = self.sql(case.else_(f"({this})")) 4528 4529 this = f" {this}" if this else "" 4530 4531 on = self.sql(expression, "on") 4532 on = f" ON {on}" if on else "" 4533 return f"DISTINCT{this}{on}" 4534 4535 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4536 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4537 4538 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4539 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4540 4541 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4542 this_sql = self.sql(expression, "this") 4543 expression_sql = self.sql(expression, "expression") 4544 kind = "MAX" if expression.args.get("max") else "MIN" 4545 return f"{this_sql} HAVING {kind} {expression_sql}" 4546 4547 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4548 return self._truncated_div_sql(expression.this, expression.expression) 4549 4550 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4551 div: exp.Expr = exp.Div(this=this, expression=divisor) 4552 if self.CAST_TO_INT_ROUNDS: 4553 div = exp.Trunc(this=div) 4554 4555 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4556 4557 def dpipe_sql(self, expression: exp.DPipe) -> str: 4558 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4559 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4560 return self.binary(expression, "||") 4561 4562 def div_sql(self, expression: exp.Div) -> str: 4563 l, r = expression.left, expression.right 4564 4565 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4566 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4567 4568 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4569 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4570 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4571 4572 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4573 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4574 # Native integer division depends on the target's operand types, which may differ 4575 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4576 # integer literals or casts 4577 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4578 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4579 ): 4580 return self.sql(exp.IntDiv(this=l, expression=r)) 4581 return self._truncated_div_sql(l, r) 4582 4583 return self.binary(expression, "/") 4584 4585 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4586 n = exp._wrap(expression.this, exp.Binary) 4587 d = exp._wrap(expression.expression, exp.Binary) 4588 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4589 4590 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4591 return self.binary(expression, "OVERLAPS") 4592 4593 def distance_sql(self, expression: exp.Distance) -> str: 4594 return self.binary(expression, "<->") 4595 4596 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4597 return self.binary(expression, "<<->>") 4598 4599 def dot_sql(self, expression: exp.Dot) -> str: 4600 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4601 4602 def eq_sql(self, expression: exp.EQ) -> str: 4603 return self.binary(expression, "=") 4604 4605 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4606 return self.binary(expression, ":=") 4607 4608 def escape_sql(self, expression: exp.Escape) -> str: 4609 this = expression.this 4610 if ( 4611 isinstance(this, (exp.Like, exp.ILike)) 4612 and isinstance(this.expression, (exp.All, exp.Any)) 4613 and not self.SUPPORTS_LIKE_QUANTIFIERS 4614 ): 4615 return self._like_sql(this, escape=expression) 4616 return self.binary(expression, "ESCAPE") 4617 4618 def glob_sql(self, expression: exp.Glob) -> str: 4619 return self.binary(expression, "GLOB") 4620 4621 def gt_sql(self, expression: exp.GT) -> str: 4622 return self.binary(expression, ">") 4623 4624 def gte_sql(self, expression: exp.GTE) -> str: 4625 return self.binary(expression, ">=") 4626 4627 def is_sql(self, expression: exp.Is) -> str: 4628 negate = expression.args.get("negate") 4629 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4630 positive = bool(expression.expression.this) != bool(negate) 4631 return self.sql(expression.this if positive else exp.not_(expression.this)) 4632 return self.binary(expression, "IS NOT" if negate else "IS") 4633 4634 def _like_sql( 4635 self, 4636 expression: exp.Like | exp.ILike, 4637 escape: exp.Escape | None = None, 4638 ) -> str: 4639 this = expression.this 4640 rhs = expression.expression 4641 4642 if isinstance(expression, exp.Like): 4643 exp_class: type[exp.Like | exp.ILike] = exp.Like 4644 op = "LIKE" 4645 else: 4646 exp_class = exp.ILike 4647 op = "ILIKE" 4648 4649 if expression.args.get("negate"): 4650 op = f"NOT {op}" 4651 4652 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4653 exprs = rhs.this.unnest() 4654 4655 if isinstance(exprs, exp.Tuple): 4656 exprs = exprs.expressions 4657 else: 4658 exprs = [exprs] 4659 4660 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4661 4662 def _make_like(expr: exp.Expression) -> exp.Expression: 4663 like: exp.Expression = exp_class( 4664 this=this, expression=expr, negate=expression.args.get("negate") 4665 ) 4666 if escape: 4667 like = exp.Escape(this=like, expression=escape.expression.copy()) 4668 return like 4669 4670 like_expr: exp.Expr = _make_like(exprs[0]) 4671 for expr in exprs[1:]: 4672 like_expr = connective(like_expr, _make_like(expr), copy=False) 4673 4674 parent = escape.parent if escape else expression.parent 4675 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4676 parent, exp.Condition 4677 ): 4678 like_expr = exp.paren(like_expr, copy=False) 4679 4680 return self.sql(like_expr) 4681 4682 return self.binary(expression, op) 4683 4684 def like_sql(self, expression: exp.Like) -> str: 4685 return self._like_sql(expression) 4686 4687 def ilike_sql(self, expression: exp.ILike) -> str: 4688 return self._like_sql(expression) 4689 4690 def match_sql(self, expression: exp.Match) -> str: 4691 return self.binary(expression, "MATCH") 4692 4693 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4694 return self.binary(expression, "SIMILAR TO") 4695 4696 def lt_sql(self, expression: exp.LT) -> str: 4697 return self.binary(expression, "<") 4698 4699 def lte_sql(self, expression: exp.LTE) -> str: 4700 return self.binary(expression, "<=") 4701 4702 def mod_sql(self, expression: exp.Mod) -> str: 4703 this = self.sql(expression, "this") 4704 expr = self.sql(expression, "expression") 4705 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4706 4707 parent = expression.parent 4708 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4709 return f"({sql})" 4710 4711 return sql 4712 4713 def mul_sql(self, expression: exp.Mul) -> str: 4714 return self.binary(expression, "*") 4715 4716 def neq_sql(self, expression: exp.NEQ) -> str: 4717 return self.binary(expression, "<>") 4718 4719 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4720 return self.binary(expression, "IS NOT DISTINCT FROM") 4721 4722 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4723 return self.binary(expression, "IS DISTINCT FROM") 4724 4725 def sub_sql(self, expression: exp.Sub) -> str: 4726 return self.binary(expression, "-") 4727 4728 def trycast_sql(self, expression: exp.TryCast) -> str: 4729 return self.cast_sql(expression, safe_prefix="TRY_") 4730 4731 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4732 return self.cast_sql(expression) 4733 4734 def try_sql(self, expression: exp.Try) -> str: 4735 if not self.TRY_SUPPORTED: 4736 self.unsupported("Unsupported TRY function") 4737 return self.sql(expression, "this") 4738 4739 return self.func("TRY", expression.this) 4740 4741 def log_sql(self, expression: exp.Log) -> str: 4742 this = expression.this 4743 expr = expression.expression 4744 4745 if self.dialect.LOG_BASE_FIRST is False: 4746 this, expr = expr, this 4747 elif self.dialect.LOG_BASE_FIRST is None and expr: 4748 if this.name in ("2", "10"): 4749 return self.func(f"LOG{this.name}", expr) 4750 4751 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4752 4753 return self.func("LOG", this, expr) 4754 4755 def use_sql(self, expression: exp.Use) -> str: 4756 kind = self.sql(expression, "kind") 4757 kind = f" {kind}" if kind else "" 4758 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4759 this = f" {this}" if this else "" 4760 return f"USE{kind}{this}" 4761 4762 def binary(self, expression: exp.Binary, op: str) -> str: 4763 sqls: list[str] = [] 4764 stack: list[None | str | exp.Expr] = [expression] 4765 binary_type = type(expression) 4766 4767 while stack: 4768 node = stack.pop() 4769 4770 if type(node) is binary_type: 4771 op_func = node.args.get("operator") 4772 if op_func: 4773 op = f"OPERATOR({self.sql(op_func)})" 4774 4775 stack.append(node.args.get("expression")) 4776 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4777 stack.append(node.args.get("this")) 4778 else: 4779 sqls.append(self.sql(node)) 4780 4781 return "".join(sqls) 4782 4783 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4784 to_clause = self.sql(expression, "to") 4785 if to_clause: 4786 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4787 4788 return self.function_fallback_sql(expression) 4789 4790 def function_fallback_sql(self, expression: exp.Func) -> str: 4791 args = [] 4792 4793 for key in expression.arg_types: 4794 arg_value = expression.args.get(key) 4795 4796 if isinstance(arg_value, list): 4797 for value in arg_value: 4798 args.append(value) 4799 elif arg_value is not None: 4800 args.append(arg_value) 4801 4802 return self.func(self.function_name(expression), *args) 4803 4804 def function_name(self, expression: exp.Func) -> str: 4805 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4806 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4807 4808 def func( 4809 self, 4810 name: str, 4811 *args: t.Any, 4812 prefix: str = "(", 4813 suffix: str = ")", 4814 normalize: bool = True, 4815 ) -> str: 4816 name = self.normalize_func(name) if normalize else name 4817 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4818 4819 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4820 arg_sqls = tuple( 4821 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4822 ) 4823 if self.pretty and self.too_wide(arg_sqls): 4824 return self.indent( 4825 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4826 ) 4827 return sep.join(arg_sqls) 4828 4829 def too_wide(self, args: t.Iterable) -> bool: 4830 return sum(len(arg) for arg in args) > self.max_text_width 4831 4832 def format_time( 4833 self, 4834 expression: exp.Expr, 4835 inverse_time_mapping: dict[str, str] | None = None, 4836 inverse_time_trie: dict | None = None, 4837 ) -> str | None: 4838 return format_time( 4839 self.sql(expression, "format"), 4840 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4841 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4842 ) 4843 4844 def expressions( 4845 self, 4846 expression: exp.Expr | None = None, 4847 key: str | None = None, 4848 sqls: t.Collection[str | exp.Expr] | None = None, 4849 flat: bool = False, 4850 indent: bool = True, 4851 skip_first: bool = False, 4852 skip_last: bool = False, 4853 sep: str = ", ", 4854 prefix: str = "", 4855 dynamic: bool = False, 4856 new_line: bool = False, 4857 ) -> str: 4858 expressions = expression.args.get(key or "expressions") if expression else sqls 4859 4860 if not expressions: 4861 return "" 4862 4863 if flat: 4864 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4865 4866 num_sqls = len(expressions) 4867 result_sqls = [] 4868 4869 for i, e in enumerate(expressions): 4870 sql = self.sql(e, comment=False) 4871 if not sql: 4872 continue 4873 4874 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4875 4876 if self.pretty: 4877 if self.leading_comma: 4878 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4879 else: 4880 result_sqls.append( 4881 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4882 ) 4883 else: 4884 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4885 4886 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4887 if new_line: 4888 result_sqls.insert(0, "") 4889 result_sqls.append("") 4890 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4891 else: 4892 result_sql = "".join(result_sqls) 4893 4894 return ( 4895 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4896 if indent 4897 else result_sql 4898 ) 4899 4900 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4901 flat = flat or isinstance(expression.parent, exp.Properties) 4902 expressions_sql = self.expressions(expression, flat=flat) 4903 if flat: 4904 return f"{op} {expressions_sql}" 4905 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4906 4907 def naked_property(self, expression: exp.Property) -> str: 4908 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4909 if not property_name: 4910 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4911 return f"{property_name} {self.sql(expression, 'this')}" 4912 4913 def tag_sql(self, expression: exp.Tag) -> str: 4914 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4915 4916 def token_sql(self, token_type: TokenType) -> str: 4917 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4918 4919 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4920 this = self.sql(expression, "this") 4921 expressions = self.no_identify(self.expressions, expression) 4922 expressions = ( 4923 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4924 ) 4925 return f"{this}{expressions}" if expressions.strip() != "" else this 4926 4927 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4928 return self.expressions(expression, flat=True) 4929 4930 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4931 params = self.no_identify(self.expressions, expression, flat=True) 4932 body = self.sql(expression, "this") 4933 prefix = "TABLE " if expression.args.get("is_table") else "" 4934 return f"({params}) AS {prefix}{body}" 4935 4936 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4937 this = self.sql(expression, "this") 4938 expressions = self.expressions(expression, flat=True) 4939 return f"{this}({expressions})" 4940 4941 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4942 return self.binary(expression, "=>") 4943 4944 def when_sql(self, expression: exp.When) -> str: 4945 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4946 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4947 condition = self.sql(expression, "condition") 4948 condition = f" AND {condition}" if condition else "" 4949 4950 then_expression = expression.args.get("then") 4951 if isinstance(then_expression, exp.Insert): 4952 this = self.sql(then_expression, "this") 4953 this = f"INSERT {this}" if this else "INSERT" 4954 then = self.sql(then_expression, "expression") 4955 then = f"{this} VALUES {then}" if then else this 4956 elif isinstance(then_expression, exp.Update): 4957 if isinstance(then_expression.args.get("expressions"), exp.Star): 4958 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4959 else: 4960 expressions_sql = self.expressions(then_expression) 4961 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4962 else: 4963 then = self.sql(then_expression) 4964 4965 if isinstance(then_expression, (exp.Insert, exp.Update)): 4966 where = self.sql(then_expression, "where") 4967 if where and not self.SUPPORTS_MERGE_WHERE: 4968 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4969 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4970 where = "" 4971 then = f"{then}{where}" 4972 return f"WHEN {matched}{source}{condition} THEN {then}" 4973 4974 def whens_sql(self, expression: exp.Whens) -> str: 4975 return self.expressions(expression, sep=" ", indent=False) 4976 4977 def merge_sql(self, expression: exp.Merge) -> str: 4978 table = expression.this 4979 table_alias = "" 4980 4981 hints = table.args.get("hints") 4982 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4983 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4984 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4985 4986 this = self.sql(table) 4987 using = f"USING {self.sql(expression, 'using')}" 4988 whens = self.sql(expression, "whens") 4989 4990 on = self.sql(expression, "on") 4991 on = f"ON {on}" if on else "" 4992 4993 if not on: 4994 on = self.expressions(expression, key="using_cond") 4995 on = f"USING ({on})" if on else "" 4996 4997 returning = self.sql(expression, "returning") 4998 if returning: 4999 whens = f"{whens}{returning}" 5000 5001 sep = self.sep() 5002 5003 return self.prepend_ctes( 5004 expression, 5005 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5006 ) 5007 5008 @unsupported_args("format") 5009 def tochar_sql(self, expression: exp.ToChar) -> str: 5010 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5011 5012 @unsupported_args("default") 5013 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5014 if not self.SUPPORTS_TO_NUMBER: 5015 self.unsupported("Unsupported TO_NUMBER function") 5016 return self._tonumber_cast_sql(expression) 5017 5018 fmt = expression.args.get("format") 5019 if not fmt: 5020 self.unsupported("Conversion format is required for TO_NUMBER") 5021 return self._tonumber_cast_sql(expression) 5022 5023 return self.func("TO_NUMBER", expression.this, fmt) 5024 5025 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5026 if expression.args.get("safe"): 5027 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5028 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5029 5030 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5031 this = self.sql(expression, "this") 5032 kind = self.sql(expression, "kind") 5033 settings_sql = self.expressions(expression, key="settings", sep=" ") 5034 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5035 return f"{this}({kind}{args})" 5036 5037 def dictrange_sql(self, expression: exp.DictRange) -> str: 5038 this = self.sql(expression, "this") 5039 max = self.sql(expression, "max") 5040 min = self.sql(expression, "min") 5041 return f"{this}(MIN {min} MAX {max})" 5042 5043 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5044 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5045 5046 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5047 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5048 5049 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5050 def uniquekeyproperty_sql( 5051 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5052 ) -> str: 5053 return f"{prefix} ({self.expressions(expression, flat=True)})" 5054 5055 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5056 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5057 expressions = self.expressions(expression, flat=True) 5058 expressions = f" {self.wrap(expressions)}" if expressions else "" 5059 buckets = self.sql(expression, "buckets") 5060 kind = self.sql(expression, "kind") 5061 buckets = f" BUCKETS {buckets}" if buckets else "" 5062 order = self.sql(expression, "order") 5063 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5064 5065 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5066 return "" 5067 5068 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5069 expressions = self.expressions(expression, key="expressions", flat=True) 5070 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5071 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5072 buckets = self.sql(expression, "buckets") 5073 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5074 5075 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5076 this = self.sql(expression, "this") 5077 having = self.sql(expression, "having") 5078 5079 if having: 5080 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5081 5082 return self.func("ANY_VALUE", this) 5083 5084 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5085 transform = self.func("TRANSFORM", *expression.expressions) 5086 row_format_before = self.sql(expression, "row_format_before") 5087 row_format_before = f" {row_format_before}" if row_format_before else "" 5088 record_writer = self.sql(expression, "record_writer") 5089 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5090 using = f" USING {self.sql(expression, 'command_script')}" 5091 schema = self.sql(expression, "schema") 5092 schema = f" AS {schema}" if schema else "" 5093 row_format_after = self.sql(expression, "row_format_after") 5094 row_format_after = f" {row_format_after}" if row_format_after else "" 5095 record_reader = self.sql(expression, "record_reader") 5096 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5097 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5098 5099 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5100 key_block_size = self.sql(expression, "key_block_size") 5101 if key_block_size: 5102 return f"KEY_BLOCK_SIZE = {key_block_size}" 5103 5104 using = self.sql(expression, "using") 5105 if using: 5106 return f"USING {using}" 5107 5108 parser = self.sql(expression, "parser") 5109 if parser: 5110 return f"WITH PARSER {parser}" 5111 5112 comment = self.sql(expression, "comment") 5113 if comment: 5114 return f"COMMENT {comment}" 5115 5116 visible = expression.args.get("visible") 5117 if visible is not None: 5118 return "VISIBLE" if visible else "INVISIBLE" 5119 5120 engine_attr = self.sql(expression, "engine_attr") 5121 if engine_attr: 5122 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5123 5124 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5125 if secondary_engine_attr: 5126 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5127 5128 self.unsupported("Unsupported index constraint option.") 5129 return "" 5130 5131 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5132 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5133 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5134 5135 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5136 kind = self.sql(expression, "kind") 5137 kind = f"{kind} INDEX" if kind else "INDEX" 5138 this = self.sql(expression, "this") 5139 this = f" {this}" if this else "" 5140 index_type = self.sql(expression, "index_type") 5141 index_type = f" USING {index_type}" if index_type else "" 5142 expressions = self.expressions(expression, flat=True) 5143 expressions = f" ({expressions})" if expressions else "" 5144 options = self.expressions(expression, key="options", sep=" ") 5145 options = f" {options}" if options else "" 5146 return f"{kind}{this}{index_type}{expressions}{options}" 5147 5148 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5149 if self.NVL2_SUPPORTED: 5150 return self.function_fallback_sql(expression) 5151 5152 case = exp.Case().when( 5153 expression.this.is_(exp.null()).not_(copy=False), 5154 expression.args["true"], 5155 copy=False, 5156 ) 5157 else_cond = expression.args.get("false") 5158 if else_cond: 5159 case.else_(else_cond, copy=False) 5160 5161 return self.sql(case) 5162 5163 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5164 if expression.args.get("from_first") is False: 5165 self.unsupported("NTH_VALUE FROM LAST is not supported") 5166 5167 return self.function_fallback_sql(expression) 5168 5169 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5170 this = self.sql(expression, "this") 5171 expr = self.sql(expression, "expression") 5172 position = self.sql(expression, "position") 5173 position = f", {position}" if position else "" 5174 iterator = self.sql(expression, "iterator") 5175 condition = self.sql(expression, "condition") 5176 condition = f" IF {condition}" if condition else "" 5177 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5178 5179 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5180 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5181 5182 def opclass_sql(self, expression: exp.Opclass) -> str: 5183 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5184 5185 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5186 model = self.sql(expression, "this") 5187 model = f"MODEL {model}" 5188 expr = expression.expression 5189 if expr: 5190 expr_sql = self.sql(expression, "expression") 5191 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5192 else: 5193 expr_sql = None 5194 5195 parameters = self.sql(expression, "params_struct") or None 5196 5197 return self.func(name, model, expr_sql, parameters) 5198 5199 def predict_sql(self, expression: exp.Predict) -> str: 5200 return self._ml_sql(expression, "PREDICT") 5201 5202 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5203 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5204 return self._ml_sql(expression, name) 5205 5206 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5207 return self._ml_sql(expression, "GENERATE_TEXT") 5208 5209 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5210 return self._ml_sql(expression, "GENERATE_TABLE") 5211 5212 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5213 return self._ml_sql(expression, "GENERATE_BOOL") 5214 5215 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5216 return self._ml_sql(expression, "GENERATE_INT") 5217 5218 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5219 return self._ml_sql(expression, "GENERATE_DOUBLE") 5220 5221 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5222 return self._ml_sql(expression, "TRANSLATE") 5223 5224 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5225 return self._ml_sql(expression, "FORECAST") 5226 5227 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5228 this_sql = self.sql(expression, "this") 5229 if isinstance(expression.this, exp.Table): 5230 this_sql = f"TABLE {this_sql}" 5231 5232 return self.func( 5233 "FORECAST", 5234 this_sql, 5235 expression.args.get("data_col"), 5236 expression.args.get("timestamp_col"), 5237 expression.args.get("model"), 5238 expression.args.get("id_cols"), 5239 expression.args.get("horizon"), 5240 expression.args.get("forecast_end_timestamp"), 5241 expression.args.get("confidence_level"), 5242 expression.args.get("output_historical_time_series"), 5243 expression.args.get("context_window"), 5244 ) 5245 5246 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5247 this_sql = self.sql(expression, "this") 5248 if isinstance(expression.this, exp.Table): 5249 this_sql = f"TABLE {this_sql}" 5250 5251 return self.func( 5252 "FEATURES_AT_TIME", 5253 this_sql, 5254 expression.args.get("time"), 5255 expression.args.get("num_rows"), 5256 expression.args.get("ignore_feature_nulls"), 5257 ) 5258 5259 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5260 this_sql = self.sql(expression, "this") 5261 if isinstance(expression.this, exp.Table): 5262 this_sql = f"TABLE {this_sql}" 5263 5264 query_table = self.sql(expression, "query_table") 5265 if isinstance(expression.args["query_table"], exp.Table): 5266 query_table = f"TABLE {query_table}" 5267 5268 return self.func( 5269 "VECTOR_SEARCH", 5270 this_sql, 5271 expression.args.get("column_to_search"), 5272 query_table, 5273 expression.args.get("query_column_to_search"), 5274 expression.args.get("top_k"), 5275 expression.args.get("distance_type"), 5276 expression.args.get("options"), 5277 ) 5278 5279 def forin_sql(self, expression: exp.ForIn) -> str: 5280 this = self.sql(expression, "this") 5281 expression_sql = self.sql(expression, "expression") 5282 return f"FOR {this} DO {expression_sql}" 5283 5284 def refresh_sql(self, expression: exp.Refresh) -> str: 5285 this = self.sql(expression, "this") 5286 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5287 return f"REFRESH {kind}{this}" 5288 5289 def toarray_sql(self, expression: exp.ToArray) -> str: 5290 arg = expression.this 5291 if not arg.type: 5292 import sqlglot.optimizer.annotate_types 5293 5294 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5295 5296 if arg.is_type(exp.DType.ARRAY): 5297 return self.sql(arg) 5298 5299 cond_for_null = arg.is_(exp.null()) 5300 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5301 5302 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5303 this = expression.this 5304 time_format = self.format_time(expression) 5305 5306 if time_format: 5307 return self.sql( 5308 exp.cast( 5309 exp.StrToTime(this=this, format=expression.args["format"]), 5310 exp.DType.TIME, 5311 ) 5312 ) 5313 5314 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5315 return self.sql(this) 5316 5317 return self.sql(exp.cast(this, exp.DType.TIME)) 5318 5319 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5320 this = expression.this 5321 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5322 return self.sql(this) 5323 5324 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5325 5326 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5327 this = expression.this 5328 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5329 return self.sql(this) 5330 5331 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5332 5333 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5334 this = expression.this 5335 time_format = self.format_time(expression) 5336 safe = expression.args.get("safe") 5337 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5338 return self.sql( 5339 exp.cast( 5340 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5341 exp.DType.DATE, 5342 ) 5343 ) 5344 5345 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5346 return self.sql(this) 5347 5348 if safe: 5349 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5350 5351 return self.sql(exp.cast(this, exp.DType.DATE)) 5352 5353 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5354 return self.sql( 5355 exp.func( 5356 "DATEDIFF", 5357 expression.this, 5358 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5359 "day", 5360 ) 5361 ) 5362 5363 def lastday_sql(self, expression: exp.LastDay) -> str: 5364 if self.LAST_DAY_SUPPORTS_DATE_PART: 5365 return self.function_fallback_sql(expression) 5366 5367 unit = expression.args.get("unit") 5368 if unit and unit.name.upper() != "MONTH": 5369 self.unsupported("Date parts are not supported in LAST_DAY.") 5370 5371 return self.func("LAST_DAY", expression.this) 5372 5373 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5374 import sqlglot.dialects.dialect 5375 5376 return self.func( 5377 "DATE_ADD", 5378 expression.this, 5379 expression.expression, 5380 sqlglot.dialects.dialect.unit_to_str(expression), 5381 ) 5382 5383 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5384 this = expression.this 5385 position = expression.args["position"] 5386 offset = index_offset - (expression.args.get("offset") or 0) 5387 5388 if offset: 5389 if position.is_int: 5390 value = position.to_py() 5391 if value >= 0: 5392 position = exp.Literal.number(value + offset) 5393 elif offset < 0 and value == -1: 5394 # 1-based -1 appends, which a 0-based position can only express as the size 5395 position = exp.ArraySize(this=this.copy()) 5396 else: 5397 # Negative positions count from the end, so they shift in the opposite 5398 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5399 position = exp.Literal.number(value - offset) 5400 else: 5401 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5402 5403 return self.func("ARRAY_INSERT", this, position, expression.expression) 5404 5405 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5406 if self.CAN_IMPLEMENT_ARRAY_ANY: 5407 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5408 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5409 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5410 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5411 5412 import sqlglot.dialects.dialect 5413 5414 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5415 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5416 self.unsupported("ARRAY_ANY is unsupported") 5417 5418 return self.function_fallback_sql(expression) 5419 5420 def struct_sql(self, expression: exp.Struct) -> str: 5421 expression.set( 5422 "expressions", 5423 [ 5424 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5425 if isinstance(e, exp.PropertyEQ) 5426 else e 5427 for e in expression.expressions 5428 ], 5429 ) 5430 5431 return self.function_fallback_sql(expression) 5432 5433 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5434 low = self.sql(expression, "this") 5435 high = self.sql(expression, "expression") 5436 5437 return f"{low} TO {high}" 5438 5439 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5440 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5441 tables = f" {self.expressions(expression)}" 5442 5443 exists = " IF EXISTS" if expression.args.get("exists") else "" 5444 5445 on_cluster = self.sql(expression, "cluster") 5446 on_cluster = f" {on_cluster}" if on_cluster else "" 5447 5448 identity = self.sql(expression, "identity") 5449 identity = f" {identity} IDENTITY" if identity else "" 5450 5451 option = self.sql(expression, "option") 5452 option = f" {option}" if option else "" 5453 5454 partition = self.sql(expression, "partition") 5455 partition = f" {partition}" if partition else "" 5456 5457 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5458 5459 # This transpiles T-SQL's CONVERT function 5460 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5461 def convert_sql(self, expression: exp.Convert) -> str: 5462 to = expression.this 5463 value = expression.expression 5464 style = expression.args.get("style") 5465 safe = expression.args.get("safe") 5466 strict = expression.args.get("strict") 5467 5468 if not to or not value: 5469 return "" 5470 5471 # Retrieve length of datatype and override to default if not specified 5472 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5473 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5474 5475 transformed: exp.Expr | None = None 5476 cast = exp.Cast if strict else exp.TryCast 5477 5478 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5479 if isinstance(style, exp.Literal) and style.is_int: 5480 import sqlglot.dialects.tsql 5481 5482 style_value = style.name 5483 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5484 if not converted_style: 5485 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5486 5487 fmt = exp.Literal.string(converted_style) 5488 5489 if to.this == exp.DType.DATE: 5490 transformed = exp.StrToDate(this=value, format=fmt) 5491 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5492 transformed = exp.StrToTime(this=value, format=fmt) 5493 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5494 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5495 elif to.this == exp.DType.TEXT: 5496 transformed = exp.TimeToStr(this=value, format=fmt) 5497 5498 if not transformed: 5499 transformed = cast(this=value, to=to, safe=safe) 5500 5501 return self.sql(transformed) 5502 5503 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5504 this = expression.this 5505 if isinstance(this, exp.JSONPathWildcard): 5506 this = self.json_path_part(this) 5507 return f".{this}" if this else "" 5508 5509 quoted = expression.args.get("quoted") 5510 if not ( 5511 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5512 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5513 return f".{this}" 5514 5515 this = self.json_path_part(this) 5516 5517 return ( 5518 f"[{this}]" 5519 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5520 else f".{this}" 5521 ) 5522 5523 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5524 this = self.json_path_part(expression.this) 5525 return f"[{this}]" if this else "" 5526 5527 def _simplify_unless_literal(self, expression: E) -> E: 5528 if not isinstance(expression, exp.Literal): 5529 import sqlglot.optimizer.simplify 5530 5531 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5532 5533 return expression 5534 5535 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5536 this = expression.this 5537 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5538 self.unsupported( 5539 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5540 ) 5541 return self.sql(this) 5542 5543 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5544 if self.IGNORE_NULLS_BEFORE_ORDER: 5545 from sqlglot.optimizer.scope import find_all_in_scope 5546 5547 # The first modifier here will be the one closest to the AggFunc's arg 5548 mods = sorted( 5549 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5550 key=lambda x: ( 5551 0 5552 if isinstance(x, exp.HavingMax) 5553 else (1 if isinstance(x, exp.Order) else 2) 5554 ), 5555 ) 5556 5557 if mods: 5558 mod = mods[0] 5559 this = expression.__class__(this=mod.this.copy()) 5560 this.meta["inline"] = True 5561 mod.this.replace(this) 5562 return self.sql(expression.this) 5563 5564 agg_func = expression.find(exp.AggFunc) 5565 5566 if agg_func: 5567 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5568 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5569 5570 return f"{self.sql(expression, 'this')} {text}" 5571 5572 def _replace_line_breaks(self, string: str) -> str: 5573 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5574 if self.pretty: 5575 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5576 return string 5577 5578 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5579 option = self.sql(expression, "this") 5580 5581 if expression.expressions: 5582 upper = option.upper() 5583 5584 # Snowflake FILE_FORMAT options are separated by whitespace 5585 sep = " " if upper == "FILE_FORMAT" else ", " 5586 5587 # Databricks copy/format options do not set their list of values with EQ 5588 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5589 values = self.expressions(expression, flat=True, sep=sep) 5590 return f"{option}{op}({values})" 5591 5592 value = self.sql(expression, "expression") 5593 5594 if not value: 5595 return option 5596 5597 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5598 5599 return f"{option}{op}{value}" 5600 5601 def credentials_sql(self, expression: exp.Credentials) -> str: 5602 cred_expr = expression.args.get("credentials") 5603 if isinstance(cred_expr, exp.Literal): 5604 # Redshift case: CREDENTIALS <string> 5605 credentials = self.sql(expression, "credentials") 5606 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5607 else: 5608 # Snowflake case: CREDENTIALS = (...) 5609 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5610 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5611 5612 storage = self.sql(expression, "storage") 5613 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5614 5615 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5616 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5617 5618 iam_role = self.sql(expression, "iam_role") 5619 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5620 5621 region = self.sql(expression, "region") 5622 region = f" REGION {region}" if region else "" 5623 5624 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5625 5626 def copy_sql(self, expression: exp.Copy) -> str: 5627 this = self.sql(expression, "this") 5628 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5629 5630 credentials = self.sql(expression, "credentials") 5631 credentials = self.seg(credentials) if credentials else "" 5632 files = self.expressions(expression, key="files", flat=True) 5633 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5634 5635 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5636 params = self.expressions( 5637 expression, 5638 key="params", 5639 sep=sep, 5640 new_line=True, 5641 skip_last=True, 5642 skip_first=True, 5643 indent=self.COPY_PARAMS_ARE_WRAPPED, 5644 ) 5645 5646 if params: 5647 if self.COPY_PARAMS_ARE_WRAPPED: 5648 params = f" WITH ({params})" 5649 elif not self.pretty and (files or credentials): 5650 params = f" {params}" 5651 5652 return f"COPY{this}{kind} {files}{credentials}{params}" 5653 5654 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5655 return "" 5656 5657 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5658 on_sql = "ON" if expression.args.get("on") else "OFF" 5659 filter_col: str | None = self.sql(expression, "filter_column") 5660 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5661 retention_period: str | None = self.sql(expression, "retention_period") 5662 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5663 5664 if filter_col or retention_period: 5665 on_sql = self.func("ON", filter_col, retention_period) 5666 5667 return f"DATA_DELETION={on_sql}" 5668 5669 def maskingpolicycolumnconstraint_sql( 5670 self, expression: exp.MaskingPolicyColumnConstraint 5671 ) -> str: 5672 this = self.sql(expression, "this") 5673 expressions = self.expressions(expression, flat=True) 5674 expressions = f" USING ({expressions})" if expressions else "" 5675 return f"MASKING POLICY {this}{expressions}" 5676 5677 def gapfill_sql(self, expression: exp.GapFill) -> str: 5678 this = self.sql(expression, "this") 5679 this = f"TABLE {this}" 5680 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5681 5682 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5683 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5684 5685 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5686 this = self.sql(expression, "this") 5687 expr = expression.expression 5688 5689 if isinstance(expr, exp.Func): 5690 # T-SQL's CLR functions are case sensitive 5691 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5692 else: 5693 expr = self.sql(expression, "expression") 5694 5695 return self.scope_resolution(expr, this) 5696 5697 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5698 if self.PARSE_JSON_NAME is None: 5699 return self.sql(expression.this) 5700 5701 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5702 5703 def rand_sql(self, expression: exp.Rand) -> str: 5704 lower = self.sql(expression, "lower") 5705 upper = self.sql(expression, "upper") 5706 5707 if lower and upper: 5708 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5709 return self.func("RAND", expression.this) 5710 5711 def changes_sql(self, expression: exp.Changes) -> str: 5712 information = self.sql(expression, "information") 5713 information = f"INFORMATION => {information}" 5714 at_before = self.sql(expression, "at_before") 5715 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5716 end = self.sql(expression, "end") 5717 end = f"{self.seg('')}{end}" if end else "" 5718 5719 return f"CHANGES ({information}){at_before}{end}" 5720 5721 def pad_sql(self, expression: exp.Pad) -> str: 5722 prefix = "L" if expression.args.get("is_left") else "R" 5723 5724 fill_pattern = self.sql(expression, "fill_pattern") or None 5725 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5726 fill_pattern = "' '" 5727 5728 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5729 5730 def summarize_sql(self, expression: exp.Summarize) -> str: 5731 table = " TABLE" if expression.args.get("table") else "" 5732 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5733 5734 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5735 generate_series = exp.GenerateSeries(**expression.args) 5736 5737 parent = expression.parent 5738 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5739 parent = parent.parent 5740 5741 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5742 return self.sql(exp.Unnest(expressions=[generate_series])) 5743 5744 if isinstance(parent, exp.Select): 5745 self.unsupported("GenerateSeries projection unnesting is not supported.") 5746 5747 return self.sql(generate_series) 5748 5749 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5750 if self.SUPPORTS_CONVERT_TIMEZONE: 5751 return self.function_fallback_sql(expression) 5752 5753 source_tz = expression.args.get("source_tz") 5754 target_tz = expression.args.get("target_tz") 5755 timestamp = expression.args.get("timestamp") 5756 5757 if source_tz and timestamp: 5758 timestamp = exp.AtTimeZone( 5759 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5760 ) 5761 5762 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5763 5764 return self.sql(expr) 5765 5766 def json_sql(self, expression: exp.JSON) -> str: 5767 this = self.sql(expression, "this") 5768 this = f" {this}" if this else "" 5769 5770 _with = expression.args.get("with_") 5771 5772 if _with is None: 5773 with_sql = "" 5774 elif not _with: 5775 with_sql = " WITHOUT" 5776 else: 5777 with_sql = " WITH" 5778 5779 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5780 5781 return f"JSON{this}{with_sql}{unique_sql}" 5782 5783 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5784 path = self.sql(expression, "path") 5785 returning = self.sql(expression, "returning") 5786 returning = f" RETURNING {returning}" if returning else "" 5787 5788 on_condition = self.sql(expression, "on_condition") 5789 on_condition = f" {on_condition}" if on_condition else "" 5790 5791 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5792 5793 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5794 regexp = " REGEXP" if expression.args.get("regexp") else "" 5795 return f"SKIP{regexp} {self.sql(expression.expression)}" 5796 5797 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5798 else_ = "ELSE " if expression.args.get("else_") else "" 5799 condition = self.sql(expression, "expression") 5800 condition = f"WHEN {condition} THEN " if condition else else_ 5801 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5802 return f"{condition}{insert}" 5803 5804 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5805 kind = self.sql(expression, "kind") 5806 source = self.sql(expression, "source") 5807 5808 if kind: 5809 expressions = self.seg(self.expressions(expression, sep=" ")) 5810 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5811 else: 5812 # If kind isn't present, we have Hive's FROM-first form: 5813 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5814 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5815 sql = f"FROM {source}{self.seg(inserts)}" 5816 5817 return self.prepend_ctes(expression, sql) 5818 5819 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5820 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5821 empty = expression.args.get("empty") 5822 empty = ( 5823 f"DEFAULT {empty} ON EMPTY" 5824 if isinstance(empty, exp.Expr) 5825 else self.sql(expression, "empty") 5826 ) 5827 5828 error = expression.args.get("error") 5829 error = ( 5830 f"DEFAULT {error} ON ERROR" 5831 if isinstance(error, exp.Expr) 5832 else self.sql(expression, "error") 5833 ) 5834 5835 if error and empty: 5836 error = ( 5837 f"{empty} {error}" 5838 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5839 else f"{error} {empty}" 5840 ) 5841 empty = "" 5842 5843 null = self.sql(expression, "null") 5844 5845 return f"{empty}{error}{null}" 5846 5847 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5848 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5849 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5850 5851 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5852 this = self.sql(expression, "this") 5853 path = self.sql(expression, "path") 5854 5855 passing = self.expressions(expression, "passing") 5856 passing = f" PASSING {passing}" if passing else "" 5857 5858 on_condition = self.sql(expression, "on_condition") 5859 on_condition = f" {on_condition}" if on_condition else "" 5860 5861 path = f"{path}{passing}{on_condition}" 5862 5863 return self.func("JSON_EXISTS", this, path) 5864 5865 def _add_arrayagg_null_filter( 5866 self, 5867 array_agg_sql: str, 5868 array_agg_expr: exp.ArrayAgg, 5869 column_expr: exp.Expr, 5870 ) -> str: 5871 """ 5872 Add NULL filter to ARRAY_AGG if dialect requires it. 5873 5874 Args: 5875 array_agg_sql: The generated ARRAY_AGG SQL string 5876 array_agg_expr: The ArrayAgg expression node 5877 column_expr: The column/expression to filter (before ORDER BY wrapping) 5878 5879 Returns: 5880 SQL string with FILTER clause added if needed 5881 """ 5882 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5883 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5884 if not ( 5885 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5886 ): 5887 return array_agg_sql 5888 5889 parent = array_agg_expr.parent 5890 if isinstance(parent, exp.Filter): 5891 parent_cond = parent.expression.this 5892 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5893 elif column_expr.find(exp.Column): 5894 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5895 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5896 this_sql = ( 5897 self.expressions(column_expr) 5898 if isinstance(column_expr, exp.Distinct) 5899 else self.sql(column_expr) 5900 ) 5901 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5902 5903 return array_agg_sql 5904 5905 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5906 array_agg = self.function_fallback_sql(expression) 5907 column_expr = expression.this 5908 if isinstance(column_expr, exp.Order): 5909 column_expr = column_expr.this 5910 5911 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5912 5913 def slice_sql(self, expression: exp.Slice) -> str: 5914 step = self.sql(expression, "step") 5915 end = self.sql(expression.expression) 5916 begin = self.sql(expression.this) 5917 5918 sql = f"{end}:{step}" if step else end 5919 return f"{begin}:{sql}" if sql else f"{begin}:" 5920 5921 def apply_sql(self, expression: exp.Apply) -> str: 5922 this = self.sql(expression, "this") 5923 expr = self.sql(expression, "expression") 5924 5925 return f"{this} APPLY({expr})" 5926 5927 def _grant_or_revoke_sql( 5928 self, 5929 expression: exp.Grant | exp.Revoke, 5930 keyword: str, 5931 preposition: str, 5932 grant_option_prefix: str = "", 5933 grant_option_suffix: str = "", 5934 ) -> str: 5935 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5936 5937 kind = self.sql(expression, "kind") 5938 kind = f" {kind}" if kind else "" 5939 5940 securable = self.sql(expression, "securable") 5941 securable = f" {securable}" if securable else "" 5942 5943 principals = self.expressions(expression, key="principals", flat=True) 5944 5945 if not expression.args.get("grant_option"): 5946 grant_option_prefix = grant_option_suffix = "" 5947 5948 # cascade for revoke only 5949 cascade = self.sql(expression, "cascade") 5950 cascade = f" {cascade}" if cascade else "" 5951 5952 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5953 5954 def grant_sql(self, expression: exp.Grant) -> str: 5955 return self._grant_or_revoke_sql( 5956 expression, 5957 keyword="GRANT", 5958 preposition="TO", 5959 grant_option_suffix=" WITH GRANT OPTION", 5960 ) 5961 5962 def revoke_sql(self, expression: exp.Revoke) -> str: 5963 return self._grant_or_revoke_sql( 5964 expression, 5965 keyword="REVOKE", 5966 preposition="FROM", 5967 grant_option_prefix="GRANT OPTION FOR ", 5968 ) 5969 5970 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5971 this = self.sql(expression, "this") 5972 columns = self.expressions(expression, flat=True) 5973 columns = f"({columns})" if columns else "" 5974 5975 return f"{this}{columns}" 5976 5977 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5978 this = self.sql(expression, "this") 5979 5980 kind = self.sql(expression, "kind") 5981 kind = f"{kind} " if kind else "" 5982 5983 return f"{kind}{this}" 5984 5985 def columns_sql(self, expression: exp.Columns) -> str: 5986 func = self.function_fallback_sql(expression) 5987 if expression.args.get("unpack"): 5988 func = f"*{func}" 5989 5990 return func 5991 5992 def overlay_sql(self, expression: exp.Overlay) -> str: 5993 this = self.sql(expression, "this") 5994 expr = self.sql(expression, "expression") 5995 from_sql = self.sql(expression, "from_") 5996 for_sql = self.sql(expression, "for_") 5997 for_sql = f" FOR {for_sql}" if for_sql else "" 5998 5999 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6000 6001 @unsupported_args("format") 6002 def todouble_sql(self, expression: exp.ToDouble) -> str: 6003 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6004 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6005 6006 def string_sql(self, expression: exp.String) -> str: 6007 this = expression.this 6008 zone = expression.args.get("zone") 6009 6010 if zone: 6011 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6012 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6013 # set for source_tz to transpile the time conversion before the STRING cast 6014 this = exp.ConvertTimezone( 6015 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6016 ) 6017 6018 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6019 6020 def median_sql(self, expression: exp.Median) -> str: 6021 if not self.SUPPORTS_MEDIAN: 6022 return self.sql( 6023 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6024 ) 6025 6026 return self.function_fallback_sql(expression) 6027 6028 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6029 filler = self.sql(expression, "this") 6030 filler = f" {filler}" if filler else "" 6031 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6032 return f"TRUNCATE{filler} {with_count}" 6033 6034 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6035 if self.SUPPORTS_UNIX_SECONDS: 6036 return self.function_fallback_sql(expression) 6037 6038 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6039 6040 return self.sql( 6041 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6042 ) 6043 6044 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6045 dim = expression.expression 6046 6047 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6048 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6049 if not (dim.is_int and dim.name == "1"): 6050 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6051 dim = None 6052 6053 # If dimension is required but not specified, default initialize it 6054 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6055 dim = exp.Literal.number(1) 6056 6057 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6058 6059 def attach_sql(self, expression: exp.Attach) -> str: 6060 this = self.sql(expression, "this") 6061 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6062 expressions = self.expressions(expression) 6063 expressions = f" ({expressions})" if expressions else "" 6064 6065 return f"ATTACH{exists_sql} {this}{expressions}" 6066 6067 def detach_sql(self, expression: exp.Detach) -> str: 6068 kind = self.sql(expression, "kind") 6069 kind = f" {kind}" if kind else "" 6070 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6071 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6072 exists = " IF EXISTS" if expression.args.get("exists") else "" 6073 if exists: 6074 kind = kind or " DATABASE" 6075 6076 this = self.sql(expression, "this") 6077 this = f" {this}" if this else "" 6078 cluster = self.sql(expression, "cluster") 6079 cluster = f" {cluster}" if cluster else "" 6080 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6081 sync = " SYNC" if expression.args.get("sync") else "" 6082 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6083 6084 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6085 this = self.sql(expression, "this") 6086 value = self.sql(expression, "expression") 6087 value = f" {value}" if value else "" 6088 return f"{this}{value}" 6089 6090 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6091 return ( 6092 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6093 ) 6094 6095 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6096 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6097 encode = f"{encode} {self.sql(expression, 'this')}" 6098 6099 properties = expression.args.get("properties") 6100 if properties: 6101 encode = f"{encode} {self.properties(properties)}" 6102 6103 return encode 6104 6105 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6106 this = self.sql(expression, "this") 6107 include = f"INCLUDE {this}" 6108 6109 column_def = self.sql(expression, "column_def") 6110 if column_def: 6111 include = f"{include} {column_def}" 6112 6113 alias = self.sql(expression, "alias") 6114 if alias: 6115 include = f"{include} AS {alias}" 6116 6117 return include 6118 6119 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6120 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6121 name = f"{prefix} {self.sql(expression, 'this')}" 6122 return self.func("XMLELEMENT", name, *expression.expressions) 6123 6124 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6125 this = self.sql(expression, "this") 6126 expr = self.sql(expression, "expression") 6127 expr = f"({expr})" if expr else "" 6128 return f"{this}{expr}" 6129 6130 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6131 partitions = self.expressions(expression, "partition_expressions") 6132 create = self.expressions(expression, "create_expressions") 6133 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6134 6135 def partitionbyrangepropertydynamic_sql( 6136 self, expression: exp.PartitionByRangePropertyDynamic 6137 ) -> str: 6138 start = self.sql(expression, "start") 6139 end = self.sql(expression, "end") 6140 6141 every = expression.args["every"] 6142 if isinstance(every, exp.Interval) and every.this.is_string: 6143 every.this.replace(exp.Literal.number(every.name)) 6144 6145 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6146 6147 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6148 name = self.sql(expression, "this") 6149 values = self.expressions(expression, flat=True) 6150 6151 return f"NAME {name} VALUE {values}" 6152 6153 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6154 kind = self.sql(expression, "kind") 6155 sample = self.sql(expression, "sample") 6156 return f"SAMPLE {sample} {kind}" 6157 6158 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6159 kind = self.sql(expression, "kind") 6160 option = self.sql(expression, "option") 6161 option = f" {option}" if option else "" 6162 this = self.sql(expression, "this") 6163 this = f" {this}" if this else "" 6164 columns = self.expressions(expression) 6165 columns = f" {columns}" if columns else "" 6166 return f"{kind}{option} STATISTICS{this}{columns}" 6167 6168 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6169 this = self.sql(expression, "this") 6170 columns = self.expressions(expression) 6171 inner_expression = self.sql(expression, "expression") 6172 inner_expression = f" {inner_expression}" if inner_expression else "" 6173 update_options = self.sql(expression, "update_options") 6174 update_options = f" {update_options} UPDATE" if update_options else "" 6175 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6176 6177 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6178 kind = self.sql(expression, "kind") 6179 kind = f" {kind}" if kind else "" 6180 return f"DELETE{kind} STATISTICS" 6181 6182 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6183 inner_expression = self.sql(expression, "expression") 6184 return f"LIST CHAINED ROWS{inner_expression}" 6185 6186 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6187 kind = self.sql(expression, "kind") 6188 this = self.sql(expression, "this") 6189 this = f" {this}" if this else "" 6190 inner_expression = self.sql(expression, "expression") 6191 return f"VALIDATE {kind}{this}{inner_expression}" 6192 6193 def analyze_sql(self, expression: exp.Analyze) -> str: 6194 options = self.expressions(expression, key="options", sep=" ") 6195 options = f" {options}" if options else "" 6196 kind = self.sql(expression, "kind") 6197 kind = f" {kind}" if kind else "" 6198 tables = self.expressions(expression, key="tables", flat=True) 6199 tables = f" {tables}" if tables else "" 6200 mode = self.sql(expression, "mode") 6201 mode = f" {mode}" if mode else "" 6202 properties = self.sql(expression, "properties") 6203 properties = f" {properties}" if properties else "" 6204 partition = self.sql(expression, "partition") 6205 partition = f" {partition}" if partition else "" 6206 inner_expression = self.sql(expression, "expression") 6207 inner_expression = f" {inner_expression}" if inner_expression else "" 6208 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6209 6210 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6211 this = self.sql(expression, "this") 6212 namespaces = self.expressions(expression, key="namespaces") 6213 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6214 passing = self.expressions(expression, key="passing") 6215 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6216 columns = self.expressions(expression, key="columns") 6217 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6218 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6219 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6220 6221 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6222 this = self.sql(expression, "this") 6223 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6224 6225 def export_sql(self, expression: exp.Export) -> str: 6226 this = self.sql(expression, "this") 6227 connection = self.sql(expression, "connection") 6228 connection = f"WITH CONNECTION {connection} " if connection else "" 6229 options = self.sql(expression, "options") 6230 return f"EXPORT DATA {connection}{options} AS {this}" 6231 6232 def declare_sql(self, expression: exp.Declare) -> str: 6233 replace = "OR REPLACE " if expression.args.get("replace") else "" 6234 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6235 6236 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6237 variables = self.expressions(expression, "this") 6238 default = self.sql(expression, "default") 6239 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6240 6241 kind = self.sql(expression, "kind") 6242 if isinstance(expression.args.get("kind"), exp.Schema): 6243 kind = f"TABLE {kind}" 6244 6245 kind = f" {kind}" if kind else "" 6246 6247 return f"{variables}{kind}{default}" 6248 6249 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6250 kind = self.sql(expression, "kind") 6251 this = self.sql(expression, "this") 6252 set = self.sql(expression, "expression") 6253 to = self.sql(expression, "to") 6254 to = f" TO {to}" if to else "" 6255 default = self.sql(expression, "default") 6256 default = f" DEFAULT {default}" if default else "" 6257 using = self.sql(expression, "using") 6258 using = f" USING {using}" if using else "" 6259 6260 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6261 6262 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6263 6264 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6265 params = self.expressions(expression, key="params", flat=True) 6266 return self.func(expression.name, *expression.expressions) + f"({params})" 6267 6268 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6269 return self.func(expression.name, *expression.expressions) 6270 6271 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6272 return self.anonymousaggfunc_sql(expression) 6273 6274 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6275 return self.parameterizedagg_sql(expression) 6276 6277 def show_sql(self, expression: exp.Show) -> str: 6278 self.unsupported("Unsupported SHOW statement") 6279 return "" 6280 6281 def install_sql(self, expression: exp.Install) -> str: 6282 self.unsupported("Unsupported INSTALL statement") 6283 return "" 6284 6285 def liststage_sql(self, expression: exp.ListStage) -> str: 6286 pattern = self.sql(expression, "pattern") 6287 pattern = f" PATTERN = {pattern}" if pattern else "" 6288 return f"LIST {self.sql(expression, 'this')}{pattern}" 6289 6290 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6291 # Snowflake GET/PUT statements: 6292 # PUT <file> <internalStage> <properties> 6293 # GET <internalStage> <file> <properties> 6294 props = expression.args.get("properties") 6295 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6296 this = self.sql(expression, "this") 6297 target = self.sql(expression, "target") 6298 6299 if isinstance(expression, exp.Put): 6300 return f"PUT {this} {target}{props_sql}" 6301 else: 6302 return f"GET {target} {this}{props_sql}" 6303 6304 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6305 this = self.sql(expression, "this") 6306 expr = self.sql(expression, "expression") 6307 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6308 return f"TRANSLATE({this} USING {expr}{with_error})" 6309 6310 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6311 if self.SUPPORTS_DECODE_CASE: 6312 return self.func("DECODE", *expression.expressions) 6313 6314 decode_expr, *expressions = expression.expressions 6315 6316 ifs = [] 6317 for search, result in zip(expressions[::2], expressions[1::2]): 6318 if isinstance(search, exp.Literal): 6319 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6320 elif isinstance(search, exp.Null): 6321 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6322 else: 6323 if isinstance(search, exp.Binary): 6324 search = exp.paren(search) 6325 6326 cond = exp.or_( 6327 decode_expr.eq(search), 6328 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6329 copy=False, 6330 ) 6331 ifs.append(exp.If(this=cond, true=result)) 6332 6333 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6334 return self.sql(case) 6335 6336 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6337 this = self.sql(expression, "this") 6338 this = self.seg(this, sep="") 6339 dimensions = self.expressions( 6340 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6341 ) 6342 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6343 metrics = self.expressions( 6344 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6345 ) 6346 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6347 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6348 facts = self.seg(f"FACTS {facts}") if facts else "" 6349 where = self.sql(expression, "where") 6350 where = self.seg(f"WHERE {where}") if where else "" 6351 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6352 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6353 6354 def getextract_sql(self, expression: exp.GetExtract) -> str: 6355 this = expression.this 6356 expr = expression.expression 6357 6358 if not this.type or not expression.type: 6359 import sqlglot.optimizer.annotate_types 6360 6361 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6362 6363 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6364 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6365 6366 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6367 6368 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6369 return self.sql( 6370 exp.DateAdd( 6371 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6372 expression=expression.this, 6373 unit=exp.var("DAY"), 6374 ) 6375 ) 6376 6377 def space_sql(self: Generator, expression: exp.Space) -> str: 6378 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6379 6380 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6381 return f"BUILD {self.sql(expression, 'this')}" 6382 6383 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6384 method = self.sql(expression, "method") 6385 kind = expression.args.get("kind") 6386 if not kind: 6387 return f"REFRESH {method}" 6388 6389 every = self.sql(expression, "every") 6390 unit = self.sql(expression, "unit") 6391 every = f" EVERY {every} {unit}" if every else "" 6392 starts = self.sql(expression, "starts") 6393 starts = f" STARTS {starts}" if starts else "" 6394 6395 return f"REFRESH {method} ON {kind}{every}{starts}" 6396 6397 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6398 self.unsupported("The model!attribute syntax is not supported") 6399 return "" 6400 6401 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6402 return self.func("DIRECTORY", expression.this) 6403 6404 def uuid_sql(self, expression: exp.Uuid) -> str: 6405 is_string = expression.args.get("is_string", False) 6406 uuid_func_sql = self.func("UUID") 6407 6408 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6409 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6410 6411 return uuid_func_sql 6412 6413 def initcap_sql(self, expression: exp.Initcap) -> str: 6414 delimiters = expression.expression 6415 6416 if delimiters: 6417 # do not generate delimiters arg if we are round-tripping from default delimiters 6418 if ( 6419 delimiters.is_string 6420 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6421 ): 6422 delimiters = None 6423 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6424 self.unsupported("INITCAP does not support custom delimiters") 6425 delimiters = None 6426 6427 return self.func("INITCAP", expression.this, delimiters) 6428 6429 def localtime_sql(self, expression: exp.Localtime) -> str: 6430 this = expression.this 6431 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6432 6433 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6434 this = expression.this 6435 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6436 6437 def weekstart_name(self, expression: exp.WeekStart) -> str: 6438 import sqlglot.dialects.dialect 6439 6440 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6441 this = expression.this.name.upper() 6442 6443 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6444 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6445 6446 if dow_from_week_start_day != dow_from_week_offset: 6447 self.unsupported( 6448 f"WEEK({this}) is not supported; falling back to the default week start day" 6449 ) 6450 6451 return "WEEK" 6452 6453 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6454 name = self.weekstart_name(expression) 6455 6456 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6457 if isinstance(expression.parent, exp.DateTrunc): 6458 return self.sql(exp.Literal.string(name)) 6459 6460 return name 6461 6462 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6463 this = self.expressions(expression) 6464 charset = self.sql(expression, "charset") 6465 using = f" USING {charset}" if charset else "" 6466 return self.func(name, this + using) 6467 6468 def weightstring_sql(self, expression: exp.WeightString) -> str: 6469 to = self.sql(expression, "to") 6470 to = f" AS {to}" if to else "" 6471 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6472 6473 def block_sql(self, expression: exp.Block) -> str: 6474 expressions = self.expressions(expression, sep="; ", flat=True) 6475 begin = "BEGIN " if expression.args.get("begin") else "" 6476 return f"{begin}{expressions}" if expressions else "" 6477 6478 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6479 self.unsupported("Unsupported Inline UDFs syntax") 6480 return "" 6481 6482 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6483 self.unsupported("Unsupported Stored Procedure syntax") 6484 return "" 6485 6486 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6487 self.unsupported("Unsupported If block syntax") 6488 return "" 6489 6490 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6491 self.unsupported("Unsupported Case statement syntax") 6492 return "" 6493 6494 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6495 self.unsupported("Unsupported While block syntax") 6496 return "" 6497 6498 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6499 self.unsupported("Unsupported Loop block syntax") 6500 return "" 6501 6502 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6503 self.unsupported("Unsupported Repeat block syntax") 6504 return "" 6505 6506 def leave_sql(self, expression: exp.Leave) -> str: 6507 self.unsupported("Unsupported Leave syntax") 6508 return "" 6509 6510 def iterate_sql(self, expression: exp.Iterate) -> str: 6511 self.unsupported("Unsupported Iterate syntax") 6512 return "" 6513 6514 def execute_sql(self, expression: exp.Execute) -> str: 6515 self.unsupported("Unsupported Execute syntax") 6516 return "" 6517 6518 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6519 self.unsupported("Unsupported Execute syntax") 6520 return "" 6521 6522 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6523 props = self.expressions(expression, sep=" ") 6524 return f"MODIFY {props}" 6525 6526 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6527 kind = expression.args.get("kind") 6528 return f"USING {kind} {self.sql(expression, 'this')}" 6529 6530 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6531 this = self.sql(expression, "this") 6532 to = self.sql(expression, "to") 6533 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.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 the INTERVAL expression works only with values like '1 day' 338 SINGLE_STRING_INTERVAL = False 339 340 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 341 INTERVAL_ALLOWS_PLURAL_FORM = True 342 343 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 344 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 345 AUTO_REFRESH_BARE_INTERVALS = False 346 347 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 348 LIMIT_FETCH = "ALL" 349 350 # Whether limit and fetch allows expresions or just limits 351 LIMIT_ONLY_LITERALS = False 352 353 # Whether a table is allowed to be renamed with a db 354 RENAME_TABLE_WITH_DB = True 355 356 # The separator for grouping sets and rollups 357 GROUPINGS_SEP = "," 358 359 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 360 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 361 362 # The string used for creating an index on a table 363 INDEX_ON = "ON" 364 365 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 366 INOUT_SEPARATOR = " " 367 368 # Whether join hints should be generated 369 JOIN_HINTS = True 370 371 # Whether directed joins are supported 372 DIRECTED_JOINS = False 373 374 # Whether table hints should be generated 375 TABLE_HINTS = True 376 377 # Whether query hints should be generated 378 QUERY_HINTS = True 379 380 # What kind of separator to use for query hints 381 QUERY_HINT_SEP = ", " 382 383 # Whether comparing against booleans (e.g. x IS TRUE) is supported 384 IS_BOOL_ALLOWED = True 385 386 # Whether casting a fractional number to an integer rounds it instead of truncating it 387 CAST_TO_INT_ROUNDS = False 388 389 # Whether the native integer division operator returns NULL for a zero divisor instead of raising 390 SAFE_INT_DIVISION = False 391 392 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 393 DUPLICATE_KEY_UPDATE_WITH_SET = True 394 395 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 396 LIMIT_IS_TOP = False 397 398 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 399 RETURNING_END = True 400 401 # Whether to generate an unquoted value for EXTRACT's date part argument 402 EXTRACT_ALLOWS_QUOTES = True 403 404 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 405 TZ_TO_WITH_TIME_ZONE = False 406 407 # Whether the NVL2 function is supported 408 NVL2_SUPPORTED = True 409 410 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 411 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 412 413 # Whether VALUES statements can be used as derived tables. 414 # MySQL 5 and Redshift do not allow this, so when False, it will convert 415 # SELECT * VALUES into SELECT UNION 416 VALUES_AS_TABLE = True 417 418 # Whether the word COLUMN is included when adding a column with ALTER TABLE 419 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 420 421 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 422 UNNEST_WITH_ORDINALITY = True 423 424 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 425 SEMI_ANTI_JOIN_WITH_SIDE = True 426 427 # Whether to include the type of a computed column in the CREATE DDL 428 COMPUTED_COLUMN_WITH_TYPE = True 429 430 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 431 SUPPORTS_TABLE_COPY = True 432 433 # Whether parentheses are required around the table sample's expression 434 TABLESAMPLE_REQUIRES_PARENS = True 435 436 # Whether a table sample clause's size needs to be followed by the ROWS keyword 437 TABLESAMPLE_SIZE_IS_ROWS = True 438 439 # The keyword(s) to use when generating a sample clause 440 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 441 442 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 443 TABLESAMPLE_WITH_METHOD = True 444 445 # The keyword to use when specifying the seed of a sample clause 446 TABLESAMPLE_SEED_KEYWORD = "SEED" 447 448 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 449 HISTORICAL_DATA_POST_ALIAS = False 450 451 # Whether COLLATE is a function instead of a binary operator 452 COLLATE_IS_FUNC = False 453 454 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 455 DATA_TYPE_SPECIFIERS_ALLOWED = False 456 457 # Whether conditions require booleans WHERE x = 0 vs WHERE x 458 ENSURE_BOOLS = False 459 460 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 461 CTE_RECURSIVE_KEYWORD_REQUIRED = True 462 463 # Whether CONCAT requires >1 arguments 464 SUPPORTS_SINGLE_ARG_CONCAT = True 465 466 # Whether LAST_DAY function supports a date part argument 467 LAST_DAY_SUPPORTS_DATE_PART = True 468 469 # Whether named columns are allowed in table aliases 470 SUPPORTS_TABLE_ALIAS_COLUMNS = True 471 472 # Whether named columns are allowed in CTE definitions 473 SUPPORTS_NAMED_CTE_COLUMNS = True 474 475 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 476 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 477 478 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 479 PIVOT_ALIAS_WITH_AS = True 480 481 # What delimiter to use for separating JSON key/value pairs 482 JSON_KEY_VALUE_PAIR_SEP = ":" 483 484 # INSERT OVERWRITE TABLE x override 485 INSERT_OVERWRITE = " OVERWRITE TABLE" 486 487 # Whether the SELECT .. INTO syntax is used instead of CTAS 488 SUPPORTS_SELECT_INTO = False 489 490 # Whether UNLOGGED tables can be created 491 SUPPORTS_UNLOGGED_TABLES = False 492 493 # Whether the CREATE TABLE LIKE statement is supported 494 SUPPORTS_CREATE_TABLE_LIKE = True 495 496 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 497 SUPPORTS_MODIFY_COLUMN = False 498 499 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 500 SUPPORTS_CHANGE_COLUMN = False 501 502 # Whether ALTER COLUMN can set a column's nullability together with its type 503 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 504 505 # Whether ALTER COLUMN IF EXISTS is supported 506 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 507 508 # Whether the LikeProperty needs to be specified inside of the schema clause 509 LIKE_PROPERTY_INSIDE_SCHEMA = False 510 511 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 512 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 513 MULTI_ARG_DISTINCT = True 514 515 # Whether the JSON extraction operators expect a value of type JSON 516 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 517 518 # Whether bracketed keys like ["foo"] are supported in JSON paths 519 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 520 521 # Whether to escape keys using single quotes in JSON paths 522 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 523 524 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 525 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 526 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 527 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 528 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 529 530 # The JSONPathPart expressions supported by this dialect 531 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 532 533 # Whether any(f(x) for x in array) can be implemented by this dialect 534 CAN_IMPLEMENT_ARRAY_ANY = False 535 536 # Whether the function TO_NUMBER is supported 537 SUPPORTS_TO_NUMBER = True 538 539 # Whether EXCLUDE in window specification is supported 540 SUPPORTS_WINDOW_EXCLUDE = False 541 542 # Whether or not set op modifiers apply to the outer set op or select. 543 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 544 # True means limit 1 happens after the set op, False means it it happens on y. 545 SET_OP_MODIFIERS = True 546 547 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 548 SET_OP_LIMITS = False 549 550 # Whether set operation operands can be parenthesized without a SELECT wrapper. 551 SET_OP_PARENTHESIZED_OPERANDS = True 552 553 # Whether parameters from COPY statement are wrapped in parentheses 554 COPY_PARAMS_ARE_WRAPPED = True 555 556 # Whether values of params are set with "=" token or empty space 557 COPY_PARAMS_EQ_REQUIRED = False 558 559 # Whether COPY statement has INTO keyword 560 COPY_HAS_INTO_KEYWORD = True 561 562 # Whether the conditional TRY(expression) function is supported 563 TRY_SUPPORTED = True 564 565 # Whether the UESCAPE syntax in unicode strings is supported 566 SUPPORTS_UESCAPE = True 567 568 # Function used to replace escaped unicode codes in unicode strings 569 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 570 571 # The keyword to use when generating a star projection with excluded columns 572 STAR_EXCEPT = "EXCEPT" 573 574 # The HEX function name 575 HEX_FUNC = "HEX" 576 577 # Function that constructs a binary value from a hex-encoded byte string. 578 BYTE_STRING_FUNCTION = "" 579 580 # The keywords to use when prefixing & separating WITH based properties 581 WITH_PROPERTIES_PREFIX = "WITH" 582 583 # Whether to quote the generated expression of exp.JsonPath 584 QUOTE_JSON_PATH = True 585 586 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 587 PAD_FILL_PATTERN_IS_REQUIRED = False 588 589 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 590 SUPPORTS_EXPLODING_PROJECTIONS = True 591 592 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 593 ARRAY_CONCAT_IS_VAR_LEN = True 594 595 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 596 SUPPORTS_CONVERT_TIMEZONE = False 597 598 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 599 SUPPORTS_MEDIAN = True 600 601 # Whether UNIX_SECONDS(timestamp) is supported 602 SUPPORTS_UNIX_SECONDS = False 603 604 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 605 ALTER_SET_WRAPPED = False 606 607 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 608 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 609 # TODO: The normalization should be done by default once we've tested it across all dialects. 610 NORMALIZE_EXTRACT_DATE_PARTS = False 611 612 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 613 PARSE_JSON_NAME: str | None = "PARSE_JSON" 614 615 # The function name of the exp.ArraySize expression 616 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 617 618 # The syntax to use when altering the type of a column 619 ALTER_SET_TYPE = "SET DATA TYPE" 620 621 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 622 # None -> Doesn't support it at all 623 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 624 # True (Postgres) -> Explicitly requires it 625 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 626 627 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 628 SUPPORTS_DECODE_CASE = True 629 630 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 631 SUPPORTS_BETWEEN_FLAGS = False 632 633 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 634 SUPPORTS_LIKE_QUANTIFIERS = True 635 636 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 637 MATCH_AGAINST_TABLE_PREFIX: str | None = None 638 639 # Whether to include the VARIABLE keyword for SET assignments 640 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 641 642 # The keyword to use for default value assignment in DECLARE statements 643 DECLARE_DEFAULT_ASSIGNMENT = "=" 644 645 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 646 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 647 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 648 UPDATE_STATEMENT_SUPPORTS_FROM = True 649 650 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 651 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 652 653 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 654 # - Snowflake: DROP ICEBERG TABLE a.b; 655 # - DuckDB: DROP TABLE a.b; 656 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 657 658 TYPE_MAPPING: t.ClassVar = { 659 exp.DType.DATETIME2: "TIMESTAMP", 660 exp.DType.NCHAR: "CHAR", 661 exp.DType.NVARCHAR: "VARCHAR", 662 exp.DType.MEDIUMTEXT: "TEXT", 663 exp.DType.LONGTEXT: "TEXT", 664 exp.DType.TINYTEXT: "TEXT", 665 exp.DType.BLOB: "VARBINARY", 666 exp.DType.MEDIUMBLOB: "BLOB", 667 exp.DType.LONGBLOB: "BLOB", 668 exp.DType.TINYBLOB: "BLOB", 669 exp.DType.INET: "INET", 670 exp.DType.ROWVERSION: "VARBINARY", 671 exp.DType.SMALLDATETIME: "TIMESTAMP", 672 } 673 674 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 675 676 # mapping of DType to its default parameters, bounds 677 TYPE_PARAM_SETTINGS: t.ClassVar[ 678 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 679 ] = {} 680 681 TIME_PART_SINGULARS: t.ClassVar = { 682 "MICROSECONDS": "MICROSECOND", 683 "SECONDS": "SECOND", 684 "MINUTES": "MINUTE", 685 "HOURS": "HOUR", 686 "DAYS": "DAY", 687 "WEEKS": "WEEK", 688 "MONTHS": "MONTH", 689 "QUARTERS": "QUARTER", 690 "YEARS": "YEAR", 691 } 692 693 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 694 "cluster": lambda self, e: self.sql(e, "cluster"), 695 "distribute": lambda self, e: self.sql(e, "distribute"), 696 "sort": lambda self, e: self.sql(e, "sort"), 697 **AFTER_HAVING_MODIFIER_TRANSFORMS, 698 } 699 700 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 701 702 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 703 704 PARAMETER_TOKEN = "@" 705 NAMED_PLACEHOLDER_TOKEN = ":" 706 707 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 708 709 PROPERTIES_LOCATION: t.ClassVar = { 710 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 711 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 712 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 713 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 714 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 718 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 719 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 720 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 722 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 724 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 726 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 731 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 732 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 734 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 735 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 737 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 741 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 747 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 748 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 749 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 750 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 751 exp.HeapProperty: exp.Properties.Location.POST_WITH, 752 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 753 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 755 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 758 exp.JournalProperty: exp.Properties.Location.POST_NAME, 759 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 761 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 764 exp.LogProperty: exp.Properties.Location.POST_NAME, 765 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 766 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 767 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 768 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 770 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 771 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 773 exp.Order: exp.Properties.Location.POST_SCHEMA, 774 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 776 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 778 exp.Property: exp.Properties.Location.POST_WITH, 779 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 782 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 783 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 784 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 785 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 790 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 791 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 792 exp.Set: exp.Properties.Location.POST_SCHEMA, 793 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.SetProperty: exp.Properties.Location.POST_CREATE, 795 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 796 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 797 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 798 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 799 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 801 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 802 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 805 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 806 exp.Tags: exp.Properties.Location.POST_WITH, 807 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 808 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 809 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 810 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 811 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 812 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 813 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 814 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 815 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 816 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 817 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 818 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 819 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 820 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 821 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 822 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 823 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 824 } 825 826 # Keywords that can't be used as unquoted identifier names 827 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 828 829 # Exprs whose comments are separated from them for better formatting 830 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 831 exp.Command, 832 exp.Create, 833 exp.Describe, 834 exp.Delete, 835 exp.Drop, 836 exp.From, 837 exp.Insert, 838 exp.Join, 839 exp.MultitableInserts, 840 exp.Order, 841 exp.Group, 842 exp.Having, 843 exp.Select, 844 exp.SetOperation, 845 exp.Update, 846 exp.Where, 847 exp.With, 848 ) 849 850 # Exprs that should not have their comments generated in maybe_comment 851 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 852 exp.Binary, 853 exp.SetOperation, 854 ) 855 856 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 857 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 858 exp.Column, 859 exp.Literal, 860 exp.Neg, 861 exp.Paren, 862 ) 863 864 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 865 exp.DType.NVARCHAR, 866 exp.DType.VARCHAR, 867 exp.DType.CHAR, 868 exp.DType.NCHAR, 869 } 870 871 # Exprs that need to have all CTEs under them bubbled up to them 872 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 873 874 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 875 876 MOD_OPERATOR = "%" 877 878 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 879 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 880 exp.Mul, 881 exp.Div, 882 exp.IntDiv, 883 exp.Mod, 884 ) 885 886 # ALTER options that follow the others without a comma, e.g. MySQL's 887 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 888 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 889 890 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 891 892 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 893 894 __slots__ = ( 895 "pretty", 896 "identify", 897 "normalize", 898 "pad", 899 "_indent", 900 "normalize_functions", 901 "unsupported_level", 902 "max_unsupported", 903 "leading_comma", 904 "max_text_width", 905 "comments", 906 "dialect", 907 "unsupported_messages", 908 "_escaped_quote_end", 909 "_escaped_byte_quote_end", 910 "_escaped_identifier_end", 911 "_identifier_escaped_sequences", 912 "_next_name", 913 "_identifier_start", 914 "_identifier_end", 915 "_quote_json_path_key_using_brackets", 916 "_dispatch", 917 ) 918 919 def __init__( 920 self, 921 pretty: bool | int | None = None, 922 identify: str | bool = False, 923 normalize: bool = False, 924 pad: int = 2, 925 indent: int = 2, 926 normalize_functions: str | bool | None = None, 927 unsupported_level: ErrorLevel = ErrorLevel.WARN, 928 max_unsupported: int = 3, 929 leading_comma: bool = False, 930 max_text_width: int = 80, 931 comments: bool = True, 932 dialect: DialectType = None, 933 ): 934 import sqlglot 935 import sqlglot.dialects.dialect 936 937 self.pretty = pretty if pretty is not None else sqlglot.pretty 938 self.identify = identify 939 self.normalize = normalize 940 self.pad = pad 941 self._indent = indent 942 self.unsupported_level = unsupported_level 943 self.max_unsupported = max_unsupported 944 self.leading_comma = leading_comma 945 self.max_text_width = max_text_width 946 self.comments = comments 947 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 948 949 # This is both a Dialect property and a Generator argument, so we prioritize the latter 950 self.normalize_functions = ( 951 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 952 ) 953 954 self.unsupported_messages: list[str] = [] 955 self._escaped_quote_end: str = ( 956 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 957 ) 958 self._escaped_byte_quote_end: str = ( 959 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 960 if self.dialect.BYTE_END 961 else "" 962 ) 963 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 964 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 965 966 self._next_name = name_sequence("_t") 967 968 self._identifier_start = self.dialect.IDENTIFIER_START 969 self._identifier_end = self.dialect.IDENTIFIER_END 970 971 self._quote_json_path_key_using_brackets = True 972 973 cls = type(self) 974 dispatch = _DISPATCH_CACHE.get(cls) 975 if dispatch is None: 976 dispatch = _build_dispatch(cls) 977 _DISPATCH_CACHE[cls] = dispatch 978 self._dispatch = dispatch 979 980 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 981 """ 982 Generates the SQL string corresponding to the given syntax tree. 983 984 Args: 985 expression: The syntax tree. 986 copy: Whether to copy the expression. The generator performs mutations so 987 it is safer to copy. 988 989 Returns: 990 The SQL string corresponding to `expression`. 991 """ 992 if copy: 993 expression = expression.copy() 994 995 expression = self.preprocess(expression) 996 997 self.unsupported_messages = [] 998 sql = self.sql(expression).strip() 999 1000 if self.pretty: 1001 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1002 1003 if self.unsupported_level == ErrorLevel.IGNORE: 1004 return sql 1005 1006 if self.unsupported_level == ErrorLevel.WARN: 1007 for msg in self.unsupported_messages: 1008 logger.warning(msg) 1009 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1010 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1011 1012 return sql 1013 1014 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1015 """Apply generic preprocessing transformations to a given expression.""" 1016 expression = self._move_ctes_to_top_level(expression) 1017 1018 if self.ENSURE_BOOLS: 1019 import sqlglot.transforms 1020 1021 expression = sqlglot.transforms.ensure_bools(expression) 1022 1023 return expression 1024 1025 def _move_ctes_to_top_level(self, expression: E) -> E: 1026 if ( 1027 not expression.parent 1028 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1029 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1030 ): 1031 import sqlglot.transforms 1032 1033 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1034 return expression 1035 1036 def unsupported(self, message: str) -> None: 1037 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1038 raise UnsupportedError(message) 1039 self.unsupported_messages.append(message) 1040 1041 def sep(self, sep: str = " ") -> str: 1042 return f"{sep.strip()}\n" if self.pretty else sep 1043 1044 def seg(self, sql: str, sep: str = " ") -> str: 1045 return f"{self.sep(sep)}{sql}" 1046 1047 def sanitize_comment(self, comment: str) -> str: 1048 comment = " " + comment if comment[0].strip() else comment 1049 comment = comment + " " if comment[-1].strip() else comment 1050 1051 # Escape block comment markers to prevent premature closure or unintended nesting. 1052 # This is necessary because single-line comments (--) are converted to block comments 1053 # (/* */) on output, and any */ in the original text would close the comment early. 1054 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1055 1056 return comment 1057 1058 def maybe_comment( 1059 self, 1060 sql: str, 1061 expression: exp.Expr | None = None, 1062 comments: list[str] | None = None, 1063 separated: bool = False, 1064 ) -> str: 1065 comments = ( 1066 ((expression and expression.comments) if comments is None else comments) # type: ignore 1067 if self.comments 1068 else None 1069 ) 1070 1071 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1072 return sql 1073 1074 comments_list = [ 1075 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1076 for comment in comments 1077 if comment 1078 ] 1079 1080 if not comments_list: 1081 return sql 1082 1083 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1084 comments_sql = self.sep().join(comments_list) 1085 return ( 1086 f"{self.sep()}{comments_sql}{sql}" 1087 if not sql or sql[0].isspace() 1088 else f"{comments_sql}{self.sep()}{sql}" 1089 ) 1090 1091 return f"{sql} {' '.join(comments_list)}" 1092 1093 def wrap(self, expression: exp.Expr | str) -> str: 1094 this_sql = ( 1095 self.sql(expression) 1096 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1097 else self.sql(expression, "this") 1098 ) 1099 if not this_sql: 1100 return "()" 1101 1102 this_sql = self.indent(this_sql, level=1, pad=0) 1103 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1104 1105 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1106 original = self.identify 1107 self.identify = False 1108 result = func(*args, **kwargs) 1109 self.identify = original 1110 return result 1111 1112 def normalize_func(self, name: str) -> str: 1113 if self.normalize_functions == "upper" or self.normalize_functions is True: 1114 return name.upper() 1115 if self.normalize_functions == "lower": 1116 return name.lower() 1117 return name 1118 1119 def indent( 1120 self, 1121 sql: str, 1122 level: int = 0, 1123 pad: int | None = None, 1124 skip_first: bool = False, 1125 skip_last: bool = False, 1126 ) -> str: 1127 if not self.pretty or not sql: 1128 return sql 1129 1130 pad = self.pad if pad is None else pad 1131 lines = sql.split("\n") 1132 1133 return "\n".join( 1134 ( 1135 line 1136 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1137 else f"{' ' * (level * self._indent + pad)}{line}" 1138 ) 1139 for i, line in enumerate(lines) 1140 ) 1141 1142 def sql( 1143 self, 1144 expression: str | exp.Expr | None, 1145 key: str | None = None, 1146 comment: bool = True, 1147 ) -> str: 1148 if not expression: 1149 return "" 1150 1151 if isinstance(expression, str): 1152 return expression 1153 1154 if key: 1155 value = expression.args.get(key) 1156 if value: 1157 return self.sql(value) 1158 return "" 1159 1160 handler = self._dispatch.get(expression.__class__) 1161 1162 if handler: 1163 sql = handler(self, expression) 1164 elif isinstance(expression, exp.Func): 1165 sql = self.function_fallback_sql(expression) 1166 elif isinstance(expression, exp.Property): 1167 sql = self.property_sql(expression) 1168 else: 1169 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1170 1171 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1172 1173 def uncache_sql(self, expression: exp.Uncache) -> str: 1174 table = self.sql(expression, "this") 1175 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1176 return f"UNCACHE TABLE{exists_sql} {table}" 1177 1178 def cache_sql(self, expression: exp.Cache) -> str: 1179 lazy = " LAZY" if expression.args.get("lazy") else "" 1180 table = self.sql(expression, "this") 1181 options = expression.args.get("options") 1182 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1183 sql = self.sql(expression, "expression") 1184 sql = f" AS{self.sep()}{sql}" if sql else "" 1185 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1186 return self.prepend_ctes(expression, sql) 1187 1188 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1189 default = "DEFAULT " if expression.args.get("default") else "" 1190 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1191 1192 def column_parts(self, expression: exp.Column) -> str: 1193 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1194 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1195 return self.sql(expression, "this") 1196 1197 return ".".join( 1198 self.sql(part) 1199 for part in ( 1200 expression.args.get("catalog"), 1201 expression.args.get("db"), 1202 expression.args.get("table"), 1203 expression.args.get("this"), 1204 ) 1205 if part 1206 ) 1207 1208 def column_sql(self, expression: exp.Column) -> str: 1209 join_mark = " (+)" if expression.args.get("join_mark") else "" 1210 1211 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1212 join_mark = "" 1213 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1214 1215 return f"{self.column_parts(expression)}{join_mark}" 1216 1217 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1218 return self.column_sql(expression) 1219 1220 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1221 this = self.sql(expression, "this") 1222 this = f" {this}" if this else "" 1223 position = self.sql(expression, "position") 1224 return f"{position}{this}" 1225 1226 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1227 column = self.sql(expression, "this") 1228 kind = self.sql(expression, "kind") 1229 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1230 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1231 kind = f"{sep}{kind}" if kind else "" 1232 constraints = f" {constraints}" if constraints else "" 1233 position = self.sql(expression, "position") 1234 position = f" {position}" if position else "" 1235 1236 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1237 kind = "" 1238 1239 return f"{exists}{column}{kind}{constraints}{position}" 1240 1241 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1242 this = self.sql(expression, "this") 1243 kind_sql = self.sql(expression, "kind").strip() 1244 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1245 1246 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1247 this = self.sql(expression, "this") 1248 if expression.args.get("not_null"): 1249 persisted = " PERSISTED NOT NULL" 1250 elif expression.args.get("persisted"): 1251 persisted = " PERSISTED" 1252 else: 1253 persisted = "" 1254 1255 return f"AS {this}{persisted}" 1256 1257 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1258 return self.token_sql(TokenType.AUTO_INCREMENT) 1259 1260 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1261 if isinstance(expression.this, list): 1262 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1263 else: 1264 this = self.sql(expression, "this") 1265 1266 return f"COMPRESS {this}" 1267 1268 def generatedasidentitycolumnconstraint_sql( 1269 self, expression: exp.GeneratedAsIdentityColumnConstraint 1270 ) -> str: 1271 this = "" 1272 if expression.this is not None: 1273 on_null = " ON NULL" if expression.args.get("on_null") else "" 1274 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1275 1276 start = expression.args.get("start") 1277 start = f"START WITH {start}" if start else "" 1278 increment = expression.args.get("increment") 1279 increment = f" INCREMENT BY {increment}" if increment else "" 1280 minvalue = expression.args.get("minvalue") 1281 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1282 maxvalue = expression.args.get("maxvalue") 1283 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1284 cycle = expression.args.get("cycle") 1285 cycle_sql = "" 1286 1287 if cycle is not None: 1288 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1289 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1290 1291 sequence_opts = "" 1292 if start or increment or cycle_sql: 1293 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1294 sequence_opts = f" ({sequence_opts.strip()})" 1295 1296 expr = self.sql(expression, "expression") 1297 expr = f"({expr})" if expr else "IDENTITY" 1298 1299 return f"GENERATED{this} AS {expr}{sequence_opts}" 1300 1301 def generatedasrowcolumnconstraint_sql( 1302 self, expression: exp.GeneratedAsRowColumnConstraint 1303 ) -> str: 1304 start = "START" if expression.args.get("start") else "END" 1305 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1306 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1307 1308 def periodforsystemtimeconstraint_sql( 1309 self, expression: exp.PeriodForSystemTimeConstraint 1310 ) -> str: 1311 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1312 1313 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1314 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1315 1316 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1317 desc = expression.args.get("desc") 1318 if desc is not None: 1319 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1320 options = self.expressions(expression, key="options", flat=True, sep=" ") 1321 options = f" {options}" if options else "" 1322 return f"PRIMARY KEY{options}" 1323 1324 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1325 this = self.sql(expression, "this") 1326 this = f" {this}" if this else "" 1327 index_type = expression.args.get("index_type") 1328 index_type = f" USING {index_type}" if index_type else "" 1329 on_conflict = self.sql(expression, "on_conflict") 1330 on_conflict = f" {on_conflict}" if on_conflict else "" 1331 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1332 options = self.expressions(expression, key="options", flat=True, sep=" ") 1333 options = f" {options}" if options else "" 1334 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1335 1336 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1337 input_ = expression.args.get("input_") 1338 output = expression.args.get("output") 1339 variadic = expression.args.get("variadic") 1340 1341 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1342 if variadic: 1343 return "VARIADIC" 1344 1345 if input_ and output: 1346 return f"IN{self.INOUT_SEPARATOR}OUT" 1347 if input_: 1348 return "IN" 1349 if output: 1350 return "OUT" 1351 1352 return "" 1353 1354 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1355 return self.sql(expression, "this") 1356 1357 def create_sql(self, expression: exp.Create) -> str: 1358 kind = self.sql(expression, "kind") 1359 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1360 1361 properties = expression.args.get("properties") 1362 1363 if ( 1364 kind == "TRIGGER" 1365 and properties 1366 and properties.expressions 1367 and isinstance(properties.expressions[0], exp.TriggerProperties) 1368 and properties.expressions[0].args.get("constraint") 1369 ): 1370 kind = f"CONSTRAINT {kind}" 1371 1372 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1373 1374 this = self.createable_sql(expression, properties_locs) 1375 1376 properties_sql = "" 1377 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1378 exp.Properties.Location.POST_WITH 1379 ): 1380 props_ast = exp.Properties( 1381 expressions=[ 1382 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1383 *properties_locs[exp.Properties.Location.POST_WITH], 1384 ] 1385 ) 1386 props_ast.parent = expression 1387 properties_sql = self.sql(props_ast) 1388 1389 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1390 properties_sql = self.sep() + properties_sql 1391 elif not self.pretty: 1392 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1393 properties_sql = f" {properties_sql}" 1394 1395 begin = " BEGIN" if expression.args.get("begin") else "" 1396 1397 expression_sql = self.sql(expression, "expression") 1398 if expression_sql: 1399 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1400 1401 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1402 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1403 ): 1404 postalias_props_sql = "" 1405 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1406 postalias_props_sql = self.properties( 1407 exp.Properties( 1408 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1409 ), 1410 wrapped=False, 1411 ) 1412 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1413 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1414 1415 postindex_props_sql = "" 1416 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1417 postindex_props_sql = self.properties( 1418 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1419 wrapped=False, 1420 prefix=" ", 1421 ) 1422 1423 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1424 indexes = f" {indexes}" if indexes else "" 1425 index_sql = indexes + postindex_props_sql 1426 1427 replace = " OR REPLACE" if expression.args.get("replace") else "" 1428 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1429 unique = " UNIQUE" if expression.args.get("unique") else "" 1430 1431 clustered = expression.args.get("clustered") 1432 if clustered is None: 1433 clustered_sql = "" 1434 elif clustered: 1435 clustered_sql = " CLUSTERED COLUMNSTORE" 1436 else: 1437 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1438 1439 postcreate_props_sql = "" 1440 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1441 postcreate_props_sql = self.properties( 1442 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1443 sep=" ", 1444 prefix=" ", 1445 wrapped=False, 1446 ) 1447 1448 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1449 1450 postexpression_props_sql = "" 1451 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1452 postexpression_props_sql = self.properties( 1453 exp.Properties( 1454 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1455 ), 1456 sep=" ", 1457 prefix=" ", 1458 wrapped=False, 1459 ) 1460 1461 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1462 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1463 no_schema_binding = ( 1464 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1465 ) 1466 1467 clone = self.sql(expression, "clone") 1468 clone = f" {clone}" if clone else "" 1469 1470 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1471 properties_expression = f"{expression_sql}{properties_sql}" 1472 else: 1473 properties_expression = f"{properties_sql}{expression_sql}" 1474 1475 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1476 return self.prepend_ctes(expression, expression_sql) 1477 1478 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1479 start = self.sql(expression, "start") 1480 start = f"START WITH {start}" if start else "" 1481 increment = self.sql(expression, "increment") 1482 increment = f" INCREMENT BY {increment}" if increment else "" 1483 minvalue = self.sql(expression, "minvalue") 1484 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1485 maxvalue = self.sql(expression, "maxvalue") 1486 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1487 owned = self.sql(expression, "owned") 1488 owned = f" OWNED BY {owned}" if owned else "" 1489 1490 cache = expression.args.get("cache") 1491 if cache is None: 1492 cache_str = "" 1493 elif cache is True: 1494 cache_str = " CACHE" 1495 else: 1496 cache_str = f" CACHE {cache}" 1497 1498 options = self.expressions(expression, key="options", flat=True, sep=" ") 1499 options = f" {options}" if options else "" 1500 1501 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1502 1503 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1504 timing = expression.args.get("timing", "") 1505 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1506 timing_events = f"{timing} {events}".strip() if timing or events else "" 1507 1508 parts = [timing_events, "ON", self.sql(expression, "table")] 1509 1510 if referenced_table := expression.args.get("referenced_table"): 1511 parts.extend(["FROM", self.sql(referenced_table)]) 1512 1513 if deferrable := expression.args.get("deferrable"): 1514 parts.append(deferrable) 1515 1516 if initially := expression.args.get("initially"): 1517 parts.append(f"INITIALLY {initially}") 1518 1519 if referencing := expression.args.get("referencing"): 1520 parts.append(self.sql(referencing)) 1521 1522 if for_each := expression.args.get("for_each"): 1523 parts.append(f"FOR EACH {for_each}") 1524 1525 if when := expression.args.get("when"): 1526 parts.append(f"WHEN ({self.sql(when)})") 1527 1528 parts.append(self.sql(expression, "execute")) 1529 1530 return self.sep().join(parts) 1531 1532 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1533 parts = [] 1534 1535 if old_alias := expression.args.get("old"): 1536 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1537 1538 if new_alias := expression.args.get("new"): 1539 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1540 1541 return f"REFERENCING {' '.join(parts)}" 1542 1543 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1544 columns = expression.args.get("columns") 1545 if columns: 1546 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1547 1548 return self.sql(expression, "this") 1549 1550 def clone_sql(self, expression: exp.Clone) -> str: 1551 this = self.sql(expression, "this") 1552 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1553 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1554 return f"{shallow}{keyword} {this}" 1555 1556 def describe_sql(self, expression: exp.Describe) -> str: 1557 style = expression.args.get("style") 1558 style = f" {style}" if style else "" 1559 partition = self.sql(expression, "partition") 1560 partition = f" {partition}" if partition else "" 1561 format = self.sql(expression, "format") 1562 format = f" {format}" if format else "" 1563 as_json = " AS JSON" if expression.args.get("as_json") else "" 1564 1565 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1566 1567 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1568 tag = self.sql(expression, "tag") 1569 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1570 1571 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1572 with_ = self.sql(expression, "with_") 1573 if with_: 1574 sql = f"{with_}{self.sep()}{sql}" 1575 return sql 1576 1577 def with_sql(self, expression: exp.With) -> str: 1578 udfs = self.expressions(expression, key="udfs", flat=True) 1579 udfs = f"WITH {udfs}" if udfs else "" 1580 1581 sql = self.expressions(expression, flat=True) 1582 1583 recursive = ( 1584 "RECURSIVE " 1585 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1586 else "" 1587 ) 1588 search = self.sql(expression, "search") 1589 search = f" {search}" if search else "" 1590 cycle = self.sql(expression, "cycle") 1591 cycle = f" {cycle}" if cycle else "" 1592 1593 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1594 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1595 1596 def cte_sql(self, expression: exp.CTE) -> str: 1597 alias = expression.args.get("alias") 1598 if alias: 1599 alias.add_comments(expression.pop_comments()) 1600 1601 alias_sql = self.sql(expression, "alias") 1602 1603 materialized = expression.args.get("materialized") 1604 if materialized is False: 1605 materialized = "NOT MATERIALIZED " 1606 elif materialized: 1607 materialized = "MATERIALIZED " 1608 1609 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1610 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1611 1612 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1613 1614 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1615 alias = self.sql(expression, "this") 1616 columns = self.expressions(expression, key="columns", flat=True) 1617 columns = f"({columns})" if columns else "" 1618 1619 if ( 1620 columns 1621 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1622 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1623 ): 1624 columns = "" 1625 self.unsupported("Named columns are not supported in table alias.") 1626 1627 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1628 alias = self._next_name() 1629 1630 return f"{alias}{columns}" 1631 1632 def bitstring_sql(self, expression: exp.BitString) -> str: 1633 this = self.sql(expression, "this") 1634 if self.dialect.BIT_START: 1635 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1636 return f"{int(this, 2)}" 1637 1638 def hexstring_sql( 1639 self, expression: exp.HexString, binary_function_repr: str | None = None 1640 ) -> str: 1641 this = self.sql(expression, "this") 1642 is_integer_type = expression.args.get("is_integer") 1643 1644 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1645 not self.dialect.HEX_START and not binary_function_repr 1646 ): 1647 # Integer representation will be returned if: 1648 # - The read dialect treats the hex value as integer literal but not the write 1649 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1650 return f"{int(this, 16)}" 1651 1652 if not is_integer_type: 1653 # Read dialect treats the hex value as BINARY/BLOB 1654 if binary_function_repr: 1655 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1656 return self.func(binary_function_repr, exp.Literal.string(this)) 1657 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1658 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1659 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1660 1661 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1662 1663 def bytestring_sql(self, expression: exp.ByteString) -> str: 1664 this = self.sql(expression, "this") 1665 is_bytes = bool(expression.args.get("is_bytes")) 1666 if is_bytes and self.BYTE_STRING_FUNCTION: 1667 # Hex digits can be quoted directly without escaping. 1668 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1669 1670 if self.dialect.BYTE_START: 1671 escaped_byte_string = self.escape_str( 1672 this, 1673 escape_backslash=bool( 1674 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1675 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1676 ), 1677 delimiter=self.dialect.BYTE_END, 1678 escaped_delimiter=self._escaped_byte_quote_end, 1679 is_byte_string=True, 1680 is_bytes=is_bytes, 1681 ) 1682 delimited_byte_string = ( 1683 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1684 ) 1685 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1686 return self.sql( 1687 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1688 ) 1689 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1690 return self.sql( 1691 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1692 ) 1693 1694 return delimited_byte_string 1695 1696 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1697 return self.sql(exp.Literal.string(this)) 1698 1699 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1700 return "" 1701 1702 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1703 this = self.sql(expression, "this") 1704 escape = expression.args.get("escape") 1705 unicode_start = self.dialect.UNICODE_START 1706 1707 if unicode_start: 1708 escape_substitute = r"\\\1" 1709 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1710 else: 1711 escape_substitute = r"\\u\1" 1712 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1713 1714 if escape: 1715 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1716 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1717 else: 1718 escape_pattern = ESCAPED_UNICODE_RE 1719 escape_sql = "" 1720 1721 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1722 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1723 1724 if unicode_start: 1725 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1726 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1727 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1728 else: 1729 this = self.escape_str(this, escape_backslash=False) 1730 1731 return f"{left_quote}{this}{right_quote}{escape_sql}" 1732 1733 def rawstring_sql(self, expression: exp.RawString) -> str: 1734 string = expression.this 1735 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1736 string = string.replace("\\", "\\\\") 1737 1738 string = self.escape_str(string, escape_backslash=False) 1739 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1740 1741 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1742 this = self.sql(expression, "this") 1743 specifier = self.sql(expression, "expression") 1744 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1745 return f"{this}{specifier}" 1746 1747 def datatype_param_bound_limiter( 1748 self, 1749 expression: exp.DataType, 1750 type_value: exp.DType, 1751 defaults: tuple[int, ...], 1752 bounds: tuple[int | None, ...], 1753 ) -> exp.DataType: 1754 params = expression.expressions 1755 1756 if not params: 1757 if defaults: 1758 expression.set( 1759 "expressions", 1760 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1761 ) 1762 return expression 1763 1764 if not bounds: 1765 return expression 1766 1767 for i, param in enumerate(params): 1768 bound = bounds[i] if i < len(bounds) else None 1769 if bound is None: 1770 continue 1771 1772 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1773 value = ( 1774 param_value.to_py() 1775 if isinstance(param_value, exp.Literal) and param_value.is_number 1776 else None 1777 ) 1778 if isinstance(value, (int, Decimal)) and value > bound: 1779 self.unsupported( 1780 f"{type_value.value} parameter {param_value.name} exceeds " 1781 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1782 ) 1783 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1784 1785 return expression 1786 1787 def datatype_sql(self, expression: exp.DataType) -> str: 1788 nested = "" 1789 values = "" 1790 1791 expr_nested = expression.args.get("nested") 1792 type_value = expression.this 1793 1794 if ( 1795 not expr_nested 1796 and isinstance(type_value, exp.DType) 1797 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1798 ): 1799 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1800 1801 interior = ( 1802 self.expressions( 1803 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1804 ) 1805 if expr_nested and self.pretty 1806 else self.expressions(expression, flat=True) 1807 ) 1808 1809 if type_value in self.UNSUPPORTED_TYPES: 1810 self.unsupported( 1811 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1812 ) 1813 1814 type_sql: t.Any = "" 1815 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1816 type_sql = self.sql(expression, "kind") 1817 elif type_value == exp.DType.CHARACTER_SET: 1818 char = f"CHAR({interior})" if interior else "CHAR" 1819 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1820 else: 1821 type_sql = ( 1822 self.TYPE_MAPPING.get(type_value, type_value.value) 1823 if isinstance(type_value, exp.DType) 1824 else type_value 1825 ) 1826 1827 if interior: 1828 if expr_nested: 1829 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1830 if expression.args.get("values") is not None: 1831 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1832 values = self.expressions(expression, key="values", flat=True) 1833 values = f"{delimiters[0]}{values}{delimiters[1]}" 1834 elif type_value == exp.DType.INTERVAL: 1835 nested = f" {interior}" 1836 else: 1837 nested = f"({interior})" 1838 1839 type_sql = f"{type_sql}{nested}{values}" 1840 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1841 exp.DType.TIMETZ, 1842 exp.DType.TIMESTAMPTZ, 1843 ): 1844 type_sql = f"{type_sql} WITH TIME ZONE" 1845 1846 collate = self.sql(expression, "collate") 1847 if collate: 1848 type_sql = f"{type_sql} COLLATE {collate}" 1849 1850 return type_sql 1851 1852 def directory_sql(self, expression: exp.Directory) -> str: 1853 local = "LOCAL " if expression.args.get("local") else "" 1854 row_format = self.sql(expression, "row_format") 1855 row_format = f" {row_format}" if row_format else "" 1856 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1857 1858 def delete_sql(self, expression: exp.Delete) -> str: 1859 hint = self.sql(expression, "hint") 1860 this = self.sql(expression, "this") 1861 this = f" FROM {this}" if this else "" 1862 using = self.expressions(expression, key="using") 1863 using = f" USING {using}" if using else "" 1864 cluster = self.sql(expression, "cluster") 1865 cluster = f" {cluster}" if cluster else "" 1866 where = self.sql(expression, "where") 1867 returning = self.sql(expression, "returning") 1868 order = self.sql(expression, "order") 1869 limit = self.sql(expression, "limit") 1870 tables = self.expressions(expression, key="tables") 1871 tables = f" {tables}" if tables else "" 1872 if self.RETURNING_END: 1873 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1874 else: 1875 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1876 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1877 1878 def drop_sql(self, expression: exp.Drop) -> str: 1879 tables = self.expressions(expression, key="tables", flat=True) 1880 expressions = self.expressions(expression, flat=True) 1881 expressions = f" ({expressions})" if expressions else "" 1882 kind = expression.args["kind"] 1883 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1884 iceberg = ( 1885 " ICEBERG" 1886 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1887 else "" 1888 ) 1889 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1890 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1891 on_cluster = self.sql(expression, "cluster") 1892 on_cluster = f" {on_cluster}" if on_cluster else "" 1893 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1894 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1895 cascade = " CASCADE" if expression.args.get("cascade") else "" 1896 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1897 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1898 purge = " PURGE" if expression.args.get("purge") else "" 1899 sync = " SYNC" if expression.args.get("sync") else "" 1900 force = " FORCE" if expression.args.get("force") else "" 1901 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1902 1903 def set_operation(self, expression: exp.SetOperation) -> str: 1904 op_type = type(expression) 1905 op_name = op_type.key.upper() 1906 1907 distinct = expression.args.get("distinct") 1908 if ( 1909 distinct is False 1910 and op_type in (exp.Except, exp.Intersect) 1911 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1912 ): 1913 self.unsupported(f"{op_name} ALL is not supported") 1914 1915 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1916 1917 if distinct is None: 1918 distinct = default_distinct 1919 if distinct is None: 1920 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1921 1922 if distinct is default_distinct: 1923 distinct_or_all = "" 1924 else: 1925 distinct_or_all = " DISTINCT" if distinct else " ALL" 1926 1927 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1928 side_kind = f"{side_kind} " if side_kind else "" 1929 1930 by_name = " BY NAME" if expression.args.get("by_name") else "" 1931 on = self.expressions(expression, key="on", flat=True) 1932 on = f" ON ({on})" if on else "" 1933 1934 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1935 1936 def set_operations(self, expression: exp.SetOperation) -> str: 1937 if not self.SET_OP_MODIFIERS: 1938 limit = expression.args.get("limit") 1939 order = expression.args.get("order") 1940 offset = expression.args.get("offset") 1941 1942 if limit or order or offset: 1943 select = self._move_ctes_to_top_level( 1944 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1945 ) 1946 1947 for arg in ("limit", "order", "offset"): 1948 if value := expression.args.get(arg): 1949 select.set(arg, value.pop()) 1950 return self.sql(select) 1951 1952 sqls: list[str] = [] 1953 stack: list[str | exp.Expr] = [expression] 1954 1955 while stack: 1956 node = stack.pop() 1957 1958 if isinstance(node, exp.SetOperation): 1959 stack.append(node.expression) 1960 stack.append( 1961 self.maybe_comment( 1962 self.set_operation(node), comments=node.comments, separated=True 1963 ) 1964 ) 1965 stack.append(node.this) 1966 else: 1967 if ( 1968 not self.SET_OP_LIMITS 1969 and isinstance(node, exp.Select) 1970 and node.args.get("limit") 1971 ): 1972 node = node.subquery(copy=False) 1973 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1974 node = exp.select("*").from_(node, copy=False) 1975 sqls.append(self.sql(node)) 1976 1977 this = self.sep().join(sqls) 1978 this = self.query_modifiers(expression, this) 1979 return self.prepend_ctes(expression, this) 1980 1981 def fetch_sql(self, expression: exp.Fetch) -> str: 1982 direction = expression.args.get("direction") 1983 direction = f" {direction}" if direction else "" 1984 count = self.sql(expression, "count") 1985 count = f" {count}" if count else "" 1986 limit_options = self.sql(expression, "limit_options") 1987 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1988 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1989 1990 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1991 percent = " PERCENT" if expression.args.get("percent") else "" 1992 rows = " ROWS" if expression.args.get("rows") else "" 1993 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1994 if not with_ties and rows: 1995 with_ties = " ONLY" 1996 return f"{percent}{rows}{with_ties}" 1997 1998 def filter_sql(self, expression: exp.Filter) -> str: 1999 this = self.sql(expression, "this") 2000 where = self.sql(expression, "expression").strip() 2001 return f"{this} FILTER({where})" 2002 2003 def hint_sql(self, expression: exp.Hint) -> str: 2004 if not self.QUERY_HINTS: 2005 self.unsupported("Hints are not supported") 2006 return "" 2007 2008 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2009 2010 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2011 using = self.sql(expression, "using") 2012 using = f" USING {using}" if using else "" 2013 columns = self.expressions(expression, key="columns", flat=True) 2014 columns = f"({columns})" if columns else "" 2015 partition_by = self.expressions(expression, key="partition_by", flat=True) 2016 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2017 where = self.sql(expression, "where") 2018 include = self.expressions(expression, key="include", flat=True) 2019 if include: 2020 include = f" INCLUDE ({include})" 2021 with_storage = self.expressions(expression, key="with_storage", flat=True) 2022 with_storage = f" WITH ({with_storage})" if with_storage else "" 2023 tablespace = self.sql(expression, "tablespace") 2024 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 2025 on = self.sql(expression, "on") 2026 on = f" ON {on}" if on else "" 2027 2028 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2029 2030 def index_sql(self, expression: exp.Index) -> str: 2031 unique = "UNIQUE " if expression.args.get("unique") else "" 2032 primary = "PRIMARY " if expression.args.get("primary") else "" 2033 amp = "AMP " if expression.args.get("amp") else "" 2034 name = self.sql(expression, "this") 2035 name = f"{name} " if name else "" 2036 table = self.sql(expression, "table") 2037 table = f"{self.INDEX_ON} {table}" if table else "" 2038 2039 index = "INDEX " if not table else "" 2040 2041 params = self.sql(expression, "params") 2042 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2043 2044 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2045 this = expression.this 2046 if this and this.is_string: 2047 resolved = maybe_parse(this.name).sql(self.dialect) 2048 if "expressions" in expression.args: 2049 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2050 # We can't safely emit the call to other dialects since name/arg semantics may differ 2051 self.unsupported( 2052 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2053 ) 2054 return resolved 2055 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2056 return self.func("IDENTIFIER", this) 2057 2058 def identifier_sql(self, expression: exp.Identifier) -> str: 2059 text = expression.name 2060 lower = text.lower() 2061 quoted = expression.quoted 2062 text = lower if self.normalize and not quoted else text 2063 if self._identifier_escaped_sequences: 2064 for char, escaped in self._identifier_escaped_sequences.items(): 2065 text = text.replace(char, escaped) 2066 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2067 if ( 2068 quoted 2069 or self.dialect.can_quote(expression, self.identify) 2070 or lower in self.RESERVED_KEYWORDS 2071 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2072 ): 2073 text = ( 2074 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2075 ) 2076 return text 2077 2078 def hex_sql(self, expression: exp.Hex) -> str: 2079 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2080 if self.dialect.HEX_LOWERCASE: 2081 text = self.func("LOWER", text) 2082 2083 return text 2084 2085 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2086 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2087 if not self.dialect.HEX_LOWERCASE: 2088 text = self.func("LOWER", text) 2089 return text 2090 2091 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2092 input_format = self.sql(expression, "input_format") 2093 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2094 output_format = self.sql(expression, "output_format") 2095 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2096 return self.sep().join((input_format, output_format)) 2097 2098 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2099 string = self.sql(exp.Literal.string(expression.name)) 2100 return f"{prefix}{string}" 2101 2102 def partition_sql(self, expression: exp.Partition) -> str: 2103 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2104 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2105 2106 def properties_sql(self, expression: exp.Properties) -> str: 2107 root_properties = [] 2108 with_properties = [] 2109 2110 for p in expression.expressions: 2111 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2112 if p_loc == exp.Properties.Location.POST_WITH: 2113 with_properties.append(p) 2114 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2115 root_properties.append(p) 2116 2117 root_props_ast = exp.Properties(expressions=root_properties) 2118 root_props_ast.parent = expression.parent 2119 2120 with_props_ast = exp.Properties(expressions=with_properties) 2121 with_props_ast.parent = expression.parent 2122 2123 root_props = self.root_properties(root_props_ast) 2124 with_props = self.with_properties(with_props_ast) 2125 2126 if root_props and with_props and not self.pretty: 2127 with_props = " " + with_props 2128 2129 return root_props + with_props 2130 2131 def root_properties(self, properties: exp.Properties) -> str: 2132 if properties.expressions: 2133 return self.expressions(properties, indent=False, sep=" ") 2134 return "" 2135 2136 def properties( 2137 self, 2138 properties: exp.Properties, 2139 prefix: str = "", 2140 sep: str = ", ", 2141 suffix: str = "", 2142 wrapped: bool = True, 2143 ) -> str: 2144 if properties.expressions: 2145 expressions = self.expressions(properties, sep=sep, indent=False) 2146 if expressions: 2147 expressions = self.wrap(expressions) if wrapped else expressions 2148 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2149 return "" 2150 2151 def with_properties(self, properties: exp.Properties) -> str: 2152 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2153 2154 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2155 properties_locs = defaultdict(list) 2156 for p in properties.expressions: 2157 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2158 if p_loc != exp.Properties.Location.UNSUPPORTED: 2159 properties_locs[p_loc].append(p) 2160 else: 2161 self.unsupported(f"Unsupported property {p.key}") 2162 2163 return properties_locs 2164 2165 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2166 if isinstance(expression.this, exp.Dot): 2167 return self.sql(expression, "this") 2168 return f"'{expression.name}'" if string_key else expression.name 2169 2170 def property_sql(self, expression: exp.Property) -> str: 2171 property_cls = expression.__class__ 2172 if property_cls == exp.Property: 2173 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2174 2175 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2176 if not property_name: 2177 self.unsupported(f"Unsupported property {expression.key}") 2178 2179 return f"{property_name}={self.sql(expression, 'this')}" 2180 2181 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2182 return f"UUID {self.sql(expression, 'this')}" 2183 2184 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2185 if self.SUPPORTS_CREATE_TABLE_LIKE: 2186 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2187 options = f" {options}" if options else "" 2188 2189 like = f"LIKE {self.sql(expression, 'this')}{options}" 2190 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2191 like = f"({like})" 2192 2193 return like 2194 2195 if expression.expressions: 2196 self.unsupported("Transpilation of LIKE property options is unsupported") 2197 2198 select = exp.select("*").from_(expression.this).limit(0) 2199 return f"AS {self.sql(select)}" 2200 2201 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2202 no = "NO " if expression.args.get("no") else "" 2203 protection = " PROTECTION" if expression.args.get("protection") else "" 2204 return f"{no}FALLBACK{protection}" 2205 2206 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2207 no = "NO " if expression.args.get("no") else "" 2208 local = expression.args.get("local") 2209 local = f"{local} " if local else "" 2210 dual = "DUAL " if expression.args.get("dual") else "" 2211 before = "BEFORE " if expression.args.get("before") else "" 2212 after = "AFTER " if expression.args.get("after") else "" 2213 return f"{no}{local}{dual}{before}{after}JOURNAL" 2214 2215 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2216 freespace = self.sql(expression, "this") 2217 percent = " PERCENT" if expression.args.get("percent") else "" 2218 return f"FREESPACE={freespace}{percent}" 2219 2220 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2221 if expression.args.get("default"): 2222 property = "DEFAULT" 2223 elif expression.args.get("on"): 2224 property = "ON" 2225 else: 2226 property = "OFF" 2227 return f"CHECKSUM={property}" 2228 2229 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2230 if expression.args.get("no"): 2231 return "NO MERGEBLOCKRATIO" 2232 if expression.args.get("default"): 2233 return "DEFAULT MERGEBLOCKRATIO" 2234 2235 percent = " PERCENT" if expression.args.get("percent") else "" 2236 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2237 2238 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2239 expressions = self.expressions(expression, flat=True) 2240 expressions = f"({expressions})" if expressions else "" 2241 return f"USING {self.sql(expression, 'this')}{expressions}" 2242 2243 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2244 default = expression.args.get("default") 2245 minimum = expression.args.get("minimum") 2246 maximum = expression.args.get("maximum") 2247 if default or minimum or maximum: 2248 if default: 2249 prop = "DEFAULT" 2250 elif minimum: 2251 prop = "MINIMUM" 2252 else: 2253 prop = "MAXIMUM" 2254 return f"{prop} DATABLOCKSIZE" 2255 units = expression.args.get("units") 2256 units = f" {units}" if units else "" 2257 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2258 2259 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2260 autotemp = expression.args.get("autotemp") 2261 always = expression.args.get("always") 2262 default = expression.args.get("default") 2263 manual = expression.args.get("manual") 2264 never = expression.args.get("never") 2265 2266 if autotemp is not None: 2267 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2268 elif always: 2269 prop = "ALWAYS" 2270 elif default: 2271 prop = "DEFAULT" 2272 elif manual: 2273 prop = "MANUAL" 2274 elif never: 2275 prop = "NEVER" 2276 return f"BLOCKCOMPRESSION={prop}" 2277 2278 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2279 no = expression.args.get("no") 2280 no = " NO" if no else "" 2281 concurrent = expression.args.get("concurrent") 2282 concurrent = " CONCURRENT" if concurrent else "" 2283 target = self.sql(expression, "target") 2284 target = f" {target}" if target else "" 2285 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2286 2287 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2288 if isinstance(expression.this, list): 2289 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2290 if expression.this: 2291 modulus = self.sql(expression, "this") 2292 remainder = self.sql(expression, "expression") 2293 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2294 2295 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2296 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2297 return f"FROM ({from_expressions}) TO ({to_expressions})" 2298 2299 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2300 this = self.sql(expression, "this") 2301 2302 for_values_or_default = expression.expression 2303 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2304 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2305 else: 2306 for_values_or_default = " DEFAULT" 2307 2308 return f"PARTITION OF {this}{for_values_or_default}" 2309 2310 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2311 kind = expression.args.get("kind") 2312 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2313 for_or_in = expression.args.get("for_or_in") 2314 for_or_in = f" {for_or_in}" if for_or_in else "" 2315 lock_type = expression.args.get("lock_type") 2316 override = " OVERRIDE" if expression.args.get("override") else "" 2317 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2318 2319 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2320 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2321 statistics = expression.args.get("statistics") 2322 statistics_sql = "" 2323 if statistics is not None: 2324 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2325 return f"{data_sql}{statistics_sql}" 2326 2327 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2328 this = self.sql(expression, "this") 2329 this = f"HISTORY_TABLE={this}" if this else "" 2330 data_consistency: str | None = self.sql(expression, "data_consistency") 2331 data_consistency = ( 2332 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2333 ) 2334 retention_period: str | None = self.sql(expression, "retention_period") 2335 retention_period = ( 2336 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2337 ) 2338 2339 if this: 2340 on_sql = self.func("ON", this, data_consistency, retention_period) 2341 else: 2342 on_sql = "ON" if expression.args.get("on") else "OFF" 2343 2344 sql = f"SYSTEM_VERSIONING={on_sql}" 2345 2346 return f"WITH({sql})" if expression.args.get("with_") else sql 2347 2348 def insert_sql(self, expression: exp.Insert) -> str: 2349 hint = self.sql(expression, "hint") 2350 overwrite = expression.args.get("overwrite") 2351 2352 if isinstance(expression.this, exp.Directory): 2353 this = " OVERWRITE" if overwrite else " INTO" 2354 else: 2355 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2356 2357 stored = self.sql(expression, "stored") 2358 stored = f" {stored}" if stored else "" 2359 alternative = expression.args.get("alternative") 2360 alternative = f" OR {alternative}" if alternative else "" 2361 ignore = " IGNORE" if expression.args.get("ignore") else "" 2362 is_function = expression.args.get("is_function") 2363 if is_function: 2364 this = f"{this} FUNCTION" 2365 this = f"{this} {self.sql(expression, 'this')}" 2366 2367 exists = " IF EXISTS" if expression.args.get("exists") else "" 2368 where = self.sql(expression, "where") 2369 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2370 using = self.expressions(expression, key="using", flat=True) 2371 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2372 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2373 on_conflict = self.sql(expression, "conflict") 2374 on_conflict = f" {on_conflict}" if on_conflict else "" 2375 by_name = " BY NAME" if expression.args.get("by_name") else "" 2376 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2377 returning = self.sql(expression, "returning") 2378 2379 if self.RETURNING_END: 2380 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2381 else: 2382 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2383 2384 partition_by = self.sql(expression, "partition") 2385 partition_by = f" {partition_by}" if partition_by else "" 2386 settings = self.sql(expression, "settings") 2387 settings = f" {settings}" if settings else "" 2388 2389 source = self.sql(expression, "source") 2390 source = f"TABLE {source}" if source else "" 2391 2392 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2393 return self.prepend_ctes(expression, sql) 2394 2395 def introducer_sql(self, expression: exp.Introducer) -> str: 2396 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2397 2398 def kill_sql(self, expression: exp.Kill) -> str: 2399 kind = self.sql(expression, "kind") 2400 kind = f" {kind}" if kind else "" 2401 this = self.sql(expression, "this") 2402 this = f" {this}" if this else "" 2403 return f"KILL{kind}{this}" 2404 2405 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2406 return expression.name 2407 2408 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2409 return expression.name 2410 2411 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2412 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2413 2414 constraint = self.sql(expression, "constraint") 2415 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2416 2417 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2418 if conflict_keys: 2419 conflict_keys = f"({conflict_keys})" 2420 2421 index_predicate = self.sql(expression, "index_predicate") 2422 conflict_keys = f"{conflict_keys}{index_predicate} " 2423 2424 action = self.sql(expression, "action") 2425 2426 expressions = self.expressions(expression, flat=True) 2427 if expressions: 2428 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2429 expressions = f" {set_keyword}{expressions}" 2430 2431 where = self.sql(expression, "where") 2432 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2433 2434 def returning_sql(self, expression: exp.Returning) -> str: 2435 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2436 2437 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2438 fields = self.sql(expression, "fields") 2439 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2440 escaped = self.sql(expression, "escaped") 2441 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2442 items = self.sql(expression, "collection_items") 2443 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2444 keys = self.sql(expression, "map_keys") 2445 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2446 lines = self.sql(expression, "lines") 2447 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2448 null = self.sql(expression, "null") 2449 null = f" NULL DEFINED AS {null}" if null else "" 2450 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2451 2452 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2453 return f"WITH ({self.expressions(expression, flat=True)})" 2454 2455 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2456 this = f"{self.sql(expression, 'this')} INDEX" 2457 target = self.sql(expression, "target") 2458 target = f" FOR {target}" if target else "" 2459 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2460 2461 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2462 this = self.sql(expression, "this") 2463 kind = self.sql(expression, "kind") 2464 expr = self.sql(expression, "expression") 2465 return f"{this} ({kind} => {expr})" 2466 2467 def table_parts(self, expression: exp.Table) -> str: 2468 return ".".join( 2469 self.sql(part) 2470 for part in ( 2471 expression.args.get("catalog"), 2472 expression.args.get("db"), 2473 expression.args.get("this"), 2474 ) 2475 if part is not None 2476 ) 2477 2478 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2479 table = self.table_parts(expression) 2480 only = "ONLY " if expression.args.get("only") else "" 2481 partition = self.sql(expression, "partition") 2482 partition = f" {partition}" if partition else "" 2483 version = self.sql(expression, "version") 2484 version = f" {version}" if version else "" 2485 alias = self.sql(expression, "alias") 2486 alias = f"{sep}{alias}" if alias else "" 2487 2488 sample = self.sql(expression, "sample") 2489 post_alias = "" 2490 pre_alias = "" 2491 2492 if self.dialect.ALIAS_POST_TABLESAMPLE: 2493 pre_alias = sample 2494 else: 2495 post_alias = sample 2496 2497 if self.dialect.ALIAS_POST_VERSION: 2498 pre_alias = f"{pre_alias}{version}" 2499 else: 2500 post_alias = f"{post_alias}{version}" 2501 2502 hints = self.expressions(expression, key="hints", sep=" ") 2503 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2504 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2505 joins = self.indent( 2506 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2507 ) 2508 laterals = self.expressions(expression, key="laterals", sep="") 2509 2510 file_format = self.sql(expression, "format") 2511 pattern = self.sql(expression, "pattern") 2512 if file_format: 2513 pattern = f", PATTERN => {pattern}" if pattern else "" 2514 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2515 elif pattern: 2516 file_format = f" (PATTERN => {pattern})" 2517 2518 ordinality = expression.args.get("ordinality") or "" 2519 if ordinality: 2520 ordinality = f" WITH ORDINALITY{alias}" 2521 alias = "" 2522 2523 when = self.sql(expression, "when") 2524 if when: 2525 if self.HISTORICAL_DATA_POST_ALIAS: 2526 alias = f"{alias} {when}" 2527 else: 2528 table = f"{table} {when}" 2529 2530 changes = self.sql(expression, "changes") 2531 changes = f" {changes}" if changes else "" 2532 2533 rows_from = self.expressions(expression, key="rows_from") 2534 if rows_from: 2535 table = f"ROWS FROM {self.wrap(rows_from)}" 2536 2537 indexed = expression.args.get("indexed") 2538 if indexed is not None: 2539 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2540 else: 2541 indexed = "" 2542 2543 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2544 2545 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2546 table = self.func("TABLE", expression.this) 2547 alias = self.sql(expression, "alias") 2548 alias = f" AS {alias}" if alias else "" 2549 sample = self.sql(expression, "sample") 2550 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2551 joins = self.indent( 2552 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2553 ) 2554 return f"{table}{alias}{pivots}{sample}{joins}" 2555 2556 def tablesample_sql( 2557 self, 2558 expression: exp.TableSample, 2559 tablesample_keyword: str | None = None, 2560 ) -> str: 2561 method = self.sql(expression, "method") 2562 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2563 numerator = self.sql(expression, "bucket_numerator") 2564 denominator = self.sql(expression, "bucket_denominator") 2565 field = self.sql(expression, "bucket_field") 2566 field = f" ON {field}" if field else "" 2567 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2568 seed = self.sql(expression, "seed") 2569 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2570 2571 size = self.sql(expression, "size") 2572 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2573 size = f"{size} ROWS" 2574 2575 percent = self.sql(expression, "percent") 2576 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2577 percent = f"{percent} PERCENT" 2578 2579 expr = f"{bucket}{percent}{size}" 2580 if self.TABLESAMPLE_REQUIRES_PARENS: 2581 expr = f"({expr})" 2582 2583 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2584 2585 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2586 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2587 # the stored column name differs from the target dialect's natural output. 2588 columns = expression.args.get("columns") 2589 if not columns or len(expression.fields) != 1: 2590 return None 2591 2592 args = expression.args 2593 parser_cls = self.dialect.parser_class 2594 2595 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2596 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2597 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2598 2599 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2600 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2601 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2602 2603 if ( 2604 src_identify_pivot_strings == tgt_identify_pivot_strings 2605 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2606 and src_pivot_column_naming == tgt_pivot_column_naming 2607 ): 2608 return None 2609 2610 in_exprs = expression.fields[0].expressions 2611 step = len(columns) // len(in_exprs) 2612 2613 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2614 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2615 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2616 first_stored = columns[0].name 2617 2618 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2619 if not first_stored.startswith(first_base): 2620 return None 2621 2622 suffix = first_stored[len(first_base) :] 2623 2624 # Whether the target dialect would append an agg-name suffix for this pivot. 2625 # Spark single-agg uniquely drops the agg alias entirely. 2626 target_has_suffix = ( 2627 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2628 ) and any(a.alias for a in expression.expressions) 2629 source_has_suffix = suffix != "" 2630 2631 new_exprs: list[exp.Expression] = [] 2632 modified = False 2633 for val_idx, e in enumerate(in_exprs): 2634 if isinstance(e, exp.PivotAlias): 2635 new_exprs.append(e) 2636 continue 2637 2638 i = val_idx * step 2639 stored_full = columns[i].name 2640 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2641 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2642 2643 # Source had a suffix, but target won't apply one 2644 if source_has_suffix and not target_has_suffix: 2645 new_exprs.append( 2646 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2647 ) 2648 modified = True 2649 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2650 elif stored_value != target_value: 2651 new_exprs.append( 2652 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2653 ) 2654 modified = True 2655 else: 2656 new_exprs.append(e) 2657 2658 return new_exprs if modified else None 2659 2660 def pivot_sql(self, expression: exp.Pivot) -> str: 2661 expressions = self.expressions(expression, flat=True) 2662 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2663 2664 group = self.sql(expression, "group") 2665 2666 if expression.this: 2667 this = self.sql(expression, "this") 2668 if not expressions: 2669 sql = f"UNPIVOT {this}" 2670 else: 2671 on = f"{self.seg('ON')} {expressions}" 2672 into = self.sql(expression, "into") 2673 into = f"{self.seg('INTO')} {into}" if into else "" 2674 using = self.expressions(expression, key="using", flat=True) 2675 using = f"{self.seg('USING')} {using}" if using else "" 2676 sql = f"{direction} {this}{on}{into}{using}{group}" 2677 return self.prepend_ctes(expression, sql) 2678 2679 if not expression.unpivot: 2680 # Wrap IN-list values with explicit aliases where the target dialect would differ 2681 new_field_exprs = self._pivot_in_value_aliases(expression) 2682 if new_field_exprs is not None: 2683 expression.fields[0].set("expressions", new_field_exprs) 2684 2685 alias = self.sql(expression, "alias") 2686 if alias: 2687 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2688 2689 fields = self.expressions( 2690 expression, 2691 "fields", 2692 sep=" ", 2693 dynamic=True, 2694 new_line=True, 2695 skip_first=True, 2696 skip_last=True, 2697 ) 2698 2699 include_nulls = expression.args.get("include_nulls") 2700 if include_nulls is not None: 2701 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2702 else: 2703 nulls = "" 2704 2705 default_on_null = self.sql(expression, "default_on_null") 2706 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2707 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2708 return self.prepend_ctes(expression, sql) 2709 2710 def version_sql(self, expression: exp.Version) -> str: 2711 this = f"FOR {expression.name}" 2712 kind = expression.text("kind") 2713 expr = self.sql(expression, "expression") 2714 return f"{this} {kind} {expr}" 2715 2716 def tuple_sql(self, expression: exp.Tuple) -> str: 2717 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2718 2719 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2720 """ 2721 Returns (join_sql, from_sql) for UPDATE statements. 2722 - join_sql: placed after UPDATE table, before SET 2723 - from_sql: placed after SET clause (standard position) 2724 Dialects like MySQL need to convert FROM to JOIN syntax. 2725 """ 2726 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2727 return ("", self.sql(expression, "from_")) 2728 2729 # Qualify unqualified columns in SET clause with the target table 2730 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2731 target_table = expression.this 2732 if isinstance(target_table, exp.Table): 2733 target_name = exp.to_identifier(target_table.alias_or_name) 2734 for eq in expression.expressions: 2735 col = eq.this 2736 if isinstance(col, exp.Column) and not col.table: 2737 col.set("table", target_name) 2738 2739 table = from_expr.this 2740 if nested_joins := table.args.get("joins", []): 2741 table.set("joins", None) 2742 2743 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2744 for nested in nested_joins: 2745 if not nested.args.get("on") and not nested.args.get("using"): 2746 nested.set("on", exp.true()) 2747 join_sql += self.sql(nested) 2748 2749 return (join_sql, "") 2750 2751 def update_sql(self, expression: exp.Update) -> str: 2752 hint = self.sql(expression, "hint") 2753 this = self.sql(expression, "this") 2754 join_sql, from_sql = self._update_from_joins_sql(expression) 2755 set_sql = self.expressions(expression, flat=True) 2756 where_sql = self.sql(expression, "where") 2757 returning = self.sql(expression, "returning") 2758 order = self.sql(expression, "order") 2759 limit = self.sql(expression, "limit") 2760 if self.RETURNING_END: 2761 expression_sql = f"{from_sql}{where_sql}{returning}" 2762 else: 2763 expression_sql = f"{returning}{from_sql}{where_sql}" 2764 options = self.expressions(expression, key="options") 2765 options = f" OPTION({options})" if options else "" 2766 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2767 return self.prepend_ctes(expression, sql) 2768 2769 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2770 values_as_table = values_as_table and self.VALUES_AS_TABLE 2771 2772 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2773 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2774 args = self.expressions(expression) 2775 alias = self.sql(expression, "alias") 2776 values = f"VALUES{self.seg('')}{args}" 2777 values = ( 2778 f"({values})" 2779 if self.WRAP_DERIVED_VALUES 2780 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2781 else values 2782 ) 2783 values = self.query_modifiers(expression, values) 2784 return f"{values} AS {alias}" if alias else values 2785 2786 # Converts `VALUES...` expression into a series of select unions. 2787 alias_node = expression.args.get("alias") 2788 column_names = alias_node and alias_node.columns 2789 2790 selects: list[exp.Query] = [] 2791 2792 for i, tup in enumerate(expression.expressions): 2793 row = tup.expressions 2794 2795 if i == 0 and column_names: 2796 row = [ 2797 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2798 ] 2799 2800 selects.append(exp.Select(expressions=row)) 2801 2802 if self.pretty: 2803 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2804 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2805 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2806 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2807 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2808 2809 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2810 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2811 return f"({unions}){alias}" 2812 2813 def var_sql(self, expression: exp.Var) -> str: 2814 return self.sql(expression, "this") 2815 2816 @unsupported_args("expressions") 2817 def into_sql(self, expression: exp.Into) -> str: 2818 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2819 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2820 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2821 2822 def from_sql(self, expression: exp.From) -> str: 2823 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2824 2825 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2826 grouping_sets = self.expressions(expression, indent=False) 2827 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2828 2829 def rollup_sql(self, expression: exp.Rollup) -> str: 2830 expressions = self.expressions(expression, indent=False) 2831 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2832 2833 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2834 this = self.sql(expression, "this") 2835 2836 columns = self.expressions(expression, flat=True) 2837 2838 from_sql = self.sql(expression, "from_index") 2839 from_sql = f" FROM {from_sql}" if from_sql else "" 2840 2841 properties = expression.args.get("properties") 2842 properties_sql = ( 2843 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2844 ) 2845 2846 return f"{this}({columns}){from_sql}{properties_sql}" 2847 2848 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2849 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2850 2851 def cube_sql(self, expression: exp.Cube) -> str: 2852 expressions = self.expressions(expression, indent=False) 2853 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2854 2855 def group_sql(self, expression: exp.Group) -> str: 2856 group_by_all = expression.args.get("all") 2857 if group_by_all is True: 2858 modifier = " ALL" 2859 elif group_by_all is False: 2860 modifier = " DISTINCT" 2861 else: 2862 modifier = "" 2863 2864 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2865 2866 grouping_sets = self.expressions(expression, key="grouping_sets") 2867 cube = self.expressions(expression, key="cube") 2868 rollup = self.expressions(expression, key="rollup") 2869 2870 groupings = csv( 2871 self.seg(grouping_sets) if grouping_sets else "", 2872 self.seg(cube) if cube else "", 2873 self.seg(rollup) if rollup else "", 2874 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2875 sep=self.GROUPINGS_SEP, 2876 ) 2877 2878 if ( 2879 expression.expressions 2880 and groupings 2881 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2882 ): 2883 add_separator = True 2884 2885 if grouping_sets: 2886 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2887 add_separator = False 2888 else: 2889 self.unsupported( 2890 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2891 ) 2892 2893 if add_separator: 2894 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2895 2896 return f"{group_by}{groupings}" 2897 2898 def having_sql(self, expression: exp.Having) -> str: 2899 this = self.indent(self.sql(expression, "this")) 2900 return f"{self.seg('HAVING')}{self.sep()}{this}" 2901 2902 def connect_sql(self, expression: exp.Connect) -> str: 2903 start = self.sql(expression, "start") 2904 start = self.seg(f"START WITH {start}") if start else "" 2905 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2906 connect = self.sql(expression, "connect") 2907 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2908 return start + connect 2909 2910 def prior_sql(self, expression: exp.Prior) -> str: 2911 return f"PRIOR {self.sql(expression, 'this')}" 2912 2913 def join_sql(self, expression: exp.Join) -> str: 2914 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2915 side = None 2916 else: 2917 side = expression.side 2918 2919 op_sql = " ".join( 2920 op 2921 for op in ( 2922 expression.method, 2923 "GLOBAL" if expression.args.get("global_") else None, 2924 side, 2925 expression.kind, 2926 expression.hint if self.JOIN_HINTS else None, 2927 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2928 ) 2929 if op 2930 ) 2931 match_cond = self.sql(expression, "match_condition") 2932 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2933 on_sql = self.sql(expression, "on") 2934 using = expression.args.get("using") 2935 2936 if not on_sql and using: 2937 on_sql = csv(*(self.sql(column) for column in using)) 2938 2939 this = expression.this 2940 this_sql = self.sql(this) 2941 2942 exprs = self.expressions(expression) 2943 if exprs: 2944 this_sql = f"{this_sql},{self.seg(exprs)}" 2945 2946 if on_sql: 2947 on_sql = self.indent(on_sql, skip_first=True) 2948 space = self.seg(" " * self.pad) if self.pretty else " " 2949 if using: 2950 on_sql = f"{space}USING ({on_sql})" 2951 else: 2952 on_sql = f"{space}ON {on_sql}" 2953 elif not op_sql: 2954 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2955 return f" {this_sql}" 2956 2957 return f", {this_sql}" 2958 2959 if op_sql != "STRAIGHT_JOIN": 2960 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2961 2962 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2963 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2964 2965 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2966 args = self.expressions(expression, flat=True) 2967 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2968 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2969 2970 def lateral_op(self, expression: exp.Lateral) -> str: 2971 cross_apply = expression.args.get("cross_apply") 2972 2973 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2974 if cross_apply is True: 2975 op = "INNER JOIN " 2976 elif cross_apply is False: 2977 op = "LEFT JOIN " 2978 else: 2979 op = "" 2980 2981 return f"{op}LATERAL" 2982 2983 def lateral_sql(self, expression: exp.Lateral) -> str: 2984 this = self.sql(expression, "this") 2985 2986 if expression.args.get("view"): 2987 alias = expression.args["alias"] 2988 columns = self.expressions(alias, key="columns", flat=True) 2989 table = f" {alias.name}" if alias.name else "" 2990 columns = f" AS {columns}" if columns else "" 2991 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2992 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2993 2994 table_alias = expression.args.get("alias") 2995 offset = expression.this.args.get("offset") 2996 2997 if ( 2998 self.UNNEST_WITH_ORDINALITY 2999 and table_alias 3000 and isinstance(expression.this, exp.Unnest) 3001 and isinstance(offset, exp.Identifier) 3002 ): 3003 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3004 table_alias = table_alias.copy() 3005 table_alias.append("columns", offset.copy()) 3006 3007 alias = self.sql(table_alias) 3008 alias = f" AS {alias}" if alias else "" 3009 3010 ordinality = expression.args.get("ordinality") or "" 3011 if ordinality: 3012 ordinality = f" WITH ORDINALITY{alias}" 3013 alias = "" 3014 3015 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3016 3017 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3018 this = self.sql(expression, "this") 3019 3020 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3021 return this 3022 3023 args = [ 3024 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3025 for e in (expression.args.get(k) for k in ("offset", "expression")) 3026 if e 3027 ] 3028 3029 args_sql = ", ".join(self.sql(e) for e in args) 3030 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3031 expressions = self.expressions(expression, flat=True) 3032 limit_options = self.sql(expression, "limit_options") 3033 expressions = f" BY {expressions}" if expressions else "" 3034 3035 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3036 3037 def offset_sql(self, expression: exp.Offset) -> str: 3038 this = self.sql(expression, "this") 3039 value = expression.expression 3040 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3041 expressions = self.expressions(expression, flat=True) 3042 expressions = f" BY {expressions}" if expressions else "" 3043 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3044 3045 def setitem_sql(self, expression: exp.SetItem) -> str: 3046 kind = self.sql(expression, "kind") 3047 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3048 kind = "" 3049 else: 3050 kind = f"{kind} " if kind else "" 3051 this = self.sql(expression, "this") 3052 expressions = self.expressions(expression) 3053 collate = self.sql(expression, "collate") 3054 collate = f" COLLATE {collate}" if collate else "" 3055 global_ = "GLOBAL " if expression.args.get("global_") else "" 3056 return f"{global_}{kind}{this}{expressions}{collate}" 3057 3058 def set_sql(self, expression: exp.Set) -> str: 3059 expressions = f" {self.expressions(expression, flat=True)}" 3060 tag = " TAG" if expression.args.get("tag") else "" 3061 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3062 3063 def queryband_sql(self, expression: exp.QueryBand) -> str: 3064 this = self.sql(expression, "this") 3065 update = " UPDATE" if expression.args.get("update") else "" 3066 scope = self.sql(expression, "scope") 3067 scope = f" FOR {scope}" if scope else "" 3068 3069 return f"QUERY_BAND = {this}{update}{scope}" 3070 3071 def pragma_sql(self, expression: exp.Pragma) -> str: 3072 return f"PRAGMA {self.sql(expression, 'this')}" 3073 3074 def lock_sql(self, expression: exp.Lock) -> str: 3075 if not self.LOCKING_READS_SUPPORTED: 3076 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3077 return "" 3078 3079 update = expression.args["update"] 3080 key = expression.args.get("key") 3081 if update: 3082 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3083 else: 3084 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3085 expressions = self.expressions(expression, flat=True) 3086 expressions = f" OF {expressions}" if expressions else "" 3087 wait = expression.args.get("wait") 3088 3089 if wait is not None: 3090 if isinstance(wait, exp.Literal): 3091 wait = f" WAIT {self.sql(wait)}" 3092 else: 3093 wait = " NOWAIT" if wait else " SKIP LOCKED" 3094 3095 return f"{lock_type}{expressions}{wait or ''}" 3096 3097 def literal_sql(self, expression: exp.Literal) -> str: 3098 text = expression.this or "" 3099 if expression.is_string: 3100 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3101 return text 3102 3103 def escape_str( 3104 self, 3105 text: str, 3106 escape_backslash: bool = True, 3107 delimiter: str | None = None, 3108 escaped_delimiter: str | None = None, 3109 is_byte_string: bool = False, 3110 is_bytes: bool = False, 3111 ) -> str: 3112 if is_byte_string: 3113 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3114 else: 3115 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3116 3117 if supports_escape_sequences: 3118 text = "".join( 3119 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3120 for ch in text 3121 ) 3122 3123 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3124 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3125 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3126 3127 delimiter = delimiter or self.dialect.QUOTE_END 3128 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3129 3130 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3131 3132 def loaddata_sql(self, expression: exp.LoadData) -> str: 3133 is_overwrite = expression.args.get("overwrite") 3134 overwrite = " OVERWRITE" if is_overwrite else "" 3135 this = self.sql(expression, "this") 3136 3137 files = expression.args.get("files") 3138 if files: 3139 files_sql = self.expressions(files, flat=True) 3140 files_sql = f"FILES{self.wrap(files_sql)}" 3141 if is_overwrite: 3142 this = f" {this}" 3143 elif expression.args.get("temp"): 3144 this = f" INTO TEMP TABLE {this}" 3145 else: 3146 this = f" INTO TABLE {this}" 3147 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3148 3149 local = " LOCAL" if expression.args.get("local") else "" 3150 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3151 this = f" INTO TABLE {this}" 3152 partition = self.sql(expression, "partition") 3153 partition = f" {partition}" if partition else "" 3154 input_format = self.sql(expression, "input_format") 3155 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3156 serde = self.sql(expression, "serde") 3157 serde = f" SERDE {serde}" if serde else "" 3158 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3159 3160 def null_sql(self, *_) -> str: 3161 return "NULL" 3162 3163 def boolean_sql(self, expression: exp.Boolean) -> str: 3164 return "TRUE" if expression.this else "FALSE" 3165 3166 def booland_sql(self, expression: exp.Booland) -> str: 3167 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3168 3169 def boolor_sql(self, expression: exp.Boolor) -> str: 3170 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3171 3172 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3173 this = self.sql(expression, "this") 3174 this = f"{this} " if this else this 3175 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3176 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3177 3178 def withfill_sql(self, expression: exp.WithFill) -> str: 3179 from_sql = self.sql(expression, "from_") 3180 from_sql = f" FROM {from_sql}" if from_sql else "" 3181 to_sql = self.sql(expression, "to") 3182 to_sql = f" TO {to_sql}" if to_sql else "" 3183 step_sql = self.sql(expression, "step") 3184 step_sql = f" STEP {step_sql}" if step_sql else "" 3185 interpolated_values = [ 3186 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3187 if isinstance(e, exp.Alias) 3188 else self.sql(e, "this") 3189 for e in expression.args.get("interpolate") or [] 3190 ] 3191 interpolate = ( 3192 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3193 ) 3194 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3195 3196 def cluster_sql(self, expression: exp.Cluster) -> str: 3197 return self.op_expressions("CLUSTER BY", expression) 3198 3199 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3200 if expression.this: 3201 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3202 return "" 3203 expressions = self.expressions(expression, flat=True) 3204 return f"CLUSTER BY ({expressions})" 3205 3206 def distribute_sql(self, expression: exp.Distribute) -> str: 3207 return self.op_expressions("DISTRIBUTE BY", expression) 3208 3209 def sort_sql(self, expression: exp.Sort) -> str: 3210 return self.op_expressions("SORT BY", expression) 3211 3212 def _resolve_ordered_for_null_ordering_simulation( 3213 self, expression: exp.Ordered 3214 ) -> exp.Expr | None: 3215 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3216 3217 Returns the underlying expression of the uniquely-matching projection 3218 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3219 simulation, since the CASE is evaluated in FROM-clause scope rather 3220 than alias scope (MySQL error 1052). Returns None if no safe 3221 substitution applies, leaving the original behaviour unchanged. 3222 """ 3223 this = expression.this 3224 if not (isinstance(this, exp.Column) and not this.table): 3225 return None 3226 3227 # Only a query's own ORDER BY can reference its projection aliases 3228 order = expression.parent 3229 ancestor = order.parent if isinstance(order, exp.Order) else None 3230 if not isinstance(ancestor, exp.Select): 3231 return None 3232 3233 column_name = this.name 3234 matched: list[exp.Expr] = [ 3235 p.this if isinstance(p, exp.Alias) else p 3236 for p in ancestor.selects 3237 if p.output_name == column_name 3238 ] 3239 match = matched[0] if len(matched) == 1 else None 3240 3241 # Skip the substitution when it would be identical to the existing 3242 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3243 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3244 return None 3245 3246 return match 3247 3248 def ordered_sql(self, expression: exp.Ordered) -> str: 3249 desc = expression.args.get("desc") 3250 asc = not desc 3251 3252 nulls_first = expression.args.get("nulls_first") 3253 nulls_last = not nulls_first 3254 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3255 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3256 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3257 3258 this = self.sql(expression, "this") 3259 3260 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3261 nulls_sort_change = "" 3262 if nulls_first and ( 3263 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3264 ): 3265 nulls_sort_change = " NULLS FIRST" 3266 elif ( 3267 nulls_last 3268 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3269 and not nulls_are_last 3270 ): 3271 nulls_sort_change = " NULLS LAST" 3272 3273 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3274 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3275 window = expression.find_ancestor(exp.Window, exp.Select) 3276 3277 if isinstance(window, exp.Window): 3278 window_this = window.this 3279 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3280 window_this = window_this.this 3281 spec = window.args.get("spec") 3282 else: 3283 window_this = None 3284 spec = None 3285 3286 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3287 # without a spec or with a ROWS spec, but not with RANGE 3288 if not ( 3289 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3290 and (not spec or spec.text("kind").upper() == "ROWS") 3291 ): 3292 if window_this and spec: 3293 self.unsupported( 3294 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3295 ) 3296 nulls_sort_change = "" 3297 elif self.NULL_ORDERING_SUPPORTED is False and ( 3298 (asc and nulls_sort_change == " NULLS LAST") 3299 or (desc and nulls_sort_change == " NULLS FIRST") 3300 ): 3301 # BigQuery does not allow these ordering/nulls combinations when used under 3302 # an aggregation func or under a window containing one 3303 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3304 3305 if isinstance(ancestor, exp.Window): 3306 ancestor = ancestor.this 3307 if isinstance(ancestor, exp.AggFunc): 3308 self.unsupported( 3309 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3310 ) 3311 nulls_sort_change = "" 3312 elif self.NULL_ORDERING_SUPPORTED is None: 3313 if expression.this.is_int: 3314 self.unsupported( 3315 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3316 ) 3317 elif not isinstance(expression.this, exp.Rand): 3318 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3319 target = self.sql(resolved) if resolved is not None else this 3320 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3321 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3322 nulls_sort_change = "" 3323 3324 with_fill = self.sql(expression, "with_fill") 3325 with_fill = f" {with_fill}" if with_fill else "" 3326 3327 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3328 3329 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3330 window_frame = self.sql(expression, "window_frame") 3331 window_frame = f"{window_frame} " if window_frame else "" 3332 3333 this = self.sql(expression, "this") 3334 3335 return f"{window_frame}{this}" 3336 3337 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3338 partition = self.partition_by_sql(expression) 3339 order = self.sql(expression, "order") 3340 measures = self.expressions(expression, key="measures") 3341 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3342 rows = self.sql(expression, "rows") 3343 rows = self.seg(rows) if rows else "" 3344 after = self.sql(expression, "after") 3345 after = self.seg(after) if after else "" 3346 pattern = self.sql(expression, "pattern") 3347 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3348 definition_sqls = [ 3349 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3350 for definition in expression.args.get("define", []) 3351 ] 3352 definitions = self.expressions(sqls=definition_sqls) 3353 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3354 body = "".join( 3355 ( 3356 partition, 3357 order, 3358 measures, 3359 rows, 3360 after, 3361 pattern, 3362 define, 3363 ) 3364 ) 3365 alias = self.sql(expression, "alias") 3366 alias = f" {alias}" if alias else "" 3367 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3368 3369 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3370 limit = expression.args.get("limit") 3371 3372 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3373 count = limit.args.get("count") 3374 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3375 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3376 limit = exp.Limit( 3377 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3378 ) 3379 elif ( 3380 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3381 ): 3382 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3383 3384 return csv( 3385 *sqls, 3386 *[self.sql(join) for join in expression.args.get("joins") or []], 3387 self.sql(expression, "match"), 3388 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3389 self.sql(expression, "prewhere"), 3390 self.sql(expression, "where"), 3391 self.sql(expression, "connect"), 3392 self.sql(expression, "group"), 3393 self.sql(expression, "having"), 3394 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3395 self.sql(expression, "order"), 3396 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3397 *self.after_limit_modifiers(expression), 3398 self.sql(expression, "for_"), 3399 self.options_modifier(expression), 3400 sep="", 3401 ) 3402 3403 def options_modifier(self, expression: exp.Expr) -> str: 3404 options = self.expressions(expression, key="options") 3405 return f" {options}" if options else "" 3406 3407 def forclause_sql(self, expression: exp.ForClause) -> str: 3408 kind = expression.args["kind"] 3409 if kind == "BROWSE": 3410 return f"{self.sep()}FOR BROWSE" 3411 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3412 # the target dialect doesn't support QueryOption, so we drop the clause. 3413 options = self.expressions(expression, key="expressions") 3414 if not options: 3415 return "" 3416 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3417 3418 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3419 self.unsupported("Unsupported query option.") 3420 return "" 3421 3422 def offset_limit_modifiers( 3423 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3424 ) -> list[str]: 3425 return [ 3426 self.sql(expression, "offset") if fetch else self.sql(limit), 3427 self.sql(limit) if fetch else self.sql(expression, "offset"), 3428 ] 3429 3430 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3431 locks = self.expressions(expression, key="locks", sep=" ") 3432 locks = f" {locks}" if locks else "" 3433 return [locks, self.sql(expression, "sample")] 3434 3435 def select_sql(self, expression: exp.Select) -> str: 3436 into = expression.args.get("into") 3437 if not self.SUPPORTS_SELECT_INTO and into: 3438 into.pop() 3439 3440 hint = self.sql(expression, "hint") 3441 distinct = self.sql(expression, "distinct") 3442 distinct = f" {distinct}" if distinct else "" 3443 kind = self.sql(expression, "kind") 3444 3445 limit = expression.args.get("limit") 3446 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3447 top = self.limit_sql(limit, top=True) 3448 limit.pop() 3449 else: 3450 top = "" 3451 3452 expressions = self.expressions(expression) 3453 3454 if kind: 3455 if kind in self.SELECT_KINDS: 3456 kind = f" AS {kind}" 3457 else: 3458 if kind == "STRUCT": 3459 expressions = self.expressions( 3460 sqls=[ 3461 self.sql( 3462 exp.Struct( 3463 expressions=[ 3464 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3465 if isinstance(e, exp.Alias) 3466 else e 3467 for e in expression.expressions 3468 ] 3469 ) 3470 ) 3471 ] 3472 ) 3473 kind = "" 3474 3475 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3476 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3477 3478 exclude = expression.args.get("exclude") 3479 3480 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3481 exclude_sql = self.expressions(sqls=exclude, flat=True) 3482 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3483 3484 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3485 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3486 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3487 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3488 sql = self.query_modifiers( 3489 expression, 3490 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3491 self.sql(expression, "into", comment=False), 3492 self.sql(expression, "from_", comment=False), 3493 ) 3494 3495 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3496 if expression.args.get("with_"): 3497 sql = self.maybe_comment(sql, expression) 3498 expression.pop_comments() 3499 3500 sql = self.prepend_ctes(expression, sql) 3501 3502 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3503 expression.set("exclude", None) 3504 subquery = expression.subquery(copy=False) 3505 star = exp.Star(except_=exclude) 3506 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3507 3508 if not self.SUPPORTS_SELECT_INTO and into: 3509 if into.args.get("temporary"): 3510 table_kind = " TEMPORARY" 3511 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3512 table_kind = " UNLOGGED" 3513 else: 3514 table_kind = "" 3515 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3516 3517 return sql 3518 3519 def schema_sql(self, expression: exp.Schema) -> str: 3520 this = self.sql(expression, "this") 3521 sql = self.schema_columns_sql(expression) 3522 return f"{this} {sql}" if this and sql else this or sql 3523 3524 def schema_columns_sql(self, expression: exp.Expr) -> str: 3525 if expression.expressions: 3526 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3527 return "" 3528 3529 def star_sql(self, expression: exp.Star) -> str: 3530 except_ = self.expressions(expression, key="except_", flat=True) 3531 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3532 replace = self.expressions(expression, key="replace", flat=True) 3533 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3534 rename = self.expressions(expression, key="rename", flat=True) 3535 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3536 ilike = self.sql(expression, "ilike") 3537 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3538 return f"*{ilike}{except_}{replace}{rename}" 3539 3540 def parameter_sql(self, expression: exp.Parameter) -> str: 3541 this = self.sql(expression, "this") 3542 return f"{self.PARAMETER_TOKEN}{this}" 3543 3544 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3545 this = self.sql(expression, "this") 3546 kind = expression.text("kind") 3547 if kind: 3548 kind = f"{kind}." 3549 return f"@@{kind}{this}" 3550 3551 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3552 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3553 3554 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3555 alias = self.sql(expression, "alias") 3556 alias = f"{sep}{alias}" if alias else "" 3557 sample = self.sql(expression, "sample") 3558 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3559 alias = f"{sample}{alias}" 3560 3561 # Set to None so it's not generated again by self.query_modifiers() 3562 expression.set("sample", None) 3563 3564 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3565 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3566 return self.prepend_ctes(expression, sql) 3567 3568 def qualify_sql(self, expression: exp.Qualify) -> str: 3569 this = self.indent(self.sql(expression, "this")) 3570 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3571 3572 def unnest_sql(self, expression: exp.Unnest) -> str: 3573 args = self.expressions(expression, flat=True) 3574 3575 alias = expression.args.get("alias") 3576 offset = expression.args.get("offset") 3577 3578 if self.UNNEST_WITH_ORDINALITY: 3579 if alias and isinstance(offset, exp.Expr): 3580 alias.append("columns", offset) 3581 expression.set("offset", None) 3582 3583 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3584 columns = alias.columns 3585 alias = self.sql(columns[0]) if columns else "" 3586 else: 3587 alias = self.sql(alias) 3588 3589 alias = f" AS {alias}" if alias else alias 3590 if self.UNNEST_WITH_ORDINALITY: 3591 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3592 else: 3593 if isinstance(offset, exp.Expr): 3594 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3595 elif offset: 3596 suffix = f"{alias} WITH OFFSET" 3597 else: 3598 suffix = alias 3599 3600 return f"UNNEST({args}){suffix}" 3601 3602 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3603 return "" 3604 3605 def where_sql(self, expression: exp.Where) -> str: 3606 this = self.indent(self.sql(expression, "this")) 3607 return f"{self.seg('WHERE')}{self.sep()}{this}" 3608 3609 def window_sql(self, expression: exp.Window) -> str: 3610 this = self.sql(expression, "this") 3611 partition = self.partition_by_sql(expression) 3612 order = expression.args.get("order") 3613 order = self.order_sql(order, flat=True) if order else "" 3614 spec = self.sql(expression, "spec") 3615 alias = self.sql(expression, "alias") 3616 over = self.sql(expression, "over") or "OVER" 3617 3618 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3619 3620 first = expression.args.get("first") 3621 if first is None: 3622 first = "" 3623 else: 3624 first = "FIRST" if first else "LAST" 3625 3626 if not partition and not order and not spec and alias: 3627 return f"{this} {alias}" 3628 3629 args = self.format_args( 3630 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3631 ) 3632 return f"{this} ({args})" 3633 3634 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3635 partition = self.expressions(expression, key="partition_by", flat=True) 3636 return f"PARTITION BY {partition}" if partition else "" 3637 3638 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3639 kind = self.sql(expression, "kind") 3640 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3641 end = ( 3642 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3643 or "CURRENT ROW" 3644 ) 3645 3646 window_spec = f"{kind} BETWEEN {start} AND {end}" 3647 3648 exclude = self.sql(expression, "exclude") 3649 if exclude: 3650 if self.SUPPORTS_WINDOW_EXCLUDE: 3651 window_spec += f" EXCLUDE {exclude}" 3652 else: 3653 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3654 3655 return window_spec 3656 3657 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3658 this = self.sql(expression, "this") 3659 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3660 return f"{this} WITHIN GROUP ({expression_sql})" 3661 3662 def between_sql(self, expression: exp.Between) -> str: 3663 this = self.sql(expression, "this") 3664 low = self.sql(expression, "low") 3665 high = self.sql(expression, "high") 3666 symmetric = expression.args.get("symmetric") 3667 3668 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3669 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3670 3671 flag = ( 3672 " SYMMETRIC" 3673 if symmetric 3674 else " ASYMMETRIC" 3675 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3676 else "" # silently drop ASYMMETRIC – semantics identical 3677 ) 3678 return f"{this} BETWEEN{flag} {low} AND {high}" 3679 3680 def bracket_offset_expressions( 3681 self, expression: exp.Bracket, index_offset: int | None = None 3682 ) -> list[exp.Expr]: 3683 if expression.args.get("json_access"): 3684 return expression.expressions 3685 3686 return apply_index_offset( 3687 expression.this, 3688 expression.expressions, 3689 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3690 dialect=self.dialect, 3691 ) 3692 3693 def bracket_sql(self, expression: exp.Bracket) -> str: 3694 expressions = self.bracket_offset_expressions(expression) 3695 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3696 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3697 3698 def all_sql(self, expression: exp.All) -> str: 3699 this = self.sql(expression, "this") 3700 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3701 this = self.wrap(this) 3702 return f"ALL {this}" 3703 3704 def any_sql(self, expression: exp.Any) -> str: 3705 this = self.sql(expression, "this") 3706 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3707 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3708 this = self.wrap(this) 3709 return f"ANY{this}" 3710 return f"ANY {this}" 3711 3712 def exists_sql(self, expression: exp.Exists) -> str: 3713 return f"EXISTS{self.wrap(expression)}" 3714 3715 def case_sql(self, expression: exp.Case) -> str: 3716 this = self.sql(expression, "this") 3717 statements = [f"CASE {this}" if this else "CASE"] 3718 3719 for e in expression.args["ifs"]: 3720 statements.append(f"WHEN {self.sql(e, 'this')}") 3721 statements.append(f"THEN {self.sql(e, 'true')}") 3722 3723 default = self.sql(expression, "default") 3724 3725 if default: 3726 statements.append(f"ELSE {default}") 3727 3728 statements.append("END") 3729 3730 if self.pretty and self.too_wide(statements): 3731 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3732 3733 return " ".join(statements) 3734 3735 def constraint_sql(self, expression: exp.Constraint) -> str: 3736 this = self.sql(expression, "this") 3737 expressions = self.expressions(expression, flat=True) 3738 return f"CONSTRAINT {this} {expressions}" 3739 3740 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3741 order = expression.args.get("order") 3742 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3743 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3744 3745 def extract_sql(self, expression: exp.Extract) -> str: 3746 import sqlglot.dialects.dialect 3747 3748 this = ( 3749 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3750 if self.NORMALIZE_EXTRACT_DATE_PARTS 3751 else expression.this 3752 ) 3753 if self.EXTRACT_ALLOWS_QUOTES: 3754 this_sql = self.sql(this) 3755 elif isinstance(this, exp.WeekStart): 3756 this_sql = self.weekstart_name(this) 3757 else: 3758 this_sql = this.name 3759 expression_sql = self.sql(expression, "expression") 3760 3761 return f"EXTRACT({this_sql} FROM {expression_sql})" 3762 3763 def trim_sql(self, expression: exp.Trim) -> str: 3764 trim_type = self.sql(expression, "position") 3765 3766 if trim_type == "LEADING": 3767 func_name = "LTRIM" 3768 elif trim_type == "TRAILING": 3769 func_name = "RTRIM" 3770 else: 3771 func_name = "TRIM" 3772 3773 return self.func(func_name, expression.this, expression.expression) 3774 3775 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3776 args = expression.expressions 3777 if isinstance(expression, exp.ConcatWs): 3778 args = args[1:] # Skip the delimiter 3779 3780 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3781 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3782 3783 concat_coalesce = ( 3784 self.dialect.CONCAT_WS_COALESCE 3785 if isinstance(expression, exp.ConcatWs) 3786 else self.dialect.CONCAT_COALESCE 3787 ) 3788 3789 if not concat_coalesce and expression.args.get("coalesce"): 3790 3791 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3792 if not e.type: 3793 import sqlglot.optimizer.annotate_types 3794 3795 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3796 3797 if e.is_string or e.is_type(exp.DType.ARRAY): 3798 return e 3799 3800 return exp.func("coalesce", e, exp.Literal.string("")) 3801 3802 args = [_wrap_with_coalesce(e) for e in args] 3803 3804 return args 3805 3806 def concat_sql(self, expression: exp.Concat) -> str: 3807 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3808 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3809 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3810 # instead of coalescing them to empty string. 3811 import sqlglot.dialects.dialect 3812 3813 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3814 3815 expressions = self.convert_concat_args(expression) 3816 3817 # Some dialects don't allow a single-argument CONCAT call 3818 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3819 return self.sql(expressions[0]) 3820 3821 return self.func("CONCAT", *expressions) 3822 3823 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3824 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3825 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3826 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3827 all_args = expression.expressions 3828 expression.set("coalesce", True) 3829 return self.sql( 3830 exp.case() 3831 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3832 .else_(expression) 3833 ) 3834 3835 return self.func( 3836 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3837 ) 3838 3839 def check_sql(self, expression: exp.Check) -> str: 3840 this = self.sql(expression, key="this") 3841 return f"CHECK ({this})" 3842 3843 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3844 expressions = self.expressions(expression, flat=True) 3845 expressions = f" ({expressions})" if expressions else "" 3846 reference = self.sql(expression, "reference") 3847 reference = f" {reference}" if reference else "" 3848 delete = self.sql(expression, "delete") 3849 delete = f" ON DELETE {delete}" if delete else "" 3850 update = self.sql(expression, "update") 3851 update = f" ON UPDATE {update}" if update else "" 3852 options = self.expressions(expression, key="options", flat=True, sep=" ") 3853 options = f" {options}" if options else "" 3854 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3855 3856 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3857 this = self.sql(expression, "this") 3858 this = f" {this}" if this else "" 3859 expressions = self.expressions(expression, flat=True) 3860 include = self.sql(expression, "include") 3861 options = self.expressions(expression, key="options", flat=True, sep=" ") 3862 options = f" {options}" if options else "" 3863 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3864 3865 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3866 self.unsupported("TIMESERIES primary key columns are not supported") 3867 return self.sql(expression, "this") 3868 3869 def if_sql(self, expression: exp.If) -> str: 3870 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3871 3872 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3873 if self.MATCH_AGAINST_TABLE_PREFIX: 3874 expressions = [] 3875 for expr in expression.expressions: 3876 if isinstance(expr, exp.Table): 3877 expressions.append(f"TABLE {self.sql(expr)}") 3878 else: 3879 expressions.append(expr) 3880 else: 3881 expressions = expression.expressions 3882 3883 modifier = expression.args.get("modifier") 3884 modifier = f" {modifier}" if modifier else "" 3885 return ( 3886 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3887 ) 3888 3889 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3890 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3891 3892 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3893 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3894 3895 if self.QUOTE_JSON_PATH: 3896 path = self.escape_str(path) 3897 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3898 3899 return path 3900 3901 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3902 if isinstance(expression, exp.JSONPathPart): 3903 transform = self.TRANSFORMS.get(expression.__class__) 3904 if not callable(transform): 3905 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3906 return "" 3907 3908 return transform(self, expression) 3909 3910 if isinstance(expression, int): 3911 return str(expression) 3912 3913 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3914 escaped = expression.replace("'", "\\'") 3915 escaped = f"'{escaped}'" 3916 else: 3917 escaped = expression.replace('"', '\\"') 3918 escaped = f'"{escaped}"' 3919 3920 return escaped 3921 3922 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3923 return f"{self.sql(expression, 'this')} FORMAT JSON" 3924 3925 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3926 # Output the Teradata column FORMAT override. 3927 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3928 this = self.sql(expression, "this") 3929 fmt = self.sql(expression, "format") 3930 return f"{this} (FORMAT {fmt})" 3931 3932 def _jsonobject_sql( 3933 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3934 ) -> str: 3935 null_handling = expression.args.get("null_handling") 3936 null_handling = f" {null_handling}" if null_handling else "" 3937 3938 unique_keys = expression.args.get("unique_keys") 3939 if unique_keys is not None: 3940 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3941 else: 3942 unique_keys = "" 3943 3944 return_type = self.sql(expression, "return_type") 3945 return_type = f" RETURNING {return_type}" if return_type else "" 3946 encoding = self.sql(expression, "encoding") 3947 encoding = f" ENCODING {encoding}" if encoding else "" 3948 3949 if not name: 3950 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3951 3952 return self.func( 3953 name, 3954 *expression.expressions, 3955 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3956 ) 3957 3958 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3959 null_handling = expression.args.get("null_handling") 3960 null_handling = f" {null_handling}" if null_handling else "" 3961 return_type = self.sql(expression, "return_type") 3962 return_type = f" RETURNING {return_type}" if return_type else "" 3963 strict = " STRICT" if expression.args.get("strict") else "" 3964 return self.func( 3965 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3966 ) 3967 3968 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3969 this = self.sql(expression, "this") 3970 order = self.sql(expression, "order") 3971 null_handling = expression.args.get("null_handling") 3972 null_handling = f" {null_handling}" if null_handling else "" 3973 return_type = self.sql(expression, "return_type") 3974 return_type = f" RETURNING {return_type}" if return_type else "" 3975 strict = " STRICT" if expression.args.get("strict") else "" 3976 return self.func( 3977 "JSON_ARRAYAGG", 3978 this, 3979 suffix=f"{order}{null_handling}{return_type}{strict})", 3980 ) 3981 3982 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3983 path = self.sql(expression, "path") 3984 path = f" PATH {path}" if path else "" 3985 nested_schema = self.sql(expression, "nested_schema") 3986 3987 if nested_schema: 3988 return f"NESTED{path} {nested_schema}" 3989 3990 this = self.sql(expression, "this") 3991 kind = self.sql(expression, "kind") 3992 kind = f" {kind}" if kind else "" 3993 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3994 3995 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3996 return f"{this}{kind}{format_json}{path}{ordinality}" 3997 3998 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3999 return self.func("COLUMNS", *expression.expressions) 4000 4001 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4002 this = self.sql(expression, "this") 4003 path = self.sql(expression, "path") 4004 path = f", {path}" if path else "" 4005 error_handling = expression.args.get("error_handling") 4006 error_handling = f" {error_handling}" if error_handling else "" 4007 empty_handling = expression.args.get("empty_handling") 4008 empty_handling = f" {empty_handling}" if empty_handling else "" 4009 schema = self.sql(expression, "schema") 4010 return self.func( 4011 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4012 ) 4013 4014 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4015 this = self.sql(expression, "this") 4016 kind = self.sql(expression, "kind") 4017 path = self.sql(expression, "path") 4018 path = f" {path}" if path else "" 4019 as_json = " AS JSON" if expression.args.get("as_json") else "" 4020 return f"{this} {kind}{path}{as_json}" 4021 4022 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4023 this = self.sql(expression, "this") 4024 path = self.sql(expression, "path") 4025 path = f", {path}" if path else "" 4026 expressions = self.expressions(expression) 4027 with_ = ( 4028 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4029 if expressions 4030 else "" 4031 ) 4032 return f"OPENJSON({this}{path}){with_}" 4033 4034 def in_sql(self, expression: exp.In) -> str: 4035 query = expression.args.get("query") 4036 unnest = expression.args.get("unnest") 4037 field = expression.args.get("field") 4038 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4039 4040 if query: 4041 in_sql = self.sql(query) 4042 elif unnest: 4043 in_sql = self.in_unnest_op(unnest) 4044 elif field: 4045 in_sql = self.sql(field) 4046 else: 4047 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4048 4049 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4050 4051 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4052 return f"(SELECT {self.sql(unnest)})" 4053 4054 def interval_sql(self, expression: exp.Interval) -> str: 4055 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4056 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4057 exp.AutoRefreshProperty, 4058 ) 4059 interval_keyword = "INTERVAL" if include_keyword else "" 4060 unit_expression = expression.args.get("unit") 4061 unit = self.sql(unit_expression) if unit_expression else "" 4062 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4063 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4064 unit = f" {unit}" if unit else "" 4065 4066 if self.SINGLE_STRING_INTERVAL: 4067 this = expression.this.name if expression.this else "" 4068 if this: 4069 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4070 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4071 return f"{interval_keyword}'{this}'{unit}" 4072 return f"{interval_keyword}'{this}{unit}'" 4073 return f"{interval_keyword}{unit}" 4074 4075 this = self.sql(expression, "this") 4076 if this: 4077 if not include_keyword and expression.this.is_string: 4078 this = expression.this.name 4079 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4080 this = f"({this})" 4081 if include_keyword: 4082 this = f" {this}" 4083 4084 return f"{interval_keyword}{this}{unit}" 4085 4086 def return_sql(self, expression: exp.Return) -> str: 4087 return f"RETURN {self.sql(expression, 'this')}" 4088 4089 def reference_sql(self, expression: exp.Reference) -> str: 4090 this = self.sql(expression, "this") 4091 expressions = self.expressions(expression, flat=True) 4092 expressions = f"({expressions})" if expressions else "" 4093 options = self.expressions(expression, key="options", flat=True, sep=" ") 4094 options = f" {options}" if options else "" 4095 return f"REFERENCES {this}{expressions}{options}" 4096 4097 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4098 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4099 parent = expression.parent 4100 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4101 4102 return self.func( 4103 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4104 ) 4105 4106 def paren_sql(self, expression: exp.Paren) -> str: 4107 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4108 return f"({sql}{self.seg(')', sep='')}" 4109 4110 def neg_sql(self, expression: exp.Neg) -> str: 4111 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4112 this_sql = self.sql(expression, "this") 4113 sep = " " if this_sql[0] == "-" else "" 4114 return f"-{sep}{this_sql}" 4115 4116 def not_sql(self, expression: exp.Not) -> str: 4117 return f"NOT {self.sql(expression, 'this')}" 4118 4119 def alias_sql(self, expression: exp.Alias) -> str: 4120 alias = self.sql(expression, "alias") 4121 alias = f" AS {alias}" if alias else "" 4122 return f"{self.sql(expression, 'this')}{alias}" 4123 4124 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4125 alias = expression.args["alias"] 4126 4127 parent = expression.parent 4128 pivot = parent and parent.parent 4129 4130 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4131 identifier_alias = isinstance(alias, exp.Identifier) 4132 literal_alias = isinstance(alias, exp.Literal) 4133 4134 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4135 alias.replace(exp.Literal.string(alias.output_name)) 4136 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4137 alias.replace(exp.to_identifier(alias.output_name)) 4138 4139 return self.alias_sql(expression) 4140 4141 def aliases_sql(self, expression: exp.Aliases) -> str: 4142 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4143 4144 def atindex_sql(self, expression: exp.AtIndex) -> str: 4145 this = self.sql(expression, "this") 4146 index = self.sql(expression, "expression") 4147 return f"{this} AT {index}" 4148 4149 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4150 this = self.sql(expression, "this") 4151 zone = self.sql(expression, "zone") 4152 return f"{this} AT TIME ZONE {zone}" 4153 4154 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4155 this = self.sql(expression, "this") 4156 zone = self.sql(expression, "zone") 4157 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4158 4159 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4160 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4161 4162 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4163 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4164 4165 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4166 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4167 4168 def add_sql(self, expression: exp.Add) -> str: 4169 return self.binary(expression, "+") 4170 4171 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4172 return self.connector_sql(expression, "AND", stack) 4173 4174 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4175 return self.connector_sql(expression, "OR", stack) 4176 4177 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4178 return self.connector_sql(expression, "XOR", stack) 4179 4180 def connector_sql( 4181 self, 4182 expression: exp.Connector, 4183 op: str, 4184 stack: list[str | exp.Expr] | None = None, 4185 ) -> str: 4186 if stack is not None: 4187 stack.append(expression.right) 4188 if expression.comments and self.comments: 4189 op = self.maybe_comment(op, comments=expression.comments) 4190 4191 stack.extend((op, expression.left)) 4192 return op 4193 4194 stack = [expression] 4195 sqls: list[str] = [] 4196 ops = set() 4197 4198 while stack: 4199 node = stack.pop() 4200 if isinstance(node, exp.Connector): 4201 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4202 else: 4203 sql = self.sql(node) 4204 if sqls and sqls[-1] in ops: 4205 sqls[-1] += f" {sql}" 4206 else: 4207 sqls.append(sql) 4208 4209 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4210 return sep.join(sqls) 4211 4212 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4213 return self.binary(expression, "&") 4214 4215 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4216 return self.binary(expression, "<<") 4217 4218 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4219 return f"~{self.sql(expression, 'this')}" 4220 4221 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4222 return self.binary(expression, "|") 4223 4224 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4225 return self.binary(expression, ">>") 4226 4227 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4228 return self.binary(expression, "^") 4229 4230 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4231 format_sql = self.sql(expression, "format") 4232 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4233 to_sql = self.sql(expression, "to") 4234 to_sql = f" {to_sql}" if to_sql else "" 4235 action = self.sql(expression, "action") 4236 action = f" {action}" if action else "" 4237 default = self.sql(expression, "default") 4238 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4239 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4240 4241 # Base implementation that excludes safe, zone, and target_type metadata args 4242 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4243 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4244 4245 # Base implementation that excludes the safe and default_year metadata args 4246 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4247 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4248 4249 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4250 return self.func( 4251 "PARSE_DATETIME", 4252 expression.this, 4253 expression.args.get("format"), 4254 expression.args.get("zone"), 4255 ) 4256 4257 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4258 zone = self.sql(expression, "this") 4259 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4260 4261 def collate_sql(self, expression: exp.Collate) -> str: 4262 if self.COLLATE_IS_FUNC: 4263 return self.function_fallback_sql(expression) 4264 return self.binary(expression, "COLLATE") 4265 4266 def command_sql(self, expression: exp.Command) -> str: 4267 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4268 4269 def comment_sql(self, expression: exp.Comment) -> str: 4270 this = self.sql(expression, "this") 4271 kind = expression.args["kind"] 4272 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4273 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4274 expression_sql = self.sql(expression, "expression") 4275 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4276 4277 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4278 this = self.sql(expression, "this") 4279 delete = " DELETE" if expression.args.get("delete") else "" 4280 recompress = self.sql(expression, "recompress") 4281 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4282 to_disk = self.sql(expression, "to_disk") 4283 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4284 to_volume = self.sql(expression, "to_volume") 4285 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4286 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4287 4288 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4289 where = self.sql(expression, "where") 4290 group = self.sql(expression, "group") 4291 aggregates = self.expressions(expression, key="aggregates") 4292 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4293 4294 if not (where or group or aggregates) and len(expression.expressions) == 1: 4295 return f"TTL {self.expressions(expression, flat=True)}" 4296 4297 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4298 4299 def transaction_sql(self, expression: exp.Transaction) -> str: 4300 modes = self.expressions(expression, key="modes") 4301 modes = f" {modes}" if modes else "" 4302 return f"BEGIN{modes}" 4303 4304 def commit_sql(self, expression: exp.Commit) -> str: 4305 chain = expression.args.get("chain") 4306 if chain is not None: 4307 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4308 4309 return f"COMMIT{chain or ''}" 4310 4311 def rollback_sql(self, expression: exp.Rollback) -> str: 4312 savepoint = expression.args.get("savepoint") 4313 savepoint = f" TO {savepoint}" if savepoint else "" 4314 return f"ROLLBACK{savepoint}" 4315 4316 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4317 this = self.sql(expression, "this") 4318 4319 exists = "" 4320 if expression.args.get("exists"): 4321 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4322 exists = " IF EXISTS" 4323 else: 4324 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4325 4326 dtype = self.sql(expression, "dtype") 4327 if dtype: 4328 collate = self.sql(expression, "collate") 4329 collate = f" COLLATE {collate}" if collate else "" 4330 using = self.sql(expression, "using") 4331 using = f" USING {using}" if using else "" 4332 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4333 null_constraint = self._alter_column_null_constraint_sql(expression) 4334 4335 return ( 4336 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4337 f"{collate}{using}{null_constraint}" 4338 ) 4339 4340 default = self.sql(expression, "default") 4341 if default: 4342 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4343 4344 comment = self.sql(expression, "comment") 4345 if comment: 4346 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4347 4348 visible = expression.args.get("visible") 4349 if visible: 4350 return f"ALTER COLUMN{exists} {this} SET {visible}" 4351 4352 allow_null = expression.args.get("allow_null") 4353 drop = expression.args.get("drop") 4354 4355 if not drop and not allow_null: 4356 self.unsupported("Unsupported ALTER COLUMN syntax") 4357 4358 if allow_null is not None: 4359 keyword = "DROP" if drop else "SET" 4360 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4361 4362 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4363 4364 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4365 allow_null = expression.args.get("allow_null") 4366 if allow_null is None: 4367 return "" 4368 4369 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4370 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4371 return "" 4372 4373 return " NULL" if allow_null else " NOT NULL" 4374 4375 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4376 this = self.sql(expression, "this") 4377 rename_from = self.sql(expression, "rename_from") 4378 if rename_from: 4379 if not self.SUPPORTS_CHANGE_COLUMN: 4380 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4381 return f"CHANGE COLUMN {rename_from} {this}" 4382 if not self.SUPPORTS_MODIFY_COLUMN: 4383 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4384 return f"MODIFY COLUMN {this}" 4385 4386 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4387 this = self.sql(expression, "this") 4388 4389 visible = expression.args.get("visible") 4390 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4391 4392 return f"ALTER INDEX {this} {visible_sql}" 4393 4394 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4395 this = self.sql(expression, "this") 4396 if not isinstance(expression.this, exp.Var): 4397 this = f"KEY DISTKEY {this}" 4398 return f"ALTER DISTSTYLE {this}" 4399 4400 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4401 compound = " COMPOUND" if expression.args.get("compound") else "" 4402 this = self.sql(expression, "this") 4403 expressions = self.expressions(expression, flat=True) 4404 expressions = f"({expressions})" if expressions else "" 4405 return f"ALTER{compound} SORTKEY {this or expressions}" 4406 4407 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4408 if not self.RENAME_TABLE_WITH_DB: 4409 # Remove db from tables 4410 expression = expression.transform( 4411 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4412 ).assert_is(exp.AlterRename) 4413 this = self.sql(expression, "this") 4414 to_kw = " TO" if include_to else "" 4415 return f"RENAME{to_kw} {this}" 4416 4417 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4418 exists = " IF EXISTS" if expression.args.get("exists") else "" 4419 old_column = self.sql(expression, "this") 4420 new_column = self.sql(expression, "to") 4421 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4422 4423 def alterset_sql(self, expression: exp.AlterSet) -> str: 4424 exprs = self.expressions(expression, flat=True) 4425 if self.ALTER_SET_WRAPPED: 4426 exprs = f"({exprs})" 4427 4428 return f"SET {exprs}" 4429 4430 def alter_sql(self, expression: exp.Alter) -> str: 4431 actions = expression.actions 4432 4433 if ( 4434 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4435 and actions 4436 and isinstance(actions[0], exp.ColumnDef) 4437 ): 4438 actions_sql = self.expressions(expression, key="actions", flat=True) 4439 actions_sql = f"ADD {actions_sql}" 4440 else: 4441 actions_list = [] 4442 for action in actions: 4443 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4444 action_sql = self.add_column_sql(action) 4445 else: 4446 action_sql = self.sql(action) 4447 if isinstance(action, exp.Query): 4448 action_sql = f"AS {action_sql}" 4449 4450 actions_list.append(action_sql) 4451 4452 actions_sql = self.format_args(*actions_list).lstrip("\n") 4453 4454 iceberg = ( 4455 "ICEBERG " 4456 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4457 else "" 4458 ) 4459 exists = " IF EXISTS" if expression.args.get("exists") else "" 4460 on_cluster = self.sql(expression, "cluster") 4461 on_cluster = f" {on_cluster}" if on_cluster else "" 4462 only = " ONLY" if expression.args.get("only") else "" 4463 options = expression.args.get("options") or [] 4464 options_sql = self.expressions( 4465 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4466 ) 4467 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4468 partitions_sql = self.expressions( 4469 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4470 sep=" ", 4471 flat=True, 4472 ) 4473 if partitions_sql and (actions_sql or options_sql): 4474 partitions_sql = f" {partitions_sql}" 4475 kind = self.sql(expression, "kind") 4476 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4477 check = " WITH CHECK" if expression.args.get("check") else "" 4478 cascade = ( 4479 " CASCADE" 4480 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4481 else "" 4482 ) 4483 this = self.sql(expression, "this") 4484 this = f" {this}" if this else "" 4485 4486 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4487 4488 def altersession_sql(self, expression: exp.AlterSession) -> str: 4489 items_sql = self.expressions(expression, flat=True) 4490 keyword = "UNSET" if expression.args.get("unset") else "SET" 4491 return f"{keyword} {items_sql}" 4492 4493 def add_column_sql(self, expression: exp.Expr) -> str: 4494 sql = self.sql(expression) 4495 if isinstance(expression, exp.Schema): 4496 column_text = " COLUMNS" 4497 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4498 column_text = " COLUMN" 4499 else: 4500 column_text = "" 4501 4502 return f"ADD{column_text} {sql}" 4503 4504 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4505 expressions = self.expressions(expression) 4506 exists = " IF EXISTS " if expression.args.get("exists") else " " 4507 return f"DROP{exists}{expressions}" 4508 4509 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4510 return "DROP PRIMARY KEY" 4511 4512 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4513 return f"ADD {self.expressions(expression, indent=False)}" 4514 4515 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4516 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4517 location = self.sql(expression, "location") 4518 location = f" {location}" if location else "" 4519 return f"ADD {exists}{self.sql(expression.this)}{location}" 4520 4521 def distinct_sql(self, expression: exp.Distinct) -> str: 4522 this = self.expressions(expression, flat=True) 4523 4524 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4525 case = exp.case() 4526 for arg in expression.expressions: 4527 case = case.when(arg.is_(exp.null()), exp.null()) 4528 this = self.sql(case.else_(f"({this})")) 4529 4530 this = f" {this}" if this else "" 4531 4532 on = self.sql(expression, "on") 4533 on = f" ON {on}" if on else "" 4534 return f"DISTINCT{this}{on}" 4535 4536 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4537 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4538 4539 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4540 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4541 4542 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4543 this_sql = self.sql(expression, "this") 4544 expression_sql = self.sql(expression, "expression") 4545 kind = "MAX" if expression.args.get("max") else "MIN" 4546 return f"{this_sql} HAVING {kind} {expression_sql}" 4547 4548 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4549 return self._truncated_div_sql(expression.this, expression.expression) 4550 4551 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4552 div: exp.Expr = exp.Div(this=this, expression=divisor) 4553 if self.CAST_TO_INT_ROUNDS: 4554 div = exp.Trunc(this=div) 4555 4556 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4557 4558 def dpipe_sql(self, expression: exp.DPipe) -> str: 4559 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4560 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4561 return self.binary(expression, "||") 4562 4563 def div_sql(self, expression: exp.Div) -> str: 4564 l, r = expression.left, expression.right 4565 4566 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4567 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4568 4569 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4570 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4571 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4572 4573 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4574 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4575 # Native integer division depends on the target's operand types, which may differ 4576 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4577 # integer literals or casts 4578 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4579 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4580 ): 4581 return self.sql(exp.IntDiv(this=l, expression=r)) 4582 return self._truncated_div_sql(l, r) 4583 4584 return self.binary(expression, "/") 4585 4586 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4587 n = exp._wrap(expression.this, exp.Binary) 4588 d = exp._wrap(expression.expression, exp.Binary) 4589 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4590 4591 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4592 return self.binary(expression, "OVERLAPS") 4593 4594 def distance_sql(self, expression: exp.Distance) -> str: 4595 return self.binary(expression, "<->") 4596 4597 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4598 return self.binary(expression, "<<->>") 4599 4600 def dot_sql(self, expression: exp.Dot) -> str: 4601 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4602 4603 def eq_sql(self, expression: exp.EQ) -> str: 4604 return self.binary(expression, "=") 4605 4606 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4607 return self.binary(expression, ":=") 4608 4609 def escape_sql(self, expression: exp.Escape) -> str: 4610 this = expression.this 4611 if ( 4612 isinstance(this, (exp.Like, exp.ILike)) 4613 and isinstance(this.expression, (exp.All, exp.Any)) 4614 and not self.SUPPORTS_LIKE_QUANTIFIERS 4615 ): 4616 return self._like_sql(this, escape=expression) 4617 return self.binary(expression, "ESCAPE") 4618 4619 def glob_sql(self, expression: exp.Glob) -> str: 4620 return self.binary(expression, "GLOB") 4621 4622 def gt_sql(self, expression: exp.GT) -> str: 4623 return self.binary(expression, ">") 4624 4625 def gte_sql(self, expression: exp.GTE) -> str: 4626 return self.binary(expression, ">=") 4627 4628 def is_sql(self, expression: exp.Is) -> str: 4629 negate = expression.args.get("negate") 4630 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4631 positive = bool(expression.expression.this) != bool(negate) 4632 return self.sql(expression.this if positive else exp.not_(expression.this)) 4633 return self.binary(expression, "IS NOT" if negate else "IS") 4634 4635 def _like_sql( 4636 self, 4637 expression: exp.Like | exp.ILike, 4638 escape: exp.Escape | None = None, 4639 ) -> str: 4640 this = expression.this 4641 rhs = expression.expression 4642 4643 if isinstance(expression, exp.Like): 4644 exp_class: type[exp.Like | exp.ILike] = exp.Like 4645 op = "LIKE" 4646 else: 4647 exp_class = exp.ILike 4648 op = "ILIKE" 4649 4650 if expression.args.get("negate"): 4651 op = f"NOT {op}" 4652 4653 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4654 exprs = rhs.this.unnest() 4655 4656 if isinstance(exprs, exp.Tuple): 4657 exprs = exprs.expressions 4658 else: 4659 exprs = [exprs] 4660 4661 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4662 4663 def _make_like(expr: exp.Expression) -> exp.Expression: 4664 like: exp.Expression = exp_class( 4665 this=this, expression=expr, negate=expression.args.get("negate") 4666 ) 4667 if escape: 4668 like = exp.Escape(this=like, expression=escape.expression.copy()) 4669 return like 4670 4671 like_expr: exp.Expr = _make_like(exprs[0]) 4672 for expr in exprs[1:]: 4673 like_expr = connective(like_expr, _make_like(expr), copy=False) 4674 4675 parent = escape.parent if escape else expression.parent 4676 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4677 parent, exp.Condition 4678 ): 4679 like_expr = exp.paren(like_expr, copy=False) 4680 4681 return self.sql(like_expr) 4682 4683 return self.binary(expression, op) 4684 4685 def like_sql(self, expression: exp.Like) -> str: 4686 return self._like_sql(expression) 4687 4688 def ilike_sql(self, expression: exp.ILike) -> str: 4689 return self._like_sql(expression) 4690 4691 def match_sql(self, expression: exp.Match) -> str: 4692 return self.binary(expression, "MATCH") 4693 4694 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4695 return self.binary(expression, "SIMILAR TO") 4696 4697 def lt_sql(self, expression: exp.LT) -> str: 4698 return self.binary(expression, "<") 4699 4700 def lte_sql(self, expression: exp.LTE) -> str: 4701 return self.binary(expression, "<=") 4702 4703 def mod_sql(self, expression: exp.Mod) -> str: 4704 this = self.sql(expression, "this") 4705 expr = self.sql(expression, "expression") 4706 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4707 4708 parent = expression.parent 4709 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4710 return f"({sql})" 4711 4712 return sql 4713 4714 def mul_sql(self, expression: exp.Mul) -> str: 4715 return self.binary(expression, "*") 4716 4717 def neq_sql(self, expression: exp.NEQ) -> str: 4718 return self.binary(expression, "<>") 4719 4720 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4721 return self.binary(expression, "IS NOT DISTINCT FROM") 4722 4723 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4724 return self.binary(expression, "IS DISTINCT FROM") 4725 4726 def sub_sql(self, expression: exp.Sub) -> str: 4727 return self.binary(expression, "-") 4728 4729 def trycast_sql(self, expression: exp.TryCast) -> str: 4730 return self.cast_sql(expression, safe_prefix="TRY_") 4731 4732 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4733 return self.cast_sql(expression) 4734 4735 def try_sql(self, expression: exp.Try) -> str: 4736 if not self.TRY_SUPPORTED: 4737 self.unsupported("Unsupported TRY function") 4738 return self.sql(expression, "this") 4739 4740 return self.func("TRY", expression.this) 4741 4742 def log_sql(self, expression: exp.Log) -> str: 4743 this = expression.this 4744 expr = expression.expression 4745 4746 if self.dialect.LOG_BASE_FIRST is False: 4747 this, expr = expr, this 4748 elif self.dialect.LOG_BASE_FIRST is None and expr: 4749 if this.name in ("2", "10"): 4750 return self.func(f"LOG{this.name}", expr) 4751 4752 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4753 4754 return self.func("LOG", this, expr) 4755 4756 def use_sql(self, expression: exp.Use) -> str: 4757 kind = self.sql(expression, "kind") 4758 kind = f" {kind}" if kind else "" 4759 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4760 this = f" {this}" if this else "" 4761 return f"USE{kind}{this}" 4762 4763 def binary(self, expression: exp.Binary, op: str) -> str: 4764 sqls: list[str] = [] 4765 stack: list[None | str | exp.Expr] = [expression] 4766 binary_type = type(expression) 4767 4768 while stack: 4769 node = stack.pop() 4770 4771 if type(node) is binary_type: 4772 op_func = node.args.get("operator") 4773 if op_func: 4774 op = f"OPERATOR({self.sql(op_func)})" 4775 4776 stack.append(node.args.get("expression")) 4777 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4778 stack.append(node.args.get("this")) 4779 else: 4780 sqls.append(self.sql(node)) 4781 4782 return "".join(sqls) 4783 4784 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4785 to_clause = self.sql(expression, "to") 4786 if to_clause: 4787 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4788 4789 return self.function_fallback_sql(expression) 4790 4791 def function_fallback_sql(self, expression: exp.Func) -> str: 4792 args = [] 4793 4794 for key in expression.arg_types: 4795 arg_value = expression.args.get(key) 4796 4797 if isinstance(arg_value, list): 4798 for value in arg_value: 4799 args.append(value) 4800 elif arg_value is not None: 4801 args.append(arg_value) 4802 4803 return self.func(self.function_name(expression), *args) 4804 4805 def function_name(self, expression: exp.Func) -> str: 4806 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4807 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4808 4809 def func( 4810 self, 4811 name: str, 4812 *args: t.Any, 4813 prefix: str = "(", 4814 suffix: str = ")", 4815 normalize: bool = True, 4816 ) -> str: 4817 name = self.normalize_func(name) if normalize else name 4818 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4819 4820 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4821 arg_sqls = tuple( 4822 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4823 ) 4824 if self.pretty and self.too_wide(arg_sqls): 4825 return self.indent( 4826 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4827 ) 4828 return sep.join(arg_sqls) 4829 4830 def too_wide(self, args: t.Iterable) -> bool: 4831 return sum(len(arg) for arg in args) > self.max_text_width 4832 4833 def format_time( 4834 self, 4835 expression: exp.Expr, 4836 inverse_time_mapping: dict[str, str] | None = None, 4837 inverse_time_trie: dict | None = None, 4838 ) -> str | None: 4839 return format_time( 4840 self.sql(expression, "format"), 4841 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4842 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4843 ) 4844 4845 def expressions( 4846 self, 4847 expression: exp.Expr | None = None, 4848 key: str | None = None, 4849 sqls: t.Collection[str | exp.Expr] | None = None, 4850 flat: bool = False, 4851 indent: bool = True, 4852 skip_first: bool = False, 4853 skip_last: bool = False, 4854 sep: str = ", ", 4855 prefix: str = "", 4856 dynamic: bool = False, 4857 new_line: bool = False, 4858 ) -> str: 4859 expressions = expression.args.get(key or "expressions") if expression else sqls 4860 4861 if not expressions: 4862 return "" 4863 4864 if flat: 4865 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4866 4867 num_sqls = len(expressions) 4868 result_sqls = [] 4869 4870 for i, e in enumerate(expressions): 4871 sql = self.sql(e, comment=False) 4872 if not sql: 4873 continue 4874 4875 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4876 4877 if self.pretty: 4878 if self.leading_comma: 4879 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4880 else: 4881 result_sqls.append( 4882 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4883 ) 4884 else: 4885 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4886 4887 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4888 if new_line: 4889 result_sqls.insert(0, "") 4890 result_sqls.append("") 4891 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4892 else: 4893 result_sql = "".join(result_sqls) 4894 4895 return ( 4896 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4897 if indent 4898 else result_sql 4899 ) 4900 4901 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4902 flat = flat or isinstance(expression.parent, exp.Properties) 4903 expressions_sql = self.expressions(expression, flat=flat) 4904 if flat: 4905 return f"{op} {expressions_sql}" 4906 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4907 4908 def naked_property(self, expression: exp.Property) -> str: 4909 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4910 if not property_name: 4911 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4912 return f"{property_name} {self.sql(expression, 'this')}" 4913 4914 def tag_sql(self, expression: exp.Tag) -> str: 4915 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4916 4917 def token_sql(self, token_type: TokenType) -> str: 4918 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4919 4920 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4921 this = self.sql(expression, "this") 4922 expressions = self.no_identify(self.expressions, expression) 4923 expressions = ( 4924 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4925 ) 4926 return f"{this}{expressions}" if expressions.strip() != "" else this 4927 4928 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4929 return self.expressions(expression, flat=True) 4930 4931 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4932 params = self.no_identify(self.expressions, expression, flat=True) 4933 body = self.sql(expression, "this") 4934 prefix = "TABLE " if expression.args.get("is_table") else "" 4935 return f"({params}) AS {prefix}{body}" 4936 4937 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4938 this = self.sql(expression, "this") 4939 expressions = self.expressions(expression, flat=True) 4940 return f"{this}({expressions})" 4941 4942 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4943 return self.binary(expression, "=>") 4944 4945 def when_sql(self, expression: exp.When) -> str: 4946 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4947 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4948 condition = self.sql(expression, "condition") 4949 condition = f" AND {condition}" if condition else "" 4950 4951 then_expression = expression.args.get("then") 4952 if isinstance(then_expression, exp.Insert): 4953 this = self.sql(then_expression, "this") 4954 this = f"INSERT {this}" if this else "INSERT" 4955 then = self.sql(then_expression, "expression") 4956 then = f"{this} VALUES {then}" if then else this 4957 elif isinstance(then_expression, exp.Update): 4958 if isinstance(then_expression.args.get("expressions"), exp.Star): 4959 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4960 else: 4961 expressions_sql = self.expressions(then_expression) 4962 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4963 else: 4964 then = self.sql(then_expression) 4965 4966 if isinstance(then_expression, (exp.Insert, exp.Update)): 4967 where = self.sql(then_expression, "where") 4968 if where and not self.SUPPORTS_MERGE_WHERE: 4969 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4970 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4971 where = "" 4972 then = f"{then}{where}" 4973 return f"WHEN {matched}{source}{condition} THEN {then}" 4974 4975 def whens_sql(self, expression: exp.Whens) -> str: 4976 return self.expressions(expression, sep=" ", indent=False) 4977 4978 def merge_sql(self, expression: exp.Merge) -> str: 4979 table = expression.this 4980 table_alias = "" 4981 4982 hints = table.args.get("hints") 4983 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4984 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4985 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4986 4987 this = self.sql(table) 4988 using = f"USING {self.sql(expression, 'using')}" 4989 whens = self.sql(expression, "whens") 4990 4991 on = self.sql(expression, "on") 4992 on = f"ON {on}" if on else "" 4993 4994 if not on: 4995 on = self.expressions(expression, key="using_cond") 4996 on = f"USING ({on})" if on else "" 4997 4998 returning = self.sql(expression, "returning") 4999 if returning: 5000 whens = f"{whens}{returning}" 5001 5002 sep = self.sep() 5003 5004 return self.prepend_ctes( 5005 expression, 5006 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5007 ) 5008 5009 @unsupported_args("format") 5010 def tochar_sql(self, expression: exp.ToChar) -> str: 5011 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5012 5013 @unsupported_args("default") 5014 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5015 if not self.SUPPORTS_TO_NUMBER: 5016 self.unsupported("Unsupported TO_NUMBER function") 5017 return self._tonumber_cast_sql(expression) 5018 5019 fmt = expression.args.get("format") 5020 if not fmt: 5021 self.unsupported("Conversion format is required for TO_NUMBER") 5022 return self._tonumber_cast_sql(expression) 5023 5024 return self.func("TO_NUMBER", expression.this, fmt) 5025 5026 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5027 if expression.args.get("safe"): 5028 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5029 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5030 5031 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5032 this = self.sql(expression, "this") 5033 kind = self.sql(expression, "kind") 5034 settings_sql = self.expressions(expression, key="settings", sep=" ") 5035 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5036 return f"{this}({kind}{args})" 5037 5038 def dictrange_sql(self, expression: exp.DictRange) -> str: 5039 this = self.sql(expression, "this") 5040 max = self.sql(expression, "max") 5041 min = self.sql(expression, "min") 5042 return f"{this}(MIN {min} MAX {max})" 5043 5044 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5045 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5046 5047 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5048 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5049 5050 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5051 def uniquekeyproperty_sql( 5052 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5053 ) -> str: 5054 return f"{prefix} ({self.expressions(expression, flat=True)})" 5055 5056 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5057 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5058 expressions = self.expressions(expression, flat=True) 5059 expressions = f" {self.wrap(expressions)}" if expressions else "" 5060 buckets = self.sql(expression, "buckets") 5061 kind = self.sql(expression, "kind") 5062 buckets = f" BUCKETS {buckets}" if buckets else "" 5063 order = self.sql(expression, "order") 5064 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5065 5066 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5067 return "" 5068 5069 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5070 expressions = self.expressions(expression, key="expressions", flat=True) 5071 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5072 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5073 buckets = self.sql(expression, "buckets") 5074 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5075 5076 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5077 this = self.sql(expression, "this") 5078 having = self.sql(expression, "having") 5079 5080 if having: 5081 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5082 5083 return self.func("ANY_VALUE", this) 5084 5085 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5086 transform = self.func("TRANSFORM", *expression.expressions) 5087 row_format_before = self.sql(expression, "row_format_before") 5088 row_format_before = f" {row_format_before}" if row_format_before else "" 5089 record_writer = self.sql(expression, "record_writer") 5090 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5091 using = f" USING {self.sql(expression, 'command_script')}" 5092 schema = self.sql(expression, "schema") 5093 schema = f" AS {schema}" if schema else "" 5094 row_format_after = self.sql(expression, "row_format_after") 5095 row_format_after = f" {row_format_after}" if row_format_after else "" 5096 record_reader = self.sql(expression, "record_reader") 5097 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5098 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5099 5100 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5101 key_block_size = self.sql(expression, "key_block_size") 5102 if key_block_size: 5103 return f"KEY_BLOCK_SIZE = {key_block_size}" 5104 5105 using = self.sql(expression, "using") 5106 if using: 5107 return f"USING {using}" 5108 5109 parser = self.sql(expression, "parser") 5110 if parser: 5111 return f"WITH PARSER {parser}" 5112 5113 comment = self.sql(expression, "comment") 5114 if comment: 5115 return f"COMMENT {comment}" 5116 5117 visible = expression.args.get("visible") 5118 if visible is not None: 5119 return "VISIBLE" if visible else "INVISIBLE" 5120 5121 engine_attr = self.sql(expression, "engine_attr") 5122 if engine_attr: 5123 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5124 5125 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5126 if secondary_engine_attr: 5127 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5128 5129 self.unsupported("Unsupported index constraint option.") 5130 return "" 5131 5132 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5133 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5134 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5135 5136 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5137 kind = self.sql(expression, "kind") 5138 kind = f"{kind} INDEX" if kind else "INDEX" 5139 this = self.sql(expression, "this") 5140 this = f" {this}" if this else "" 5141 index_type = self.sql(expression, "index_type") 5142 index_type = f" USING {index_type}" if index_type else "" 5143 expressions = self.expressions(expression, flat=True) 5144 expressions = f" ({expressions})" if expressions else "" 5145 options = self.expressions(expression, key="options", sep=" ") 5146 options = f" {options}" if options else "" 5147 return f"{kind}{this}{index_type}{expressions}{options}" 5148 5149 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5150 if self.NVL2_SUPPORTED: 5151 return self.function_fallback_sql(expression) 5152 5153 case = exp.Case().when( 5154 expression.this.is_(exp.null()).not_(copy=False), 5155 expression.args["true"], 5156 copy=False, 5157 ) 5158 else_cond = expression.args.get("false") 5159 if else_cond: 5160 case.else_(else_cond, copy=False) 5161 5162 return self.sql(case) 5163 5164 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5165 if expression.args.get("from_first") is False: 5166 self.unsupported("NTH_VALUE FROM LAST is not supported") 5167 5168 return self.function_fallback_sql(expression) 5169 5170 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5171 this = self.sql(expression, "this") 5172 expr = self.sql(expression, "expression") 5173 position = self.sql(expression, "position") 5174 position = f", {position}" if position else "" 5175 iterator = self.sql(expression, "iterator") 5176 condition = self.sql(expression, "condition") 5177 condition = f" IF {condition}" if condition else "" 5178 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5179 5180 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5181 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5182 5183 def opclass_sql(self, expression: exp.Opclass) -> str: 5184 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5185 5186 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5187 model = self.sql(expression, "this") 5188 model = f"MODEL {model}" 5189 expr = expression.expression 5190 if expr: 5191 expr_sql = self.sql(expression, "expression") 5192 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5193 else: 5194 expr_sql = None 5195 5196 parameters = self.sql(expression, "params_struct") or None 5197 5198 return self.func(name, model, expr_sql, parameters) 5199 5200 def predict_sql(self, expression: exp.Predict) -> str: 5201 return self._ml_sql(expression, "PREDICT") 5202 5203 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5204 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5205 return self._ml_sql(expression, name) 5206 5207 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5208 return self._ml_sql(expression, "GENERATE_TEXT") 5209 5210 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5211 return self._ml_sql(expression, "GENERATE_TABLE") 5212 5213 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5214 return self._ml_sql(expression, "GENERATE_BOOL") 5215 5216 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5217 return self._ml_sql(expression, "GENERATE_INT") 5218 5219 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5220 return self._ml_sql(expression, "GENERATE_DOUBLE") 5221 5222 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5223 return self._ml_sql(expression, "TRANSLATE") 5224 5225 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5226 return self._ml_sql(expression, "FORECAST") 5227 5228 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5229 this_sql = self.sql(expression, "this") 5230 if isinstance(expression.this, exp.Table): 5231 this_sql = f"TABLE {this_sql}" 5232 5233 return self.func( 5234 "FORECAST", 5235 this_sql, 5236 expression.args.get("data_col"), 5237 expression.args.get("timestamp_col"), 5238 expression.args.get("model"), 5239 expression.args.get("id_cols"), 5240 expression.args.get("horizon"), 5241 expression.args.get("forecast_end_timestamp"), 5242 expression.args.get("confidence_level"), 5243 expression.args.get("output_historical_time_series"), 5244 expression.args.get("context_window"), 5245 ) 5246 5247 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5248 this_sql = self.sql(expression, "this") 5249 if isinstance(expression.this, exp.Table): 5250 this_sql = f"TABLE {this_sql}" 5251 5252 return self.func( 5253 "FEATURES_AT_TIME", 5254 this_sql, 5255 expression.args.get("time"), 5256 expression.args.get("num_rows"), 5257 expression.args.get("ignore_feature_nulls"), 5258 ) 5259 5260 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5261 this_sql = self.sql(expression, "this") 5262 if isinstance(expression.this, exp.Table): 5263 this_sql = f"TABLE {this_sql}" 5264 5265 query_table = self.sql(expression, "query_table") 5266 if isinstance(expression.args["query_table"], exp.Table): 5267 query_table = f"TABLE {query_table}" 5268 5269 return self.func( 5270 "VECTOR_SEARCH", 5271 this_sql, 5272 expression.args.get("column_to_search"), 5273 query_table, 5274 expression.args.get("query_column_to_search"), 5275 expression.args.get("top_k"), 5276 expression.args.get("distance_type"), 5277 expression.args.get("options"), 5278 ) 5279 5280 def forin_sql(self, expression: exp.ForIn) -> str: 5281 this = self.sql(expression, "this") 5282 expression_sql = self.sql(expression, "expression") 5283 return f"FOR {this} DO {expression_sql}" 5284 5285 def refresh_sql(self, expression: exp.Refresh) -> str: 5286 this = self.sql(expression, "this") 5287 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5288 return f"REFRESH {kind}{this}" 5289 5290 def toarray_sql(self, expression: exp.ToArray) -> str: 5291 arg = expression.this 5292 if not arg.type: 5293 import sqlglot.optimizer.annotate_types 5294 5295 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5296 5297 if arg.is_type(exp.DType.ARRAY): 5298 return self.sql(arg) 5299 5300 cond_for_null = arg.is_(exp.null()) 5301 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5302 5303 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5304 this = expression.this 5305 time_format = self.format_time(expression) 5306 5307 if time_format: 5308 return self.sql( 5309 exp.cast( 5310 exp.StrToTime(this=this, format=expression.args["format"]), 5311 exp.DType.TIME, 5312 ) 5313 ) 5314 5315 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5316 return self.sql(this) 5317 5318 return self.sql(exp.cast(this, exp.DType.TIME)) 5319 5320 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5321 this = expression.this 5322 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5323 return self.sql(this) 5324 5325 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5326 5327 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5328 this = expression.this 5329 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5330 return self.sql(this) 5331 5332 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5333 5334 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5335 this = expression.this 5336 time_format = self.format_time(expression) 5337 safe = expression.args.get("safe") 5338 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5339 return self.sql( 5340 exp.cast( 5341 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5342 exp.DType.DATE, 5343 ) 5344 ) 5345 5346 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5347 return self.sql(this) 5348 5349 if safe: 5350 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5351 5352 return self.sql(exp.cast(this, exp.DType.DATE)) 5353 5354 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5355 return self.sql( 5356 exp.func( 5357 "DATEDIFF", 5358 expression.this, 5359 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5360 "day", 5361 ) 5362 ) 5363 5364 def lastday_sql(self, expression: exp.LastDay) -> str: 5365 if self.LAST_DAY_SUPPORTS_DATE_PART: 5366 return self.function_fallback_sql(expression) 5367 5368 unit = expression.args.get("unit") 5369 if unit and unit.name.upper() != "MONTH": 5370 self.unsupported("Date parts are not supported in LAST_DAY.") 5371 5372 return self.func("LAST_DAY", expression.this) 5373 5374 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5375 import sqlglot.dialects.dialect 5376 5377 return self.func( 5378 "DATE_ADD", 5379 expression.this, 5380 expression.expression, 5381 sqlglot.dialects.dialect.unit_to_str(expression), 5382 ) 5383 5384 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5385 this = expression.this 5386 position = expression.args["position"] 5387 offset = index_offset - (expression.args.get("offset") or 0) 5388 5389 if offset: 5390 if position.is_int: 5391 value = position.to_py() 5392 if value >= 0: 5393 position = exp.Literal.number(value + offset) 5394 elif offset < 0 and value == -1: 5395 # 1-based -1 appends, which a 0-based position can only express as the size 5396 position = exp.ArraySize(this=this.copy()) 5397 else: 5398 # Negative positions count from the end, so they shift in the opposite 5399 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5400 position = exp.Literal.number(value - offset) 5401 else: 5402 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5403 5404 return self.func("ARRAY_INSERT", this, position, expression.expression) 5405 5406 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5407 if self.CAN_IMPLEMENT_ARRAY_ANY: 5408 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5409 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5410 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5411 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5412 5413 import sqlglot.dialects.dialect 5414 5415 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5416 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5417 self.unsupported("ARRAY_ANY is unsupported") 5418 5419 return self.function_fallback_sql(expression) 5420 5421 def struct_sql(self, expression: exp.Struct) -> str: 5422 expression.set( 5423 "expressions", 5424 [ 5425 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5426 if isinstance(e, exp.PropertyEQ) 5427 else e 5428 for e in expression.expressions 5429 ], 5430 ) 5431 5432 return self.function_fallback_sql(expression) 5433 5434 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5435 low = self.sql(expression, "this") 5436 high = self.sql(expression, "expression") 5437 5438 return f"{low} TO {high}" 5439 5440 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5441 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5442 tables = f" {self.expressions(expression)}" 5443 5444 exists = " IF EXISTS" if expression.args.get("exists") else "" 5445 5446 on_cluster = self.sql(expression, "cluster") 5447 on_cluster = f" {on_cluster}" if on_cluster else "" 5448 5449 identity = self.sql(expression, "identity") 5450 identity = f" {identity} IDENTITY" if identity else "" 5451 5452 option = self.sql(expression, "option") 5453 option = f" {option}" if option else "" 5454 5455 partition = self.sql(expression, "partition") 5456 partition = f" {partition}" if partition else "" 5457 5458 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5459 5460 # This transpiles T-SQL's CONVERT function 5461 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5462 def convert_sql(self, expression: exp.Convert) -> str: 5463 to = expression.this 5464 value = expression.expression 5465 style = expression.args.get("style") 5466 safe = expression.args.get("safe") 5467 strict = expression.args.get("strict") 5468 5469 if not to or not value: 5470 return "" 5471 5472 # Retrieve length of datatype and override to default if not specified 5473 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5474 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5475 5476 transformed: exp.Expr | None = None 5477 cast = exp.Cast if strict else exp.TryCast 5478 5479 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5480 if isinstance(style, exp.Literal) and style.is_int: 5481 import sqlglot.dialects.tsql 5482 5483 style_value = style.name 5484 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5485 if not converted_style: 5486 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5487 5488 fmt = exp.Literal.string(converted_style) 5489 5490 if to.this == exp.DType.DATE: 5491 transformed = exp.StrToDate(this=value, format=fmt) 5492 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5493 transformed = exp.StrToTime(this=value, format=fmt) 5494 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5495 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5496 elif to.this == exp.DType.TEXT: 5497 transformed = exp.TimeToStr(this=value, format=fmt) 5498 5499 if not transformed: 5500 transformed = cast(this=value, to=to, safe=safe) 5501 5502 return self.sql(transformed) 5503 5504 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5505 this = expression.this 5506 if isinstance(this, exp.JSONPathWildcard): 5507 this = self.json_path_part(this) 5508 return f".{this}" if this else "" 5509 5510 quoted = expression.args.get("quoted") 5511 if not ( 5512 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5513 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5514 return f".{this}" 5515 5516 this = self.json_path_part(this) 5517 5518 return ( 5519 f"[{this}]" 5520 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5521 else f".{this}" 5522 ) 5523 5524 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5525 this = self.json_path_part(expression.this) 5526 return f"[{this}]" if this else "" 5527 5528 def _simplify_unless_literal(self, expression: E) -> E: 5529 if not isinstance(expression, exp.Literal): 5530 import sqlglot.optimizer.simplify 5531 5532 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5533 5534 return expression 5535 5536 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5537 this = expression.this 5538 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5539 self.unsupported( 5540 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5541 ) 5542 return self.sql(this) 5543 5544 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5545 if self.IGNORE_NULLS_BEFORE_ORDER: 5546 from sqlglot.optimizer.scope import find_all_in_scope 5547 5548 # The first modifier here will be the one closest to the AggFunc's arg 5549 mods = sorted( 5550 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5551 key=lambda x: ( 5552 0 5553 if isinstance(x, exp.HavingMax) 5554 else (1 if isinstance(x, exp.Order) else 2) 5555 ), 5556 ) 5557 5558 if mods: 5559 mod = mods[0] 5560 this = expression.__class__(this=mod.this.copy()) 5561 this.meta["inline"] = True 5562 mod.this.replace(this) 5563 return self.sql(expression.this) 5564 5565 agg_func = expression.find(exp.AggFunc) 5566 5567 if agg_func: 5568 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5569 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5570 5571 return f"{self.sql(expression, 'this')} {text}" 5572 5573 def _replace_line_breaks(self, string: str) -> str: 5574 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5575 if self.pretty: 5576 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5577 return string 5578 5579 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5580 option = self.sql(expression, "this") 5581 5582 if expression.expressions: 5583 upper = option.upper() 5584 5585 # Snowflake FILE_FORMAT options are separated by whitespace 5586 sep = " " if upper == "FILE_FORMAT" else ", " 5587 5588 # Databricks copy/format options do not set their list of values with EQ 5589 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5590 values = self.expressions(expression, flat=True, sep=sep) 5591 return f"{option}{op}({values})" 5592 5593 value = self.sql(expression, "expression") 5594 5595 if not value: 5596 return option 5597 5598 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5599 5600 return f"{option}{op}{value}" 5601 5602 def credentials_sql(self, expression: exp.Credentials) -> str: 5603 cred_expr = expression.args.get("credentials") 5604 if isinstance(cred_expr, exp.Literal): 5605 # Redshift case: CREDENTIALS <string> 5606 credentials = self.sql(expression, "credentials") 5607 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5608 else: 5609 # Snowflake case: CREDENTIALS = (...) 5610 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5611 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5612 5613 storage = self.sql(expression, "storage") 5614 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5615 5616 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5617 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5618 5619 iam_role = self.sql(expression, "iam_role") 5620 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5621 5622 region = self.sql(expression, "region") 5623 region = f" REGION {region}" if region else "" 5624 5625 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5626 5627 def copy_sql(self, expression: exp.Copy) -> str: 5628 this = self.sql(expression, "this") 5629 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5630 5631 credentials = self.sql(expression, "credentials") 5632 credentials = self.seg(credentials) if credentials else "" 5633 files = self.expressions(expression, key="files", flat=True) 5634 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5635 5636 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5637 params = self.expressions( 5638 expression, 5639 key="params", 5640 sep=sep, 5641 new_line=True, 5642 skip_last=True, 5643 skip_first=True, 5644 indent=self.COPY_PARAMS_ARE_WRAPPED, 5645 ) 5646 5647 if params: 5648 if self.COPY_PARAMS_ARE_WRAPPED: 5649 params = f" WITH ({params})" 5650 elif not self.pretty and (files or credentials): 5651 params = f" {params}" 5652 5653 return f"COPY{this}{kind} {files}{credentials}{params}" 5654 5655 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5656 return "" 5657 5658 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5659 on_sql = "ON" if expression.args.get("on") else "OFF" 5660 filter_col: str | None = self.sql(expression, "filter_column") 5661 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5662 retention_period: str | None = self.sql(expression, "retention_period") 5663 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5664 5665 if filter_col or retention_period: 5666 on_sql = self.func("ON", filter_col, retention_period) 5667 5668 return f"DATA_DELETION={on_sql}" 5669 5670 def maskingpolicycolumnconstraint_sql( 5671 self, expression: exp.MaskingPolicyColumnConstraint 5672 ) -> str: 5673 this = self.sql(expression, "this") 5674 expressions = self.expressions(expression, flat=True) 5675 expressions = f" USING ({expressions})" if expressions else "" 5676 return f"MASKING POLICY {this}{expressions}" 5677 5678 def gapfill_sql(self, expression: exp.GapFill) -> str: 5679 this = self.sql(expression, "this") 5680 this = f"TABLE {this}" 5681 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5682 5683 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5684 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5685 5686 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5687 this = self.sql(expression, "this") 5688 expr = expression.expression 5689 5690 if isinstance(expr, exp.Func): 5691 # T-SQL's CLR functions are case sensitive 5692 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5693 else: 5694 expr = self.sql(expression, "expression") 5695 5696 return self.scope_resolution(expr, this) 5697 5698 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5699 if self.PARSE_JSON_NAME is None: 5700 return self.sql(expression.this) 5701 5702 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5703 5704 def rand_sql(self, expression: exp.Rand) -> str: 5705 lower = self.sql(expression, "lower") 5706 upper = self.sql(expression, "upper") 5707 5708 if lower and upper: 5709 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5710 return self.func("RAND", expression.this) 5711 5712 def changes_sql(self, expression: exp.Changes) -> str: 5713 information = self.sql(expression, "information") 5714 information = f"INFORMATION => {information}" 5715 at_before = self.sql(expression, "at_before") 5716 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5717 end = self.sql(expression, "end") 5718 end = f"{self.seg('')}{end}" if end else "" 5719 5720 return f"CHANGES ({information}){at_before}{end}" 5721 5722 def pad_sql(self, expression: exp.Pad) -> str: 5723 prefix = "L" if expression.args.get("is_left") else "R" 5724 5725 fill_pattern = self.sql(expression, "fill_pattern") or None 5726 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5727 fill_pattern = "' '" 5728 5729 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5730 5731 def summarize_sql(self, expression: exp.Summarize) -> str: 5732 table = " TABLE" if expression.args.get("table") else "" 5733 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5734 5735 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5736 generate_series = exp.GenerateSeries(**expression.args) 5737 5738 parent = expression.parent 5739 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5740 parent = parent.parent 5741 5742 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5743 return self.sql(exp.Unnest(expressions=[generate_series])) 5744 5745 if isinstance(parent, exp.Select): 5746 self.unsupported("GenerateSeries projection unnesting is not supported.") 5747 5748 return self.sql(generate_series) 5749 5750 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5751 if self.SUPPORTS_CONVERT_TIMEZONE: 5752 return self.function_fallback_sql(expression) 5753 5754 source_tz = expression.args.get("source_tz") 5755 target_tz = expression.args.get("target_tz") 5756 timestamp = expression.args.get("timestamp") 5757 5758 if source_tz and timestamp: 5759 timestamp = exp.AtTimeZone( 5760 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5761 ) 5762 5763 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5764 5765 return self.sql(expr) 5766 5767 def json_sql(self, expression: exp.JSON) -> str: 5768 this = self.sql(expression, "this") 5769 this = f" {this}" if this else "" 5770 5771 _with = expression.args.get("with_") 5772 5773 if _with is None: 5774 with_sql = "" 5775 elif not _with: 5776 with_sql = " WITHOUT" 5777 else: 5778 with_sql = " WITH" 5779 5780 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5781 5782 return f"JSON{this}{with_sql}{unique_sql}" 5783 5784 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5785 path = self.sql(expression, "path") 5786 returning = self.sql(expression, "returning") 5787 returning = f" RETURNING {returning}" if returning else "" 5788 5789 on_condition = self.sql(expression, "on_condition") 5790 on_condition = f" {on_condition}" if on_condition else "" 5791 5792 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5793 5794 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5795 regexp = " REGEXP" if expression.args.get("regexp") else "" 5796 return f"SKIP{regexp} {self.sql(expression.expression)}" 5797 5798 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5799 else_ = "ELSE " if expression.args.get("else_") else "" 5800 condition = self.sql(expression, "expression") 5801 condition = f"WHEN {condition} THEN " if condition else else_ 5802 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5803 return f"{condition}{insert}" 5804 5805 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5806 kind = self.sql(expression, "kind") 5807 source = self.sql(expression, "source") 5808 5809 if kind: 5810 expressions = self.seg(self.expressions(expression, sep=" ")) 5811 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5812 else: 5813 # If kind isn't present, we have Hive's FROM-first form: 5814 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5815 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5816 sql = f"FROM {source}{self.seg(inserts)}" 5817 5818 return self.prepend_ctes(expression, sql) 5819 5820 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5821 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5822 empty = expression.args.get("empty") 5823 empty = ( 5824 f"DEFAULT {empty} ON EMPTY" 5825 if isinstance(empty, exp.Expr) 5826 else self.sql(expression, "empty") 5827 ) 5828 5829 error = expression.args.get("error") 5830 error = ( 5831 f"DEFAULT {error} ON ERROR" 5832 if isinstance(error, exp.Expr) 5833 else self.sql(expression, "error") 5834 ) 5835 5836 if error and empty: 5837 error = ( 5838 f"{empty} {error}" 5839 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5840 else f"{error} {empty}" 5841 ) 5842 empty = "" 5843 5844 null = self.sql(expression, "null") 5845 5846 return f"{empty}{error}{null}" 5847 5848 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5849 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5850 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5851 5852 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5853 this = self.sql(expression, "this") 5854 path = self.sql(expression, "path") 5855 5856 passing = self.expressions(expression, "passing") 5857 passing = f" PASSING {passing}" if passing else "" 5858 5859 on_condition = self.sql(expression, "on_condition") 5860 on_condition = f" {on_condition}" if on_condition else "" 5861 5862 path = f"{path}{passing}{on_condition}" 5863 5864 return self.func("JSON_EXISTS", this, path) 5865 5866 def _add_arrayagg_null_filter( 5867 self, 5868 array_agg_sql: str, 5869 array_agg_expr: exp.ArrayAgg, 5870 column_expr: exp.Expr, 5871 ) -> str: 5872 """ 5873 Add NULL filter to ARRAY_AGG if dialect requires it. 5874 5875 Args: 5876 array_agg_sql: The generated ARRAY_AGG SQL string 5877 array_agg_expr: The ArrayAgg expression node 5878 column_expr: The column/expression to filter (before ORDER BY wrapping) 5879 5880 Returns: 5881 SQL string with FILTER clause added if needed 5882 """ 5883 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5884 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5885 if not ( 5886 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5887 ): 5888 return array_agg_sql 5889 5890 parent = array_agg_expr.parent 5891 if isinstance(parent, exp.Filter): 5892 parent_cond = parent.expression.this 5893 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5894 elif column_expr.find(exp.Column): 5895 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5896 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5897 this_sql = ( 5898 self.expressions(column_expr) 5899 if isinstance(column_expr, exp.Distinct) 5900 else self.sql(column_expr) 5901 ) 5902 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5903 5904 return array_agg_sql 5905 5906 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5907 array_agg = self.function_fallback_sql(expression) 5908 column_expr = expression.this 5909 if isinstance(column_expr, exp.Order): 5910 column_expr = column_expr.this 5911 5912 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5913 5914 def slice_sql(self, expression: exp.Slice) -> str: 5915 step = self.sql(expression, "step") 5916 end = self.sql(expression.expression) 5917 begin = self.sql(expression.this) 5918 5919 sql = f"{end}:{step}" if step else end 5920 return f"{begin}:{sql}" if sql else f"{begin}:" 5921 5922 def apply_sql(self, expression: exp.Apply) -> str: 5923 this = self.sql(expression, "this") 5924 expr = self.sql(expression, "expression") 5925 5926 return f"{this} APPLY({expr})" 5927 5928 def _grant_or_revoke_sql( 5929 self, 5930 expression: exp.Grant | exp.Revoke, 5931 keyword: str, 5932 preposition: str, 5933 grant_option_prefix: str = "", 5934 grant_option_suffix: str = "", 5935 ) -> str: 5936 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5937 5938 kind = self.sql(expression, "kind") 5939 kind = f" {kind}" if kind else "" 5940 5941 securable = self.sql(expression, "securable") 5942 securable = f" {securable}" if securable else "" 5943 5944 principals = self.expressions(expression, key="principals", flat=True) 5945 5946 if not expression.args.get("grant_option"): 5947 grant_option_prefix = grant_option_suffix = "" 5948 5949 # cascade for revoke only 5950 cascade = self.sql(expression, "cascade") 5951 cascade = f" {cascade}" if cascade else "" 5952 5953 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5954 5955 def grant_sql(self, expression: exp.Grant) -> str: 5956 return self._grant_or_revoke_sql( 5957 expression, 5958 keyword="GRANT", 5959 preposition="TO", 5960 grant_option_suffix=" WITH GRANT OPTION", 5961 ) 5962 5963 def revoke_sql(self, expression: exp.Revoke) -> str: 5964 return self._grant_or_revoke_sql( 5965 expression, 5966 keyword="REVOKE", 5967 preposition="FROM", 5968 grant_option_prefix="GRANT OPTION FOR ", 5969 ) 5970 5971 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5972 this = self.sql(expression, "this") 5973 columns = self.expressions(expression, flat=True) 5974 columns = f"({columns})" if columns else "" 5975 5976 return f"{this}{columns}" 5977 5978 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5979 this = self.sql(expression, "this") 5980 5981 kind = self.sql(expression, "kind") 5982 kind = f"{kind} " if kind else "" 5983 5984 return f"{kind}{this}" 5985 5986 def columns_sql(self, expression: exp.Columns) -> str: 5987 func = self.function_fallback_sql(expression) 5988 if expression.args.get("unpack"): 5989 func = f"*{func}" 5990 5991 return func 5992 5993 def overlay_sql(self, expression: exp.Overlay) -> str: 5994 this = self.sql(expression, "this") 5995 expr = self.sql(expression, "expression") 5996 from_sql = self.sql(expression, "from_") 5997 for_sql = self.sql(expression, "for_") 5998 for_sql = f" FOR {for_sql}" if for_sql else "" 5999 6000 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6001 6002 @unsupported_args("format") 6003 def todouble_sql(self, expression: exp.ToDouble) -> str: 6004 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6005 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6006 6007 def string_sql(self, expression: exp.String) -> str: 6008 this = expression.this 6009 zone = expression.args.get("zone") 6010 6011 if zone: 6012 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6013 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6014 # set for source_tz to transpile the time conversion before the STRING cast 6015 this = exp.ConvertTimezone( 6016 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6017 ) 6018 6019 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6020 6021 def median_sql(self, expression: exp.Median) -> str: 6022 if not self.SUPPORTS_MEDIAN: 6023 return self.sql( 6024 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6025 ) 6026 6027 return self.function_fallback_sql(expression) 6028 6029 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6030 filler = self.sql(expression, "this") 6031 filler = f" {filler}" if filler else "" 6032 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6033 return f"TRUNCATE{filler} {with_count}" 6034 6035 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6036 if self.SUPPORTS_UNIX_SECONDS: 6037 return self.function_fallback_sql(expression) 6038 6039 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6040 6041 return self.sql( 6042 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6043 ) 6044 6045 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6046 dim = expression.expression 6047 6048 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6049 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6050 if not (dim.is_int and dim.name == "1"): 6051 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6052 dim = None 6053 6054 # If dimension is required but not specified, default initialize it 6055 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6056 dim = exp.Literal.number(1) 6057 6058 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6059 6060 def attach_sql(self, expression: exp.Attach) -> str: 6061 this = self.sql(expression, "this") 6062 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6063 expressions = self.expressions(expression) 6064 expressions = f" ({expressions})" if expressions else "" 6065 6066 return f"ATTACH{exists_sql} {this}{expressions}" 6067 6068 def detach_sql(self, expression: exp.Detach) -> str: 6069 kind = self.sql(expression, "kind") 6070 kind = f" {kind}" if kind else "" 6071 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6072 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6073 exists = " IF EXISTS" if expression.args.get("exists") else "" 6074 if exists: 6075 kind = kind or " DATABASE" 6076 6077 this = self.sql(expression, "this") 6078 this = f" {this}" if this else "" 6079 cluster = self.sql(expression, "cluster") 6080 cluster = f" {cluster}" if cluster else "" 6081 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6082 sync = " SYNC" if expression.args.get("sync") else "" 6083 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6084 6085 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6086 this = self.sql(expression, "this") 6087 value = self.sql(expression, "expression") 6088 value = f" {value}" if value else "" 6089 return f"{this}{value}" 6090 6091 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6092 return ( 6093 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6094 ) 6095 6096 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6097 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6098 encode = f"{encode} {self.sql(expression, 'this')}" 6099 6100 properties = expression.args.get("properties") 6101 if properties: 6102 encode = f"{encode} {self.properties(properties)}" 6103 6104 return encode 6105 6106 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6107 this = self.sql(expression, "this") 6108 include = f"INCLUDE {this}" 6109 6110 column_def = self.sql(expression, "column_def") 6111 if column_def: 6112 include = f"{include} {column_def}" 6113 6114 alias = self.sql(expression, "alias") 6115 if alias: 6116 include = f"{include} AS {alias}" 6117 6118 return include 6119 6120 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6121 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6122 name = f"{prefix} {self.sql(expression, 'this')}" 6123 return self.func("XMLELEMENT", name, *expression.expressions) 6124 6125 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6126 this = self.sql(expression, "this") 6127 expr = self.sql(expression, "expression") 6128 expr = f"({expr})" if expr else "" 6129 return f"{this}{expr}" 6130 6131 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6132 partitions = self.expressions(expression, "partition_expressions") 6133 create = self.expressions(expression, "create_expressions") 6134 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6135 6136 def partitionbyrangepropertydynamic_sql( 6137 self, expression: exp.PartitionByRangePropertyDynamic 6138 ) -> str: 6139 start = self.sql(expression, "start") 6140 end = self.sql(expression, "end") 6141 6142 every = expression.args["every"] 6143 if isinstance(every, exp.Interval) and every.this.is_string: 6144 every.this.replace(exp.Literal.number(every.name)) 6145 6146 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6147 6148 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6149 name = self.sql(expression, "this") 6150 values = self.expressions(expression, flat=True) 6151 6152 return f"NAME {name} VALUE {values}" 6153 6154 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6155 kind = self.sql(expression, "kind") 6156 sample = self.sql(expression, "sample") 6157 return f"SAMPLE {sample} {kind}" 6158 6159 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6160 kind = self.sql(expression, "kind") 6161 option = self.sql(expression, "option") 6162 option = f" {option}" if option else "" 6163 this = self.sql(expression, "this") 6164 this = f" {this}" if this else "" 6165 columns = self.expressions(expression) 6166 columns = f" {columns}" if columns else "" 6167 return f"{kind}{option} STATISTICS{this}{columns}" 6168 6169 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6170 this = self.sql(expression, "this") 6171 columns = self.expressions(expression) 6172 inner_expression = self.sql(expression, "expression") 6173 inner_expression = f" {inner_expression}" if inner_expression else "" 6174 update_options = self.sql(expression, "update_options") 6175 update_options = f" {update_options} UPDATE" if update_options else "" 6176 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6177 6178 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6179 kind = self.sql(expression, "kind") 6180 kind = f" {kind}" if kind else "" 6181 return f"DELETE{kind} STATISTICS" 6182 6183 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6184 inner_expression = self.sql(expression, "expression") 6185 return f"LIST CHAINED ROWS{inner_expression}" 6186 6187 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6188 kind = self.sql(expression, "kind") 6189 this = self.sql(expression, "this") 6190 this = f" {this}" if this else "" 6191 inner_expression = self.sql(expression, "expression") 6192 return f"VALIDATE {kind}{this}{inner_expression}" 6193 6194 def analyze_sql(self, expression: exp.Analyze) -> str: 6195 options = self.expressions(expression, key="options", sep=" ") 6196 options = f" {options}" if options else "" 6197 kind = self.sql(expression, "kind") 6198 kind = f" {kind}" if kind else "" 6199 tables = self.expressions(expression, key="tables", flat=True) 6200 tables = f" {tables}" if tables else "" 6201 mode = self.sql(expression, "mode") 6202 mode = f" {mode}" if mode else "" 6203 properties = self.sql(expression, "properties") 6204 properties = f" {properties}" if properties else "" 6205 partition = self.sql(expression, "partition") 6206 partition = f" {partition}" if partition else "" 6207 inner_expression = self.sql(expression, "expression") 6208 inner_expression = f" {inner_expression}" if inner_expression else "" 6209 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6210 6211 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6212 this = self.sql(expression, "this") 6213 namespaces = self.expressions(expression, key="namespaces") 6214 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6215 passing = self.expressions(expression, key="passing") 6216 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6217 columns = self.expressions(expression, key="columns") 6218 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6219 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6220 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6221 6222 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6223 this = self.sql(expression, "this") 6224 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6225 6226 def export_sql(self, expression: exp.Export) -> str: 6227 this = self.sql(expression, "this") 6228 connection = self.sql(expression, "connection") 6229 connection = f"WITH CONNECTION {connection} " if connection else "" 6230 options = self.sql(expression, "options") 6231 return f"EXPORT DATA {connection}{options} AS {this}" 6232 6233 def declare_sql(self, expression: exp.Declare) -> str: 6234 replace = "OR REPLACE " if expression.args.get("replace") else "" 6235 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6236 6237 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6238 variables = self.expressions(expression, "this") 6239 default = self.sql(expression, "default") 6240 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6241 6242 kind = self.sql(expression, "kind") 6243 if isinstance(expression.args.get("kind"), exp.Schema): 6244 kind = f"TABLE {kind}" 6245 6246 kind = f" {kind}" if kind else "" 6247 6248 return f"{variables}{kind}{default}" 6249 6250 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6251 kind = self.sql(expression, "kind") 6252 this = self.sql(expression, "this") 6253 set = self.sql(expression, "expression") 6254 to = self.sql(expression, "to") 6255 to = f" TO {to}" if to else "" 6256 default = self.sql(expression, "default") 6257 default = f" DEFAULT {default}" if default else "" 6258 using = self.sql(expression, "using") 6259 using = f" USING {using}" if using else "" 6260 6261 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6262 6263 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6264 6265 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6266 params = self.expressions(expression, key="params", flat=True) 6267 return self.func(expression.name, *expression.expressions) + f"({params})" 6268 6269 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6270 return self.func(expression.name, *expression.expressions) 6271 6272 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6273 return self.anonymousaggfunc_sql(expression) 6274 6275 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6276 return self.parameterizedagg_sql(expression) 6277 6278 def show_sql(self, expression: exp.Show) -> str: 6279 self.unsupported("Unsupported SHOW statement") 6280 return "" 6281 6282 def install_sql(self, expression: exp.Install) -> str: 6283 self.unsupported("Unsupported INSTALL statement") 6284 return "" 6285 6286 def liststage_sql(self, expression: exp.ListStage) -> str: 6287 pattern = self.sql(expression, "pattern") 6288 pattern = f" PATTERN = {pattern}" if pattern else "" 6289 return f"LIST {self.sql(expression, 'this')}{pattern}" 6290 6291 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6292 # Snowflake GET/PUT statements: 6293 # PUT <file> <internalStage> <properties> 6294 # GET <internalStage> <file> <properties> 6295 props = expression.args.get("properties") 6296 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6297 this = self.sql(expression, "this") 6298 target = self.sql(expression, "target") 6299 6300 if isinstance(expression, exp.Put): 6301 return f"PUT {this} {target}{props_sql}" 6302 else: 6303 return f"GET {target} {this}{props_sql}" 6304 6305 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6306 this = self.sql(expression, "this") 6307 expr = self.sql(expression, "expression") 6308 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6309 return f"TRANSLATE({this} USING {expr}{with_error})" 6310 6311 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6312 if self.SUPPORTS_DECODE_CASE: 6313 return self.func("DECODE", *expression.expressions) 6314 6315 decode_expr, *expressions = expression.expressions 6316 6317 ifs = [] 6318 for search, result in zip(expressions[::2], expressions[1::2]): 6319 if isinstance(search, exp.Literal): 6320 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6321 elif isinstance(search, exp.Null): 6322 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6323 else: 6324 if isinstance(search, exp.Binary): 6325 search = exp.paren(search) 6326 6327 cond = exp.or_( 6328 decode_expr.eq(search), 6329 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6330 copy=False, 6331 ) 6332 ifs.append(exp.If(this=cond, true=result)) 6333 6334 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6335 return self.sql(case) 6336 6337 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6338 this = self.sql(expression, "this") 6339 this = self.seg(this, sep="") 6340 dimensions = self.expressions( 6341 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6342 ) 6343 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6344 metrics = self.expressions( 6345 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6346 ) 6347 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6348 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6349 facts = self.seg(f"FACTS {facts}") if facts else "" 6350 where = self.sql(expression, "where") 6351 where = self.seg(f"WHERE {where}") if where else "" 6352 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6353 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6354 6355 def getextract_sql(self, expression: exp.GetExtract) -> str: 6356 this = expression.this 6357 expr = expression.expression 6358 6359 if not this.type or not expression.type: 6360 import sqlglot.optimizer.annotate_types 6361 6362 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6363 6364 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6365 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6366 6367 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6368 6369 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6370 return self.sql( 6371 exp.DateAdd( 6372 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6373 expression=expression.this, 6374 unit=exp.var("DAY"), 6375 ) 6376 ) 6377 6378 def space_sql(self: Generator, expression: exp.Space) -> str: 6379 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6380 6381 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6382 return f"BUILD {self.sql(expression, 'this')}" 6383 6384 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6385 method = self.sql(expression, "method") 6386 kind = expression.args.get("kind") 6387 if not kind: 6388 return f"REFRESH {method}" 6389 6390 every = self.sql(expression, "every") 6391 unit = self.sql(expression, "unit") 6392 every = f" EVERY {every} {unit}" if every else "" 6393 starts = self.sql(expression, "starts") 6394 starts = f" STARTS {starts}" if starts else "" 6395 6396 return f"REFRESH {method} ON {kind}{every}{starts}" 6397 6398 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6399 self.unsupported("The model!attribute syntax is not supported") 6400 return "" 6401 6402 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6403 return self.func("DIRECTORY", expression.this) 6404 6405 def uuid_sql(self, expression: exp.Uuid) -> str: 6406 is_string = expression.args.get("is_string", False) 6407 uuid_func_sql = self.func("UUID") 6408 6409 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6410 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6411 6412 return uuid_func_sql 6413 6414 def initcap_sql(self, expression: exp.Initcap) -> str: 6415 delimiters = expression.expression 6416 6417 if delimiters: 6418 # do not generate delimiters arg if we are round-tripping from default delimiters 6419 if ( 6420 delimiters.is_string 6421 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6422 ): 6423 delimiters = None 6424 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6425 self.unsupported("INITCAP does not support custom delimiters") 6426 delimiters = None 6427 6428 return self.func("INITCAP", expression.this, delimiters) 6429 6430 def localtime_sql(self, expression: exp.Localtime) -> str: 6431 this = expression.this 6432 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6433 6434 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6435 this = expression.this 6436 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6437 6438 def weekstart_name(self, expression: exp.WeekStart) -> str: 6439 import sqlglot.dialects.dialect 6440 6441 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6442 this = expression.this.name.upper() 6443 6444 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6445 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6446 6447 if dow_from_week_start_day != dow_from_week_offset: 6448 self.unsupported( 6449 f"WEEK({this}) is not supported; falling back to the default week start day" 6450 ) 6451 6452 return "WEEK" 6453 6454 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6455 name = self.weekstart_name(expression) 6456 6457 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6458 if isinstance(expression.parent, exp.DateTrunc): 6459 return self.sql(exp.Literal.string(name)) 6460 6461 return name 6462 6463 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6464 this = self.expressions(expression) 6465 charset = self.sql(expression, "charset") 6466 using = f" USING {charset}" if charset else "" 6467 return self.func(name, this + using) 6468 6469 def weightstring_sql(self, expression: exp.WeightString) -> str: 6470 to = self.sql(expression, "to") 6471 to = f" AS {to}" if to else "" 6472 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6473 6474 def block_sql(self, expression: exp.Block) -> str: 6475 expressions = self.expressions(expression, sep="; ", flat=True) 6476 begin = "BEGIN " if expression.args.get("begin") else "" 6477 return f"{begin}{expressions}" if expressions else "" 6478 6479 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6480 self.unsupported("Unsupported Inline UDFs syntax") 6481 return "" 6482 6483 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6484 self.unsupported("Unsupported Stored Procedure syntax") 6485 return "" 6486 6487 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6488 self.unsupported("Unsupported If block syntax") 6489 return "" 6490 6491 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6492 self.unsupported("Unsupported Case statement syntax") 6493 return "" 6494 6495 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6496 self.unsupported("Unsupported While block syntax") 6497 return "" 6498 6499 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6500 self.unsupported("Unsupported Loop block syntax") 6501 return "" 6502 6503 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6504 self.unsupported("Unsupported Repeat block syntax") 6505 return "" 6506 6507 def leave_sql(self, expression: exp.Leave) -> str: 6508 self.unsupported("Unsupported Leave syntax") 6509 return "" 6510 6511 def iterate_sql(self, expression: exp.Iterate) -> str: 6512 self.unsupported("Unsupported Iterate syntax") 6513 return "" 6514 6515 def execute_sql(self, expression: exp.Execute) -> str: 6516 self.unsupported("Unsupported Execute syntax") 6517 return "" 6518 6519 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6520 self.unsupported("Unsupported Execute syntax") 6521 return "" 6522 6523 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6524 props = self.expressions(expression, sep=" ") 6525 return f"MODIFY {props}" 6526 6527 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6528 kind = expression.args.get("kind") 6529 return f"USING {kind} {self.sql(expression, 'this')}" 6530 6531 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6532 this = self.sql(expression, "this") 6533 to = self.sql(expression, "to") 6534 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)
919 def __init__( 920 self, 921 pretty: bool | int | None = None, 922 identify: str | bool = False, 923 normalize: bool = False, 924 pad: int = 2, 925 indent: int = 2, 926 normalize_functions: str | bool | None = None, 927 unsupported_level: ErrorLevel = ErrorLevel.WARN, 928 max_unsupported: int = 3, 929 leading_comma: bool = False, 930 max_text_width: int = 80, 931 comments: bool = True, 932 dialect: DialectType = None, 933 ): 934 import sqlglot 935 import sqlglot.dialects.dialect 936 937 self.pretty = pretty if pretty is not None else sqlglot.pretty 938 self.identify = identify 939 self.normalize = normalize 940 self.pad = pad 941 self._indent = indent 942 self.unsupported_level = unsupported_level 943 self.max_unsupported = max_unsupported 944 self.leading_comma = leading_comma 945 self.max_text_width = max_text_width 946 self.comments = comments 947 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 948 949 # This is both a Dialect property and a Generator argument, so we prioritize the latter 950 self.normalize_functions = ( 951 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 952 ) 953 954 self.unsupported_messages: list[str] = [] 955 self._escaped_quote_end: str = ( 956 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 957 ) 958 self._escaped_byte_quote_end: str = ( 959 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 960 if self.dialect.BYTE_END 961 else "" 962 ) 963 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 964 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 965 966 self._next_name = name_sequence("_t") 967 968 self._identifier_start = self.dialect.IDENTIFIER_START 969 self._identifier_end = self.dialect.IDENTIFIER_END 970 971 self._quote_json_path_key_using_brackets = True 972 973 cls = type(self) 974 dispatch = _DISPATCH_CACHE.get(cls) 975 if dispatch is None: 976 dispatch = _build_dispatch(cls) 977 _DISPATCH_CACHE[cls] = dispatch 978 self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] =
{<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsTopKey'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] =
()
SUPPORTED_JSON_PATH_PARTS: ClassVar =
{<class 'sqlglot.expressions.query.JSONPathSelector'>, <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.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.CHAR: 'CHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.NCHAR: 'NCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
MOD_PAREN_PARENT_TYPES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Mul'>, <class 'sqlglot.expressions.core.Div'>, <class 'sqlglot.expressions.core.IntDiv'>, <class 'sqlglot.expressions.core.Mod'>)
980 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 981 """ 982 Generates the SQL string corresponding to the given syntax tree. 983 984 Args: 985 expression: The syntax tree. 986 copy: Whether to copy the expression. The generator performs mutations so 987 it is safer to copy. 988 989 Returns: 990 The SQL string corresponding to `expression`. 991 """ 992 if copy: 993 expression = expression.copy() 994 995 expression = self.preprocess(expression) 996 997 self.unsupported_messages = [] 998 sql = self.sql(expression).strip() 999 1000 if self.pretty: 1001 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1002 1003 if self.unsupported_level == ErrorLevel.IGNORE: 1004 return sql 1005 1006 if self.unsupported_level == ErrorLevel.WARN: 1007 for msg in self.unsupported_messages: 1008 logger.warning(msg) 1009 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1010 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1011 1012 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.
1014 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1015 """Apply generic preprocessing transformations to a given expression.""" 1016 expression = self._move_ctes_to_top_level(expression) 1017 1018 if self.ENSURE_BOOLS: 1019 import sqlglot.transforms 1020 1021 expression = sqlglot.transforms.ensure_bools(expression) 1022 1023 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1047 def sanitize_comment(self, comment: str) -> str: 1048 comment = " " + comment if comment[0].strip() else comment 1049 comment = comment + " " if comment[-1].strip() else comment 1050 1051 # Escape block comment markers to prevent premature closure or unintended nesting. 1052 # This is necessary because single-line comments (--) are converted to block comments 1053 # (/* */) on output, and any */ in the original text would close the comment early. 1054 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1055 1056 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1058 def maybe_comment( 1059 self, 1060 sql: str, 1061 expression: exp.Expr | None = None, 1062 comments: list[str] | None = None, 1063 separated: bool = False, 1064 ) -> str: 1065 comments = ( 1066 ((expression and expression.comments) if comments is None else comments) # type: ignore 1067 if self.comments 1068 else None 1069 ) 1070 1071 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1072 return sql 1073 1074 comments_list = [ 1075 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1076 for comment in comments 1077 if comment 1078 ] 1079 1080 if not comments_list: 1081 return sql 1082 1083 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1084 comments_sql = self.sep().join(comments_list) 1085 return ( 1086 f"{self.sep()}{comments_sql}{sql}" 1087 if not sql or sql[0].isspace() 1088 else f"{comments_sql}{self.sep()}{sql}" 1089 ) 1090 1091 return f"{sql} {' '.join(comments_list)}"
1093 def wrap(self, expression: exp.Expr | str) -> str: 1094 this_sql = ( 1095 self.sql(expression) 1096 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1097 else self.sql(expression, "this") 1098 ) 1099 if not this_sql: 1100 return "()" 1101 1102 this_sql = self.indent(this_sql, level=1, pad=0) 1103 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:
1119 def indent( 1120 self, 1121 sql: str, 1122 level: int = 0, 1123 pad: int | None = None, 1124 skip_first: bool = False, 1125 skip_last: bool = False, 1126 ) -> str: 1127 if not self.pretty or not sql: 1128 return sql 1129 1130 pad = self.pad if pad is None else pad 1131 lines = sql.split("\n") 1132 1133 return "\n".join( 1134 ( 1135 line 1136 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1137 else f"{' ' * (level * self._indent + pad)}{line}" 1138 ) 1139 for i, line in enumerate(lines) 1140 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1142 def sql( 1143 self, 1144 expression: str | exp.Expr | None, 1145 key: str | None = None, 1146 comment: bool = True, 1147 ) -> str: 1148 if not expression: 1149 return "" 1150 1151 if isinstance(expression, str): 1152 return expression 1153 1154 if key: 1155 value = expression.args.get(key) 1156 if value: 1157 return self.sql(value) 1158 return "" 1159 1160 handler = self._dispatch.get(expression.__class__) 1161 1162 if handler: 1163 sql = handler(self, expression) 1164 elif isinstance(expression, exp.Func): 1165 sql = self.function_fallback_sql(expression) 1166 elif isinstance(expression, exp.Property): 1167 sql = self.property_sql(expression) 1168 else: 1169 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1170 1171 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1178 def cache_sql(self, expression: exp.Cache) -> str: 1179 lazy = " LAZY" if expression.args.get("lazy") else "" 1180 table = self.sql(expression, "this") 1181 options = expression.args.get("options") 1182 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1183 sql = self.sql(expression, "expression") 1184 sql = f" AS{self.sep()}{sql}" if sql else "" 1185 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1186 return self.prepend_ctes(expression, sql)
1192 def column_parts(self, expression: exp.Column) -> str: 1193 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1194 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1195 return self.sql(expression, "this") 1196 1197 return ".".join( 1198 self.sql(part) 1199 for part in ( 1200 expression.args.get("catalog"), 1201 expression.args.get("db"), 1202 expression.args.get("table"), 1203 expression.args.get("this"), 1204 ) 1205 if part 1206 )
1208 def column_sql(self, expression: exp.Column) -> str: 1209 join_mark = " (+)" if expression.args.get("join_mark") else "" 1210 1211 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1212 join_mark = "" 1213 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1214 1215 return f"{self.column_parts(expression)}{join_mark}"
1226 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1227 column = self.sql(expression, "this") 1228 kind = self.sql(expression, "kind") 1229 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1230 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1231 kind = f"{sep}{kind}" if kind else "" 1232 constraints = f" {constraints}" if constraints else "" 1233 position = self.sql(expression, "position") 1234 position = f" {position}" if position else "" 1235 1236 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1237 kind = "" 1238 1239 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:
1246 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1247 this = self.sql(expression, "this") 1248 if expression.args.get("not_null"): 1249 persisted = " PERSISTED NOT NULL" 1250 elif expression.args.get("persisted"): 1251 persisted = " PERSISTED" 1252 else: 1253 persisted = "" 1254 1255 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:
1268 def generatedasidentitycolumnconstraint_sql( 1269 self, expression: exp.GeneratedAsIdentityColumnConstraint 1270 ) -> str: 1271 this = "" 1272 if expression.this is not None: 1273 on_null = " ON NULL" if expression.args.get("on_null") else "" 1274 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1275 1276 start = expression.args.get("start") 1277 start = f"START WITH {start}" if start else "" 1278 increment = expression.args.get("increment") 1279 increment = f" INCREMENT BY {increment}" if increment else "" 1280 minvalue = expression.args.get("minvalue") 1281 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1282 maxvalue = expression.args.get("maxvalue") 1283 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1284 cycle = expression.args.get("cycle") 1285 cycle_sql = "" 1286 1287 if cycle is not None: 1288 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1289 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1290 1291 sequence_opts = "" 1292 if start or increment or cycle_sql: 1293 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1294 sequence_opts = f" ({sequence_opts.strip()})" 1295 1296 expr = self.sql(expression, "expression") 1297 expr = f"({expr})" if expr else "IDENTITY" 1298 1299 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1301 def generatedasrowcolumnconstraint_sql( 1302 self, expression: exp.GeneratedAsRowColumnConstraint 1303 ) -> str: 1304 start = "START" if expression.args.get("start") else "END" 1305 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1306 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:
1316 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1317 desc = expression.args.get("desc") 1318 if desc is not None: 1319 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1320 options = self.expressions(expression, key="options", flat=True, sep=" ") 1321 options = f" {options}" if options else "" 1322 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1324 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1325 this = self.sql(expression, "this") 1326 this = f" {this}" if this else "" 1327 index_type = expression.args.get("index_type") 1328 index_type = f" USING {index_type}" if index_type else "" 1329 on_conflict = self.sql(expression, "on_conflict") 1330 on_conflict = f" {on_conflict}" if on_conflict else "" 1331 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1332 options = self.expressions(expression, key="options", flat=True, sep=" ") 1333 options = f" {options}" if options else "" 1334 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1336 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1337 input_ = expression.args.get("input_") 1338 output = expression.args.get("output") 1339 variadic = expression.args.get("variadic") 1340 1341 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1342 if variadic: 1343 return "VARIADIC" 1344 1345 if input_ and output: 1346 return f"IN{self.INOUT_SEPARATOR}OUT" 1347 if input_: 1348 return "IN" 1349 if output: 1350 return "OUT" 1351 1352 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1357 def create_sql(self, expression: exp.Create) -> str: 1358 kind = self.sql(expression, "kind") 1359 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1360 1361 properties = expression.args.get("properties") 1362 1363 if ( 1364 kind == "TRIGGER" 1365 and properties 1366 and properties.expressions 1367 and isinstance(properties.expressions[0], exp.TriggerProperties) 1368 and properties.expressions[0].args.get("constraint") 1369 ): 1370 kind = f"CONSTRAINT {kind}" 1371 1372 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1373 1374 this = self.createable_sql(expression, properties_locs) 1375 1376 properties_sql = "" 1377 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1378 exp.Properties.Location.POST_WITH 1379 ): 1380 props_ast = exp.Properties( 1381 expressions=[ 1382 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1383 *properties_locs[exp.Properties.Location.POST_WITH], 1384 ] 1385 ) 1386 props_ast.parent = expression 1387 properties_sql = self.sql(props_ast) 1388 1389 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1390 properties_sql = self.sep() + properties_sql 1391 elif not self.pretty: 1392 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1393 properties_sql = f" {properties_sql}" 1394 1395 begin = " BEGIN" if expression.args.get("begin") else "" 1396 1397 expression_sql = self.sql(expression, "expression") 1398 if expression_sql: 1399 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1400 1401 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1402 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1403 ): 1404 postalias_props_sql = "" 1405 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1406 postalias_props_sql = self.properties( 1407 exp.Properties( 1408 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1409 ), 1410 wrapped=False, 1411 ) 1412 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1413 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1414 1415 postindex_props_sql = "" 1416 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1417 postindex_props_sql = self.properties( 1418 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1419 wrapped=False, 1420 prefix=" ", 1421 ) 1422 1423 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1424 indexes = f" {indexes}" if indexes else "" 1425 index_sql = indexes + postindex_props_sql 1426 1427 replace = " OR REPLACE" if expression.args.get("replace") else "" 1428 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1429 unique = " UNIQUE" if expression.args.get("unique") else "" 1430 1431 clustered = expression.args.get("clustered") 1432 if clustered is None: 1433 clustered_sql = "" 1434 elif clustered: 1435 clustered_sql = " CLUSTERED COLUMNSTORE" 1436 else: 1437 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1438 1439 postcreate_props_sql = "" 1440 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1441 postcreate_props_sql = self.properties( 1442 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1443 sep=" ", 1444 prefix=" ", 1445 wrapped=False, 1446 ) 1447 1448 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1449 1450 postexpression_props_sql = "" 1451 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1452 postexpression_props_sql = self.properties( 1453 exp.Properties( 1454 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1455 ), 1456 sep=" ", 1457 prefix=" ", 1458 wrapped=False, 1459 ) 1460 1461 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1462 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1463 no_schema_binding = ( 1464 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1465 ) 1466 1467 clone = self.sql(expression, "clone") 1468 clone = f" {clone}" if clone else "" 1469 1470 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1471 properties_expression = f"{expression_sql}{properties_sql}" 1472 else: 1473 properties_expression = f"{properties_sql}{expression_sql}" 1474 1475 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1476 return self.prepend_ctes(expression, expression_sql)
1478 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1479 start = self.sql(expression, "start") 1480 start = f"START WITH {start}" if start else "" 1481 increment = self.sql(expression, "increment") 1482 increment = f" INCREMENT BY {increment}" if increment else "" 1483 minvalue = self.sql(expression, "minvalue") 1484 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1485 maxvalue = self.sql(expression, "maxvalue") 1486 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1487 owned = self.sql(expression, "owned") 1488 owned = f" OWNED BY {owned}" if owned else "" 1489 1490 cache = expression.args.get("cache") 1491 if cache is None: 1492 cache_str = "" 1493 elif cache is True: 1494 cache_str = " CACHE" 1495 else: 1496 cache_str = f" CACHE {cache}" 1497 1498 options = self.expressions(expression, key="options", flat=True, sep=" ") 1499 options = f" {options}" if options else "" 1500 1501 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1503 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1504 timing = expression.args.get("timing", "") 1505 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1506 timing_events = f"{timing} {events}".strip() if timing or events else "" 1507 1508 parts = [timing_events, "ON", self.sql(expression, "table")] 1509 1510 if referenced_table := expression.args.get("referenced_table"): 1511 parts.extend(["FROM", self.sql(referenced_table)]) 1512 1513 if deferrable := expression.args.get("deferrable"): 1514 parts.append(deferrable) 1515 1516 if initially := expression.args.get("initially"): 1517 parts.append(f"INITIALLY {initially}") 1518 1519 if referencing := expression.args.get("referencing"): 1520 parts.append(self.sql(referencing)) 1521 1522 if for_each := expression.args.get("for_each"): 1523 parts.append(f"FOR EACH {for_each}") 1524 1525 if when := expression.args.get("when"): 1526 parts.append(f"WHEN ({self.sql(when)})") 1527 1528 parts.append(self.sql(expression, "execute")) 1529 1530 return self.sep().join(parts)
1532 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1533 parts = [] 1534 1535 if old_alias := expression.args.get("old"): 1536 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1537 1538 if new_alias := expression.args.get("new"): 1539 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1540 1541 return f"REFERENCING {' '.join(parts)}"
1550 def clone_sql(self, expression: exp.Clone) -> str: 1551 this = self.sql(expression, "this") 1552 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1553 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1554 return f"{shallow}{keyword} {this}"
1556 def describe_sql(self, expression: exp.Describe) -> str: 1557 style = expression.args.get("style") 1558 style = f" {style}" if style else "" 1559 partition = self.sql(expression, "partition") 1560 partition = f" {partition}" if partition else "" 1561 format = self.sql(expression, "format") 1562 format = f" {format}" if format else "" 1563 as_json = " AS JSON" if expression.args.get("as_json") else "" 1564 1565 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1577 def with_sql(self, expression: exp.With) -> str: 1578 udfs = self.expressions(expression, key="udfs", flat=True) 1579 udfs = f"WITH {udfs}" if udfs else "" 1580 1581 sql = self.expressions(expression, flat=True) 1582 1583 recursive = ( 1584 "RECURSIVE " 1585 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1586 else "" 1587 ) 1588 search = self.sql(expression, "search") 1589 search = f" {search}" if search else "" 1590 cycle = self.sql(expression, "cycle") 1591 cycle = f" {cycle}" if cycle else "" 1592 1593 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1594 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1596 def cte_sql(self, expression: exp.CTE) -> str: 1597 alias = expression.args.get("alias") 1598 if alias: 1599 alias.add_comments(expression.pop_comments()) 1600 1601 alias_sql = self.sql(expression, "alias") 1602 1603 materialized = expression.args.get("materialized") 1604 if materialized is False: 1605 materialized = "NOT MATERIALIZED " 1606 elif materialized: 1607 materialized = "MATERIALIZED " 1608 1609 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1610 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1611 1612 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1614 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1615 alias = self.sql(expression, "this") 1616 columns = self.expressions(expression, key="columns", flat=True) 1617 columns = f"({columns})" if columns else "" 1618 1619 if ( 1620 columns 1621 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1622 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1623 ): 1624 columns = "" 1625 self.unsupported("Named columns are not supported in table alias.") 1626 1627 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1628 alias = self._next_name() 1629 1630 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1638 def hexstring_sql( 1639 self, expression: exp.HexString, binary_function_repr: str | None = None 1640 ) -> str: 1641 this = self.sql(expression, "this") 1642 is_integer_type = expression.args.get("is_integer") 1643 1644 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1645 not self.dialect.HEX_START and not binary_function_repr 1646 ): 1647 # Integer representation will be returned if: 1648 # - The read dialect treats the hex value as integer literal but not the write 1649 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1650 return f"{int(this, 16)}" 1651 1652 if not is_integer_type: 1653 # Read dialect treats the hex value as BINARY/BLOB 1654 if binary_function_repr: 1655 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1656 return self.func(binary_function_repr, exp.Literal.string(this)) 1657 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1658 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1659 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1660 1661 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1663 def bytestring_sql(self, expression: exp.ByteString) -> str: 1664 this = self.sql(expression, "this") 1665 is_bytes = bool(expression.args.get("is_bytes")) 1666 if is_bytes and self.BYTE_STRING_FUNCTION: 1667 # Hex digits can be quoted directly without escaping. 1668 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1669 1670 if self.dialect.BYTE_START: 1671 escaped_byte_string = self.escape_str( 1672 this, 1673 escape_backslash=bool( 1674 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1675 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1676 ), 1677 delimiter=self.dialect.BYTE_END, 1678 escaped_delimiter=self._escaped_byte_quote_end, 1679 is_byte_string=True, 1680 is_bytes=is_bytes, 1681 ) 1682 delimited_byte_string = ( 1683 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1684 ) 1685 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1686 return self.sql( 1687 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1688 ) 1689 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1690 return self.sql( 1691 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1692 ) 1693 1694 return delimited_byte_string 1695 1696 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1697 return self.sql(exp.Literal.string(this)) 1698 1699 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1700 return ""
1702 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1703 this = self.sql(expression, "this") 1704 escape = expression.args.get("escape") 1705 unicode_start = self.dialect.UNICODE_START 1706 1707 if unicode_start: 1708 escape_substitute = r"\\\1" 1709 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1710 else: 1711 escape_substitute = r"\\u\1" 1712 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1713 1714 if escape: 1715 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1716 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1717 else: 1718 escape_pattern = ESCAPED_UNICODE_RE 1719 escape_sql = "" 1720 1721 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1722 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1723 1724 if unicode_start: 1725 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1726 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1727 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1728 else: 1729 this = self.escape_str(this, escape_backslash=False) 1730 1731 return f"{left_quote}{this}{right_quote}{escape_sql}"
1733 def rawstring_sql(self, expression: exp.RawString) -> str: 1734 string = expression.this 1735 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1736 string = string.replace("\\", "\\\\") 1737 1738 string = self.escape_str(string, escape_backslash=False) 1739 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:
1747 def datatype_param_bound_limiter( 1748 self, 1749 expression: exp.DataType, 1750 type_value: exp.DType, 1751 defaults: tuple[int, ...], 1752 bounds: tuple[int | None, ...], 1753 ) -> exp.DataType: 1754 params = expression.expressions 1755 1756 if not params: 1757 if defaults: 1758 expression.set( 1759 "expressions", 1760 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1761 ) 1762 return expression 1763 1764 if not bounds: 1765 return expression 1766 1767 for i, param in enumerate(params): 1768 bound = bounds[i] if i < len(bounds) else None 1769 if bound is None: 1770 continue 1771 1772 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1773 value = ( 1774 param_value.to_py() 1775 if isinstance(param_value, exp.Literal) and param_value.is_number 1776 else None 1777 ) 1778 if isinstance(value, (int, Decimal)) and value > bound: 1779 self.unsupported( 1780 f"{type_value.value} parameter {param_value.name} exceeds " 1781 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1782 ) 1783 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1784 1785 return expression
1787 def datatype_sql(self, expression: exp.DataType) -> str: 1788 nested = "" 1789 values = "" 1790 1791 expr_nested = expression.args.get("nested") 1792 type_value = expression.this 1793 1794 if ( 1795 not expr_nested 1796 and isinstance(type_value, exp.DType) 1797 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1798 ): 1799 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1800 1801 interior = ( 1802 self.expressions( 1803 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1804 ) 1805 if expr_nested and self.pretty 1806 else self.expressions(expression, flat=True) 1807 ) 1808 1809 if type_value in self.UNSUPPORTED_TYPES: 1810 self.unsupported( 1811 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1812 ) 1813 1814 type_sql: t.Any = "" 1815 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1816 type_sql = self.sql(expression, "kind") 1817 elif type_value == exp.DType.CHARACTER_SET: 1818 char = f"CHAR({interior})" if interior else "CHAR" 1819 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1820 else: 1821 type_sql = ( 1822 self.TYPE_MAPPING.get(type_value, type_value.value) 1823 if isinstance(type_value, exp.DType) 1824 else type_value 1825 ) 1826 1827 if interior: 1828 if expr_nested: 1829 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1830 if expression.args.get("values") is not None: 1831 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1832 values = self.expressions(expression, key="values", flat=True) 1833 values = f"{delimiters[0]}{values}{delimiters[1]}" 1834 elif type_value == exp.DType.INTERVAL: 1835 nested = f" {interior}" 1836 else: 1837 nested = f"({interior})" 1838 1839 type_sql = f"{type_sql}{nested}{values}" 1840 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1841 exp.DType.TIMETZ, 1842 exp.DType.TIMESTAMPTZ, 1843 ): 1844 type_sql = f"{type_sql} WITH TIME ZONE" 1845 1846 collate = self.sql(expression, "collate") 1847 if collate: 1848 type_sql = f"{type_sql} COLLATE {collate}" 1849 1850 return type_sql
1852 def directory_sql(self, expression: exp.Directory) -> str: 1853 local = "LOCAL " if expression.args.get("local") else "" 1854 row_format = self.sql(expression, "row_format") 1855 row_format = f" {row_format}" if row_format else "" 1856 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1858 def delete_sql(self, expression: exp.Delete) -> str: 1859 hint = self.sql(expression, "hint") 1860 this = self.sql(expression, "this") 1861 this = f" FROM {this}" if this else "" 1862 using = self.expressions(expression, key="using") 1863 using = f" USING {using}" if using else "" 1864 cluster = self.sql(expression, "cluster") 1865 cluster = f" {cluster}" if cluster else "" 1866 where = self.sql(expression, "where") 1867 returning = self.sql(expression, "returning") 1868 order = self.sql(expression, "order") 1869 limit = self.sql(expression, "limit") 1870 tables = self.expressions(expression, key="tables") 1871 tables = f" {tables}" if tables else "" 1872 if self.RETURNING_END: 1873 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1874 else: 1875 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1876 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1878 def drop_sql(self, expression: exp.Drop) -> str: 1879 tables = self.expressions(expression, key="tables", flat=True) 1880 expressions = self.expressions(expression, flat=True) 1881 expressions = f" ({expressions})" if expressions else "" 1882 kind = expression.args["kind"] 1883 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1884 iceberg = ( 1885 " ICEBERG" 1886 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1887 else "" 1888 ) 1889 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1890 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1891 on_cluster = self.sql(expression, "cluster") 1892 on_cluster = f" {on_cluster}" if on_cluster else "" 1893 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1894 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1895 cascade = " CASCADE" if expression.args.get("cascade") else "" 1896 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1897 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1898 purge = " PURGE" if expression.args.get("purge") else "" 1899 sync = " SYNC" if expression.args.get("sync") else "" 1900 force = " FORCE" if expression.args.get("force") else "" 1901 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1903 def set_operation(self, expression: exp.SetOperation) -> str: 1904 op_type = type(expression) 1905 op_name = op_type.key.upper() 1906 1907 distinct = expression.args.get("distinct") 1908 if ( 1909 distinct is False 1910 and op_type in (exp.Except, exp.Intersect) 1911 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1912 ): 1913 self.unsupported(f"{op_name} ALL is not supported") 1914 1915 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1916 1917 if distinct is None: 1918 distinct = default_distinct 1919 if distinct is None: 1920 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1921 1922 if distinct is default_distinct: 1923 distinct_or_all = "" 1924 else: 1925 distinct_or_all = " DISTINCT" if distinct else " ALL" 1926 1927 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1928 side_kind = f"{side_kind} " if side_kind else "" 1929 1930 by_name = " BY NAME" if expression.args.get("by_name") else "" 1931 on = self.expressions(expression, key="on", flat=True) 1932 on = f" ON ({on})" if on else "" 1933 1934 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1936 def set_operations(self, expression: exp.SetOperation) -> str: 1937 if not self.SET_OP_MODIFIERS: 1938 limit = expression.args.get("limit") 1939 order = expression.args.get("order") 1940 offset = expression.args.get("offset") 1941 1942 if limit or order or offset: 1943 select = self._move_ctes_to_top_level( 1944 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1945 ) 1946 1947 for arg in ("limit", "order", "offset"): 1948 if value := expression.args.get(arg): 1949 select.set(arg, value.pop()) 1950 return self.sql(select) 1951 1952 sqls: list[str] = [] 1953 stack: list[str | exp.Expr] = [expression] 1954 1955 while stack: 1956 node = stack.pop() 1957 1958 if isinstance(node, exp.SetOperation): 1959 stack.append(node.expression) 1960 stack.append( 1961 self.maybe_comment( 1962 self.set_operation(node), comments=node.comments, separated=True 1963 ) 1964 ) 1965 stack.append(node.this) 1966 else: 1967 if ( 1968 not self.SET_OP_LIMITS 1969 and isinstance(node, exp.Select) 1970 and node.args.get("limit") 1971 ): 1972 node = node.subquery(copy=False) 1973 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1974 node = exp.select("*").from_(node, copy=False) 1975 sqls.append(self.sql(node)) 1976 1977 this = self.sep().join(sqls) 1978 this = self.query_modifiers(expression, this) 1979 return self.prepend_ctes(expression, this)
1981 def fetch_sql(self, expression: exp.Fetch) -> str: 1982 direction = expression.args.get("direction") 1983 direction = f" {direction}" if direction else "" 1984 count = self.sql(expression, "count") 1985 count = f" {count}" if count else "" 1986 limit_options = self.sql(expression, "limit_options") 1987 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1988 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1990 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1991 percent = " PERCENT" if expression.args.get("percent") else "" 1992 rows = " ROWS" if expression.args.get("rows") else "" 1993 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1994 if not with_ties and rows: 1995 with_ties = " ONLY" 1996 return f"{percent}{rows}{with_ties}"
2010 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2011 using = self.sql(expression, "using") 2012 using = f" USING {using}" if using else "" 2013 columns = self.expressions(expression, key="columns", flat=True) 2014 columns = f"({columns})" if columns else "" 2015 partition_by = self.expressions(expression, key="partition_by", flat=True) 2016 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2017 where = self.sql(expression, "where") 2018 include = self.expressions(expression, key="include", flat=True) 2019 if include: 2020 include = f" INCLUDE ({include})" 2021 with_storage = self.expressions(expression, key="with_storage", flat=True) 2022 with_storage = f" WITH ({with_storage})" if with_storage else "" 2023 tablespace = self.sql(expression, "tablespace") 2024 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 2025 on = self.sql(expression, "on") 2026 on = f" ON {on}" if on else "" 2027 2028 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2030 def index_sql(self, expression: exp.Index) -> str: 2031 unique = "UNIQUE " if expression.args.get("unique") else "" 2032 primary = "PRIMARY " if expression.args.get("primary") else "" 2033 amp = "AMP " if expression.args.get("amp") else "" 2034 name = self.sql(expression, "this") 2035 name = f"{name} " if name else "" 2036 table = self.sql(expression, "table") 2037 table = f"{self.INDEX_ON} {table}" if table else "" 2038 2039 index = "INDEX " if not table else "" 2040 2041 params = self.sql(expression, "params") 2042 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2044 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2045 this = expression.this 2046 if this and this.is_string: 2047 resolved = maybe_parse(this.name).sql(self.dialect) 2048 if "expressions" in expression.args: 2049 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2050 # We can't safely emit the call to other dialects since name/arg semantics may differ 2051 self.unsupported( 2052 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2053 ) 2054 return resolved 2055 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2056 return self.func("IDENTIFIER", this)
2058 def identifier_sql(self, expression: exp.Identifier) -> str: 2059 text = expression.name 2060 lower = text.lower() 2061 quoted = expression.quoted 2062 text = lower if self.normalize and not quoted else text 2063 if self._identifier_escaped_sequences: 2064 for char, escaped in self._identifier_escaped_sequences.items(): 2065 text = text.replace(char, escaped) 2066 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2067 if ( 2068 quoted 2069 or self.dialect.can_quote(expression, self.identify) 2070 or lower in self.RESERVED_KEYWORDS 2071 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2072 ): 2073 text = ( 2074 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2075 ) 2076 return text
2091 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2092 input_format = self.sql(expression, "input_format") 2093 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2094 output_format = self.sql(expression, "output_format") 2095 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2096 return self.sep().join((input_format, output_format))
2106 def properties_sql(self, expression: exp.Properties) -> str: 2107 root_properties = [] 2108 with_properties = [] 2109 2110 for p in expression.expressions: 2111 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2112 if p_loc == exp.Properties.Location.POST_WITH: 2113 with_properties.append(p) 2114 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2115 root_properties.append(p) 2116 2117 root_props_ast = exp.Properties(expressions=root_properties) 2118 root_props_ast.parent = expression.parent 2119 2120 with_props_ast = exp.Properties(expressions=with_properties) 2121 with_props_ast.parent = expression.parent 2122 2123 root_props = self.root_properties(root_props_ast) 2124 with_props = self.with_properties(with_props_ast) 2125 2126 if root_props and with_props and not self.pretty: 2127 with_props = " " + with_props 2128 2129 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2136 def properties( 2137 self, 2138 properties: exp.Properties, 2139 prefix: str = "", 2140 sep: str = ", ", 2141 suffix: str = "", 2142 wrapped: bool = True, 2143 ) -> str: 2144 if properties.expressions: 2145 expressions = self.expressions(properties, sep=sep, indent=False) 2146 if expressions: 2147 expressions = self.wrap(expressions) if wrapped else expressions 2148 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2149 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2154 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2155 properties_locs = defaultdict(list) 2156 for p in properties.expressions: 2157 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2158 if p_loc != exp.Properties.Location.UNSUPPORTED: 2159 properties_locs[p_loc].append(p) 2160 else: 2161 self.unsupported(f"Unsupported property {p.key}") 2162 2163 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2170 def property_sql(self, expression: exp.Property) -> str: 2171 property_cls = expression.__class__ 2172 if property_cls == exp.Property: 2173 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2174 2175 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2176 if not property_name: 2177 self.unsupported(f"Unsupported property {expression.key}") 2178 2179 return f"{property_name}={self.sql(expression, 'this')}"
2184 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2185 if self.SUPPORTS_CREATE_TABLE_LIKE: 2186 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2187 options = f" {options}" if options else "" 2188 2189 like = f"LIKE {self.sql(expression, 'this')}{options}" 2190 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2191 like = f"({like})" 2192 2193 return like 2194 2195 if expression.expressions: 2196 self.unsupported("Transpilation of LIKE property options is unsupported") 2197 2198 select = exp.select("*").from_(expression.this).limit(0) 2199 return f"AS {self.sql(select)}"
2206 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2207 no = "NO " if expression.args.get("no") else "" 2208 local = expression.args.get("local") 2209 local = f"{local} " if local else "" 2210 dual = "DUAL " if expression.args.get("dual") else "" 2211 before = "BEFORE " if expression.args.get("before") else "" 2212 after = "AFTER " if expression.args.get("after") else "" 2213 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:
2229 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2230 if expression.args.get("no"): 2231 return "NO MERGEBLOCKRATIO" 2232 if expression.args.get("default"): 2233 return "DEFAULT MERGEBLOCKRATIO" 2234 2235 percent = " PERCENT" if expression.args.get("percent") else "" 2236 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2243 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2244 default = expression.args.get("default") 2245 minimum = expression.args.get("minimum") 2246 maximum = expression.args.get("maximum") 2247 if default or minimum or maximum: 2248 if default: 2249 prop = "DEFAULT" 2250 elif minimum: 2251 prop = "MINIMUM" 2252 else: 2253 prop = "MAXIMUM" 2254 return f"{prop} DATABLOCKSIZE" 2255 units = expression.args.get("units") 2256 units = f" {units}" if units else "" 2257 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2259 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2260 autotemp = expression.args.get("autotemp") 2261 always = expression.args.get("always") 2262 default = expression.args.get("default") 2263 manual = expression.args.get("manual") 2264 never = expression.args.get("never") 2265 2266 if autotemp is not None: 2267 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2268 elif always: 2269 prop = "ALWAYS" 2270 elif default: 2271 prop = "DEFAULT" 2272 elif manual: 2273 prop = "MANUAL" 2274 elif never: 2275 prop = "NEVER" 2276 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2278 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2279 no = expression.args.get("no") 2280 no = " NO" if no else "" 2281 concurrent = expression.args.get("concurrent") 2282 concurrent = " CONCURRENT" if concurrent else "" 2283 target = self.sql(expression, "target") 2284 target = f" {target}" if target else "" 2285 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2287 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2288 if isinstance(expression.this, list): 2289 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2290 if expression.this: 2291 modulus = self.sql(expression, "this") 2292 remainder = self.sql(expression, "expression") 2293 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2294 2295 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2296 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2297 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2299 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2300 this = self.sql(expression, "this") 2301 2302 for_values_or_default = expression.expression 2303 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2304 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2305 else: 2306 for_values_or_default = " DEFAULT" 2307 2308 return f"PARTITION OF {this}{for_values_or_default}"
2310 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2311 kind = expression.args.get("kind") 2312 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2313 for_or_in = expression.args.get("for_or_in") 2314 for_or_in = f" {for_or_in}" if for_or_in else "" 2315 lock_type = expression.args.get("lock_type") 2316 override = " OVERRIDE" if expression.args.get("override") else "" 2317 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2319 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2320 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2321 statistics = expression.args.get("statistics") 2322 statistics_sql = "" 2323 if statistics is not None: 2324 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2325 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2327 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2328 this = self.sql(expression, "this") 2329 this = f"HISTORY_TABLE={this}" if this else "" 2330 data_consistency: str | None = self.sql(expression, "data_consistency") 2331 data_consistency = ( 2332 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2333 ) 2334 retention_period: str | None = self.sql(expression, "retention_period") 2335 retention_period = ( 2336 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2337 ) 2338 2339 if this: 2340 on_sql = self.func("ON", this, data_consistency, retention_period) 2341 else: 2342 on_sql = "ON" if expression.args.get("on") else "OFF" 2343 2344 sql = f"SYSTEM_VERSIONING={on_sql}" 2345 2346 return f"WITH({sql})" if expression.args.get("with_") else sql
2348 def insert_sql(self, expression: exp.Insert) -> str: 2349 hint = self.sql(expression, "hint") 2350 overwrite = expression.args.get("overwrite") 2351 2352 if isinstance(expression.this, exp.Directory): 2353 this = " OVERWRITE" if overwrite else " INTO" 2354 else: 2355 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2356 2357 stored = self.sql(expression, "stored") 2358 stored = f" {stored}" if stored else "" 2359 alternative = expression.args.get("alternative") 2360 alternative = f" OR {alternative}" if alternative else "" 2361 ignore = " IGNORE" if expression.args.get("ignore") else "" 2362 is_function = expression.args.get("is_function") 2363 if is_function: 2364 this = f"{this} FUNCTION" 2365 this = f"{this} {self.sql(expression, 'this')}" 2366 2367 exists = " IF EXISTS" if expression.args.get("exists") else "" 2368 where = self.sql(expression, "where") 2369 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2370 using = self.expressions(expression, key="using", flat=True) 2371 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2372 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2373 on_conflict = self.sql(expression, "conflict") 2374 on_conflict = f" {on_conflict}" if on_conflict else "" 2375 by_name = " BY NAME" if expression.args.get("by_name") else "" 2376 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2377 returning = self.sql(expression, "returning") 2378 2379 if self.RETURNING_END: 2380 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2381 else: 2382 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2383 2384 partition_by = self.sql(expression, "partition") 2385 partition_by = f" {partition_by}" if partition_by else "" 2386 settings = self.sql(expression, "settings") 2387 settings = f" {settings}" if settings else "" 2388 2389 source = self.sql(expression, "source") 2390 source = f"TABLE {source}" if source else "" 2391 2392 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2393 return self.prepend_ctes(expression, sql)
2411 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2412 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2413 2414 constraint = self.sql(expression, "constraint") 2415 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2416 2417 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2418 if conflict_keys: 2419 conflict_keys = f"({conflict_keys})" 2420 2421 index_predicate = self.sql(expression, "index_predicate") 2422 conflict_keys = f"{conflict_keys}{index_predicate} " 2423 2424 action = self.sql(expression, "action") 2425 2426 expressions = self.expressions(expression, flat=True) 2427 if expressions: 2428 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2429 expressions = f" {set_keyword}{expressions}" 2430 2431 where = self.sql(expression, "where") 2432 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2437 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2438 fields = self.sql(expression, "fields") 2439 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2440 escaped = self.sql(expression, "escaped") 2441 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2442 items = self.sql(expression, "collection_items") 2443 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2444 keys = self.sql(expression, "map_keys") 2445 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2446 lines = self.sql(expression, "lines") 2447 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2448 null = self.sql(expression, "null") 2449 null = f" NULL DEFINED AS {null}" if null else "" 2450 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2478 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2479 table = self.table_parts(expression) 2480 only = "ONLY " if expression.args.get("only") else "" 2481 partition = self.sql(expression, "partition") 2482 partition = f" {partition}" if partition else "" 2483 version = self.sql(expression, "version") 2484 version = f" {version}" if version else "" 2485 alias = self.sql(expression, "alias") 2486 alias = f"{sep}{alias}" if alias else "" 2487 2488 sample = self.sql(expression, "sample") 2489 post_alias = "" 2490 pre_alias = "" 2491 2492 if self.dialect.ALIAS_POST_TABLESAMPLE: 2493 pre_alias = sample 2494 else: 2495 post_alias = sample 2496 2497 if self.dialect.ALIAS_POST_VERSION: 2498 pre_alias = f"{pre_alias}{version}" 2499 else: 2500 post_alias = f"{post_alias}{version}" 2501 2502 hints = self.expressions(expression, key="hints", sep=" ") 2503 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2504 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2505 joins = self.indent( 2506 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2507 ) 2508 laterals = self.expressions(expression, key="laterals", sep="") 2509 2510 file_format = self.sql(expression, "format") 2511 pattern = self.sql(expression, "pattern") 2512 if file_format: 2513 pattern = f", PATTERN => {pattern}" if pattern else "" 2514 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2515 elif pattern: 2516 file_format = f" (PATTERN => {pattern})" 2517 2518 ordinality = expression.args.get("ordinality") or "" 2519 if ordinality: 2520 ordinality = f" WITH ORDINALITY{alias}" 2521 alias = "" 2522 2523 when = self.sql(expression, "when") 2524 if when: 2525 if self.HISTORICAL_DATA_POST_ALIAS: 2526 alias = f"{alias} {when}" 2527 else: 2528 table = f"{table} {when}" 2529 2530 changes = self.sql(expression, "changes") 2531 changes = f" {changes}" if changes else "" 2532 2533 rows_from = self.expressions(expression, key="rows_from") 2534 if rows_from: 2535 table = f"ROWS FROM {self.wrap(rows_from)}" 2536 2537 indexed = expression.args.get("indexed") 2538 if indexed is not None: 2539 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2540 else: 2541 indexed = "" 2542 2543 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2545 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2546 table = self.func("TABLE", expression.this) 2547 alias = self.sql(expression, "alias") 2548 alias = f" AS {alias}" if alias else "" 2549 sample = self.sql(expression, "sample") 2550 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2551 joins = self.indent( 2552 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2553 ) 2554 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2556 def tablesample_sql( 2557 self, 2558 expression: exp.TableSample, 2559 tablesample_keyword: str | None = None, 2560 ) -> str: 2561 method = self.sql(expression, "method") 2562 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2563 numerator = self.sql(expression, "bucket_numerator") 2564 denominator = self.sql(expression, "bucket_denominator") 2565 field = self.sql(expression, "bucket_field") 2566 field = f" ON {field}" if field else "" 2567 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2568 seed = self.sql(expression, "seed") 2569 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2570 2571 size = self.sql(expression, "size") 2572 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2573 size = f"{size} ROWS" 2574 2575 percent = self.sql(expression, "percent") 2576 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2577 percent = f"{percent} PERCENT" 2578 2579 expr = f"{bucket}{percent}{size}" 2580 if self.TABLESAMPLE_REQUIRES_PARENS: 2581 expr = f"({expr})" 2582 2583 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2660 def pivot_sql(self, expression: exp.Pivot) -> str: 2661 expressions = self.expressions(expression, flat=True) 2662 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2663 2664 group = self.sql(expression, "group") 2665 2666 if expression.this: 2667 this = self.sql(expression, "this") 2668 if not expressions: 2669 sql = f"UNPIVOT {this}" 2670 else: 2671 on = f"{self.seg('ON')} {expressions}" 2672 into = self.sql(expression, "into") 2673 into = f"{self.seg('INTO')} {into}" if into else "" 2674 using = self.expressions(expression, key="using", flat=True) 2675 using = f"{self.seg('USING')} {using}" if using else "" 2676 sql = f"{direction} {this}{on}{into}{using}{group}" 2677 return self.prepend_ctes(expression, sql) 2678 2679 if not expression.unpivot: 2680 # Wrap IN-list values with explicit aliases where the target dialect would differ 2681 new_field_exprs = self._pivot_in_value_aliases(expression) 2682 if new_field_exprs is not None: 2683 expression.fields[0].set("expressions", new_field_exprs) 2684 2685 alias = self.sql(expression, "alias") 2686 if alias: 2687 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2688 2689 fields = self.expressions( 2690 expression, 2691 "fields", 2692 sep=" ", 2693 dynamic=True, 2694 new_line=True, 2695 skip_first=True, 2696 skip_last=True, 2697 ) 2698 2699 include_nulls = expression.args.get("include_nulls") 2700 if include_nulls is not None: 2701 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2702 else: 2703 nulls = "" 2704 2705 default_on_null = self.sql(expression, "default_on_null") 2706 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2707 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2708 return self.prepend_ctes(expression, sql)
2751 def update_sql(self, expression: exp.Update) -> str: 2752 hint = self.sql(expression, "hint") 2753 this = self.sql(expression, "this") 2754 join_sql, from_sql = self._update_from_joins_sql(expression) 2755 set_sql = self.expressions(expression, flat=True) 2756 where_sql = self.sql(expression, "where") 2757 returning = self.sql(expression, "returning") 2758 order = self.sql(expression, "order") 2759 limit = self.sql(expression, "limit") 2760 if self.RETURNING_END: 2761 expression_sql = f"{from_sql}{where_sql}{returning}" 2762 else: 2763 expression_sql = f"{returning}{from_sql}{where_sql}" 2764 options = self.expressions(expression, key="options") 2765 options = f" OPTION({options})" if options else "" 2766 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2767 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2769 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2770 values_as_table = values_as_table and self.VALUES_AS_TABLE 2771 2772 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2773 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2774 args = self.expressions(expression) 2775 alias = self.sql(expression, "alias") 2776 values = f"VALUES{self.seg('')}{args}" 2777 values = ( 2778 f"({values})" 2779 if self.WRAP_DERIVED_VALUES 2780 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2781 else values 2782 ) 2783 values = self.query_modifiers(expression, values) 2784 return f"{values} AS {alias}" if alias else values 2785 2786 # Converts `VALUES...` expression into a series of select unions. 2787 alias_node = expression.args.get("alias") 2788 column_names = alias_node and alias_node.columns 2789 2790 selects: list[exp.Query] = [] 2791 2792 for i, tup in enumerate(expression.expressions): 2793 row = tup.expressions 2794 2795 if i == 0 and column_names: 2796 row = [ 2797 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2798 ] 2799 2800 selects.append(exp.Select(expressions=row)) 2801 2802 if self.pretty: 2803 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2804 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2805 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2806 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2807 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2808 2809 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2810 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2811 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2816 @unsupported_args("expressions") 2817 def into_sql(self, expression: exp.Into) -> str: 2818 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2819 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2820 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2833 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2834 this = self.sql(expression, "this") 2835 2836 columns = self.expressions(expression, flat=True) 2837 2838 from_sql = self.sql(expression, "from_index") 2839 from_sql = f" FROM {from_sql}" if from_sql else "" 2840 2841 properties = expression.args.get("properties") 2842 properties_sql = ( 2843 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2844 ) 2845 2846 return f"{this}({columns}){from_sql}{properties_sql}"
2855 def group_sql(self, expression: exp.Group) -> str: 2856 group_by_all = expression.args.get("all") 2857 if group_by_all is True: 2858 modifier = " ALL" 2859 elif group_by_all is False: 2860 modifier = " DISTINCT" 2861 else: 2862 modifier = "" 2863 2864 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2865 2866 grouping_sets = self.expressions(expression, key="grouping_sets") 2867 cube = self.expressions(expression, key="cube") 2868 rollup = self.expressions(expression, key="rollup") 2869 2870 groupings = csv( 2871 self.seg(grouping_sets) if grouping_sets else "", 2872 self.seg(cube) if cube else "", 2873 self.seg(rollup) if rollup else "", 2874 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2875 sep=self.GROUPINGS_SEP, 2876 ) 2877 2878 if ( 2879 expression.expressions 2880 and groupings 2881 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2882 ): 2883 add_separator = True 2884 2885 if grouping_sets: 2886 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2887 add_separator = False 2888 else: 2889 self.unsupported( 2890 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2891 ) 2892 2893 if add_separator: 2894 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2895 2896 return f"{group_by}{groupings}"
2902 def connect_sql(self, expression: exp.Connect) -> str: 2903 start = self.sql(expression, "start") 2904 start = self.seg(f"START WITH {start}") if start else "" 2905 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2906 connect = self.sql(expression, "connect") 2907 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2908 return start + connect
2913 def join_sql(self, expression: exp.Join) -> str: 2914 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2915 side = None 2916 else: 2917 side = expression.side 2918 2919 op_sql = " ".join( 2920 op 2921 for op in ( 2922 expression.method, 2923 "GLOBAL" if expression.args.get("global_") else None, 2924 side, 2925 expression.kind, 2926 expression.hint if self.JOIN_HINTS else None, 2927 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2928 ) 2929 if op 2930 ) 2931 match_cond = self.sql(expression, "match_condition") 2932 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2933 on_sql = self.sql(expression, "on") 2934 using = expression.args.get("using") 2935 2936 if not on_sql and using: 2937 on_sql = csv(*(self.sql(column) for column in using)) 2938 2939 this = expression.this 2940 this_sql = self.sql(this) 2941 2942 exprs = self.expressions(expression) 2943 if exprs: 2944 this_sql = f"{this_sql},{self.seg(exprs)}" 2945 2946 if on_sql: 2947 on_sql = self.indent(on_sql, skip_first=True) 2948 space = self.seg(" " * self.pad) if self.pretty else " " 2949 if using: 2950 on_sql = f"{space}USING ({on_sql})" 2951 else: 2952 on_sql = f"{space}ON {on_sql}" 2953 elif not op_sql: 2954 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2955 return f" {this_sql}" 2956 2957 return f", {this_sql}" 2958 2959 if op_sql != "STRAIGHT_JOIN": 2960 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2961 2962 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2963 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def
lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2970 def lateral_op(self, expression: exp.Lateral) -> str: 2971 cross_apply = expression.args.get("cross_apply") 2972 2973 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2974 if cross_apply is True: 2975 op = "INNER JOIN " 2976 elif cross_apply is False: 2977 op = "LEFT JOIN " 2978 else: 2979 op = "" 2980 2981 return f"{op}LATERAL"
2983 def lateral_sql(self, expression: exp.Lateral) -> str: 2984 this = self.sql(expression, "this") 2985 2986 if expression.args.get("view"): 2987 alias = expression.args["alias"] 2988 columns = self.expressions(alias, key="columns", flat=True) 2989 table = f" {alias.name}" if alias.name else "" 2990 columns = f" AS {columns}" if columns else "" 2991 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2992 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2993 2994 table_alias = expression.args.get("alias") 2995 offset = expression.this.args.get("offset") 2996 2997 if ( 2998 self.UNNEST_WITH_ORDINALITY 2999 and table_alias 3000 and isinstance(expression.this, exp.Unnest) 3001 and isinstance(offset, exp.Identifier) 3002 ): 3003 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3004 table_alias = table_alias.copy() 3005 table_alias.append("columns", offset.copy()) 3006 3007 alias = self.sql(table_alias) 3008 alias = f" AS {alias}" if alias else "" 3009 3010 ordinality = expression.args.get("ordinality") or "" 3011 if ordinality: 3012 ordinality = f" WITH ORDINALITY{alias}" 3013 alias = "" 3014 3015 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3017 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3018 this = self.sql(expression, "this") 3019 3020 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3021 return this 3022 3023 args = [ 3024 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3025 for e in (expression.args.get(k) for k in ("offset", "expression")) 3026 if e 3027 ] 3028 3029 args_sql = ", ".join(self.sql(e) for e in args) 3030 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3031 expressions = self.expressions(expression, flat=True) 3032 limit_options = self.sql(expression, "limit_options") 3033 expressions = f" BY {expressions}" if expressions else "" 3034 3035 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3037 def offset_sql(self, expression: exp.Offset) -> str: 3038 this = self.sql(expression, "this") 3039 value = expression.expression 3040 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3041 expressions = self.expressions(expression, flat=True) 3042 expressions = f" BY {expressions}" if expressions else "" 3043 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3045 def setitem_sql(self, expression: exp.SetItem) -> str: 3046 kind = self.sql(expression, "kind") 3047 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3048 kind = "" 3049 else: 3050 kind = f"{kind} " if kind else "" 3051 this = self.sql(expression, "this") 3052 expressions = self.expressions(expression) 3053 collate = self.sql(expression, "collate") 3054 collate = f" COLLATE {collate}" if collate else "" 3055 global_ = "GLOBAL " if expression.args.get("global_") else "" 3056 return f"{global_}{kind}{this}{expressions}{collate}"
3063 def queryband_sql(self, expression: exp.QueryBand) -> str: 3064 this = self.sql(expression, "this") 3065 update = " UPDATE" if expression.args.get("update") else "" 3066 scope = self.sql(expression, "scope") 3067 scope = f" FOR {scope}" if scope else "" 3068 3069 return f"QUERY_BAND = {this}{update}{scope}"
3074 def lock_sql(self, expression: exp.Lock) -> str: 3075 if not self.LOCKING_READS_SUPPORTED: 3076 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3077 return "" 3078 3079 update = expression.args["update"] 3080 key = expression.args.get("key") 3081 if update: 3082 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3083 else: 3084 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3085 expressions = self.expressions(expression, flat=True) 3086 expressions = f" OF {expressions}" if expressions else "" 3087 wait = expression.args.get("wait") 3088 3089 if wait is not None: 3090 if isinstance(wait, exp.Literal): 3091 wait = f" WAIT {self.sql(wait)}" 3092 else: 3093 wait = " NOWAIT" if wait else " SKIP LOCKED" 3094 3095 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:
3103 def escape_str( 3104 self, 3105 text: str, 3106 escape_backslash: bool = True, 3107 delimiter: str | None = None, 3108 escaped_delimiter: str | None = None, 3109 is_byte_string: bool = False, 3110 is_bytes: bool = False, 3111 ) -> str: 3112 if is_byte_string: 3113 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3114 else: 3115 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3116 3117 if supports_escape_sequences: 3118 text = "".join( 3119 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3120 for ch in text 3121 ) 3122 3123 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3124 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3125 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3126 3127 delimiter = delimiter or self.dialect.QUOTE_END 3128 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3129 3130 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3132 def loaddata_sql(self, expression: exp.LoadData) -> str: 3133 is_overwrite = expression.args.get("overwrite") 3134 overwrite = " OVERWRITE" if is_overwrite else "" 3135 this = self.sql(expression, "this") 3136 3137 files = expression.args.get("files") 3138 if files: 3139 files_sql = self.expressions(files, flat=True) 3140 files_sql = f"FILES{self.wrap(files_sql)}" 3141 if is_overwrite: 3142 this = f" {this}" 3143 elif expression.args.get("temp"): 3144 this = f" INTO TEMP TABLE {this}" 3145 else: 3146 this = f" INTO TABLE {this}" 3147 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3148 3149 local = " LOCAL" if expression.args.get("local") else "" 3150 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3151 this = f" INTO TABLE {this}" 3152 partition = self.sql(expression, "partition") 3153 partition = f" {partition}" if partition else "" 3154 input_format = self.sql(expression, "input_format") 3155 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3156 serde = self.sql(expression, "serde") 3157 serde = f" SERDE {serde}" if serde else "" 3158 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3172 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3173 this = self.sql(expression, "this") 3174 this = f"{this} " if this else this 3175 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3176 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3178 def withfill_sql(self, expression: exp.WithFill) -> str: 3179 from_sql = self.sql(expression, "from_") 3180 from_sql = f" FROM {from_sql}" if from_sql else "" 3181 to_sql = self.sql(expression, "to") 3182 to_sql = f" TO {to_sql}" if to_sql else "" 3183 step_sql = self.sql(expression, "step") 3184 step_sql = f" STEP {step_sql}" if step_sql else "" 3185 interpolated_values = [ 3186 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3187 if isinstance(e, exp.Alias) 3188 else self.sql(e, "this") 3189 for e in expression.args.get("interpolate") or [] 3190 ] 3191 interpolate = ( 3192 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3193 ) 3194 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3248 def ordered_sql(self, expression: exp.Ordered) -> str: 3249 desc = expression.args.get("desc") 3250 asc = not desc 3251 3252 nulls_first = expression.args.get("nulls_first") 3253 nulls_last = not nulls_first 3254 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3255 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3256 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3257 3258 this = self.sql(expression, "this") 3259 3260 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3261 nulls_sort_change = "" 3262 if nulls_first and ( 3263 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3264 ): 3265 nulls_sort_change = " NULLS FIRST" 3266 elif ( 3267 nulls_last 3268 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3269 and not nulls_are_last 3270 ): 3271 nulls_sort_change = " NULLS LAST" 3272 3273 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3274 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3275 window = expression.find_ancestor(exp.Window, exp.Select) 3276 3277 if isinstance(window, exp.Window): 3278 window_this = window.this 3279 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3280 window_this = window_this.this 3281 spec = window.args.get("spec") 3282 else: 3283 window_this = None 3284 spec = None 3285 3286 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3287 # without a spec or with a ROWS spec, but not with RANGE 3288 if not ( 3289 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3290 and (not spec or spec.text("kind").upper() == "ROWS") 3291 ): 3292 if window_this and spec: 3293 self.unsupported( 3294 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3295 ) 3296 nulls_sort_change = "" 3297 elif self.NULL_ORDERING_SUPPORTED is False and ( 3298 (asc and nulls_sort_change == " NULLS LAST") 3299 or (desc and nulls_sort_change == " NULLS FIRST") 3300 ): 3301 # BigQuery does not allow these ordering/nulls combinations when used under 3302 # an aggregation func or under a window containing one 3303 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3304 3305 if isinstance(ancestor, exp.Window): 3306 ancestor = ancestor.this 3307 if isinstance(ancestor, exp.AggFunc): 3308 self.unsupported( 3309 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3310 ) 3311 nulls_sort_change = "" 3312 elif self.NULL_ORDERING_SUPPORTED is None: 3313 if expression.this.is_int: 3314 self.unsupported( 3315 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3316 ) 3317 elif not isinstance(expression.this, exp.Rand): 3318 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3319 target = self.sql(resolved) if resolved is not None else this 3320 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3321 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3322 nulls_sort_change = "" 3323 3324 with_fill = self.sql(expression, "with_fill") 3325 with_fill = f" {with_fill}" if with_fill else "" 3326 3327 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3337 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3338 partition = self.partition_by_sql(expression) 3339 order = self.sql(expression, "order") 3340 measures = self.expressions(expression, key="measures") 3341 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3342 rows = self.sql(expression, "rows") 3343 rows = self.seg(rows) if rows else "" 3344 after = self.sql(expression, "after") 3345 after = self.seg(after) if after else "" 3346 pattern = self.sql(expression, "pattern") 3347 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3348 definition_sqls = [ 3349 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3350 for definition in expression.args.get("define", []) 3351 ] 3352 definitions = self.expressions(sqls=definition_sqls) 3353 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3354 body = "".join( 3355 ( 3356 partition, 3357 order, 3358 measures, 3359 rows, 3360 after, 3361 pattern, 3362 define, 3363 ) 3364 ) 3365 alias = self.sql(expression, "alias") 3366 alias = f" {alias}" if alias else "" 3367 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3369 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3370 limit = expression.args.get("limit") 3371 3372 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3373 count = limit.args.get("count") 3374 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3375 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3376 limit = exp.Limit( 3377 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3378 ) 3379 elif ( 3380 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3381 ): 3382 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3383 3384 return csv( 3385 *sqls, 3386 *[self.sql(join) for join in expression.args.get("joins") or []], 3387 self.sql(expression, "match"), 3388 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3389 self.sql(expression, "prewhere"), 3390 self.sql(expression, "where"), 3391 self.sql(expression, "connect"), 3392 self.sql(expression, "group"), 3393 self.sql(expression, "having"), 3394 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3395 self.sql(expression, "order"), 3396 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3397 *self.after_limit_modifiers(expression), 3398 self.sql(expression, "for_"), 3399 self.options_modifier(expression), 3400 sep="", 3401 )
3407 def forclause_sql(self, expression: exp.ForClause) -> str: 3408 kind = expression.args["kind"] 3409 if kind == "BROWSE": 3410 return f"{self.sep()}FOR BROWSE" 3411 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3412 # the target dialect doesn't support QueryOption, so we drop the clause. 3413 options = self.expressions(expression, key="expressions") 3414 if not options: 3415 return "" 3416 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]:
3435 def select_sql(self, expression: exp.Select) -> str: 3436 into = expression.args.get("into") 3437 if not self.SUPPORTS_SELECT_INTO and into: 3438 into.pop() 3439 3440 hint = self.sql(expression, "hint") 3441 distinct = self.sql(expression, "distinct") 3442 distinct = f" {distinct}" if distinct else "" 3443 kind = self.sql(expression, "kind") 3444 3445 limit = expression.args.get("limit") 3446 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3447 top = self.limit_sql(limit, top=True) 3448 limit.pop() 3449 else: 3450 top = "" 3451 3452 expressions = self.expressions(expression) 3453 3454 if kind: 3455 if kind in self.SELECT_KINDS: 3456 kind = f" AS {kind}" 3457 else: 3458 if kind == "STRUCT": 3459 expressions = self.expressions( 3460 sqls=[ 3461 self.sql( 3462 exp.Struct( 3463 expressions=[ 3464 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3465 if isinstance(e, exp.Alias) 3466 else e 3467 for e in expression.expressions 3468 ] 3469 ) 3470 ) 3471 ] 3472 ) 3473 kind = "" 3474 3475 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3476 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3477 3478 exclude = expression.args.get("exclude") 3479 3480 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3481 exclude_sql = self.expressions(sqls=exclude, flat=True) 3482 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3483 3484 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3485 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3486 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3487 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3488 sql = self.query_modifiers( 3489 expression, 3490 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3491 self.sql(expression, "into", comment=False), 3492 self.sql(expression, "from_", comment=False), 3493 ) 3494 3495 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3496 if expression.args.get("with_"): 3497 sql = self.maybe_comment(sql, expression) 3498 expression.pop_comments() 3499 3500 sql = self.prepend_ctes(expression, sql) 3501 3502 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3503 expression.set("exclude", None) 3504 subquery = expression.subquery(copy=False) 3505 star = exp.Star(except_=exclude) 3506 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3507 3508 if not self.SUPPORTS_SELECT_INTO and into: 3509 if into.args.get("temporary"): 3510 table_kind = " TEMPORARY" 3511 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3512 table_kind = " UNLOGGED" 3513 else: 3514 table_kind = "" 3515 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3516 3517 return sql
3529 def star_sql(self, expression: exp.Star) -> str: 3530 except_ = self.expressions(expression, key="except_", flat=True) 3531 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3532 replace = self.expressions(expression, key="replace", flat=True) 3533 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3534 rename = self.expressions(expression, key="rename", flat=True) 3535 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3536 ilike = self.sql(expression, "ilike") 3537 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3538 return f"*{ilike}{except_}{replace}{rename}"
3554 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3555 alias = self.sql(expression, "alias") 3556 alias = f"{sep}{alias}" if alias else "" 3557 sample = self.sql(expression, "sample") 3558 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3559 alias = f"{sample}{alias}" 3560 3561 # Set to None so it's not generated again by self.query_modifiers() 3562 expression.set("sample", None) 3563 3564 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3565 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3566 return self.prepend_ctes(expression, sql)
3572 def unnest_sql(self, expression: exp.Unnest) -> str: 3573 args = self.expressions(expression, flat=True) 3574 3575 alias = expression.args.get("alias") 3576 offset = expression.args.get("offset") 3577 3578 if self.UNNEST_WITH_ORDINALITY: 3579 if alias and isinstance(offset, exp.Expr): 3580 alias.append("columns", offset) 3581 expression.set("offset", None) 3582 3583 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3584 columns = alias.columns 3585 alias = self.sql(columns[0]) if columns else "" 3586 else: 3587 alias = self.sql(alias) 3588 3589 alias = f" AS {alias}" if alias else alias 3590 if self.UNNEST_WITH_ORDINALITY: 3591 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3592 else: 3593 if isinstance(offset, exp.Expr): 3594 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3595 elif offset: 3596 suffix = f"{alias} WITH OFFSET" 3597 else: 3598 suffix = alias 3599 3600 return f"UNNEST({args}){suffix}"
3609 def window_sql(self, expression: exp.Window) -> str: 3610 this = self.sql(expression, "this") 3611 partition = self.partition_by_sql(expression) 3612 order = expression.args.get("order") 3613 order = self.order_sql(order, flat=True) if order else "" 3614 spec = self.sql(expression, "spec") 3615 alias = self.sql(expression, "alias") 3616 over = self.sql(expression, "over") or "OVER" 3617 3618 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3619 3620 first = expression.args.get("first") 3621 if first is None: 3622 first = "" 3623 else: 3624 first = "FIRST" if first else "LAST" 3625 3626 if not partition and not order and not spec and alias: 3627 return f"{this} {alias}" 3628 3629 args = self.format_args( 3630 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3631 ) 3632 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3638 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3639 kind = self.sql(expression, "kind") 3640 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3641 end = ( 3642 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3643 or "CURRENT ROW" 3644 ) 3645 3646 window_spec = f"{kind} BETWEEN {start} AND {end}" 3647 3648 exclude = self.sql(expression, "exclude") 3649 if exclude: 3650 if self.SUPPORTS_WINDOW_EXCLUDE: 3651 window_spec += f" EXCLUDE {exclude}" 3652 else: 3653 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3654 3655 return window_spec
3662 def between_sql(self, expression: exp.Between) -> str: 3663 this = self.sql(expression, "this") 3664 low = self.sql(expression, "low") 3665 high = self.sql(expression, "high") 3666 symmetric = expression.args.get("symmetric") 3667 3668 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3669 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3670 3671 flag = ( 3672 " SYMMETRIC" 3673 if symmetric 3674 else " ASYMMETRIC" 3675 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3676 else "" # silently drop ASYMMETRIC – semantics identical 3677 ) 3678 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]:
3680 def bracket_offset_expressions( 3681 self, expression: exp.Bracket, index_offset: int | None = None 3682 ) -> list[exp.Expr]: 3683 if expression.args.get("json_access"): 3684 return expression.expressions 3685 3686 return apply_index_offset( 3687 expression.this, 3688 expression.expressions, 3689 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3690 dialect=self.dialect, 3691 )
3704 def any_sql(self, expression: exp.Any) -> str: 3705 this = self.sql(expression, "this") 3706 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3707 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3708 this = self.wrap(this) 3709 return f"ANY{this}" 3710 return f"ANY {this}"
3715 def case_sql(self, expression: exp.Case) -> str: 3716 this = self.sql(expression, "this") 3717 statements = [f"CASE {this}" if this else "CASE"] 3718 3719 for e in expression.args["ifs"]: 3720 statements.append(f"WHEN {self.sql(e, 'this')}") 3721 statements.append(f"THEN {self.sql(e, 'true')}") 3722 3723 default = self.sql(expression, "default") 3724 3725 if default: 3726 statements.append(f"ELSE {default}") 3727 3728 statements.append("END") 3729 3730 if self.pretty and self.too_wide(statements): 3731 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3732 3733 return " ".join(statements)
3745 def extract_sql(self, expression: exp.Extract) -> str: 3746 import sqlglot.dialects.dialect 3747 3748 this = ( 3749 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3750 if self.NORMALIZE_EXTRACT_DATE_PARTS 3751 else expression.this 3752 ) 3753 if self.EXTRACT_ALLOWS_QUOTES: 3754 this_sql = self.sql(this) 3755 elif isinstance(this, exp.WeekStart): 3756 this_sql = self.weekstart_name(this) 3757 else: 3758 this_sql = this.name 3759 expression_sql = self.sql(expression, "expression") 3760 3761 return f"EXTRACT({this_sql} FROM {expression_sql})"
3763 def trim_sql(self, expression: exp.Trim) -> str: 3764 trim_type = self.sql(expression, "position") 3765 3766 if trim_type == "LEADING": 3767 func_name = "LTRIM" 3768 elif trim_type == "TRAILING": 3769 func_name = "RTRIM" 3770 else: 3771 func_name = "TRIM" 3772 3773 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3775 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3776 args = expression.expressions 3777 if isinstance(expression, exp.ConcatWs): 3778 args = args[1:] # Skip the delimiter 3779 3780 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3781 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3782 3783 concat_coalesce = ( 3784 self.dialect.CONCAT_WS_COALESCE 3785 if isinstance(expression, exp.ConcatWs) 3786 else self.dialect.CONCAT_COALESCE 3787 ) 3788 3789 if not concat_coalesce and expression.args.get("coalesce"): 3790 3791 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3792 if not e.type: 3793 import sqlglot.optimizer.annotate_types 3794 3795 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3796 3797 if e.is_string or e.is_type(exp.DType.ARRAY): 3798 return e 3799 3800 return exp.func("coalesce", e, exp.Literal.string("")) 3801 3802 args = [_wrap_with_coalesce(e) for e in args] 3803 3804 return args
3806 def concat_sql(self, expression: exp.Concat) -> str: 3807 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3808 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3809 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3810 # instead of coalescing them to empty string. 3811 import sqlglot.dialects.dialect 3812 3813 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3814 3815 expressions = self.convert_concat_args(expression) 3816 3817 # Some dialects don't allow a single-argument CONCAT call 3818 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3819 return self.sql(expressions[0]) 3820 3821 return self.func("CONCAT", *expressions)
3823 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3824 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3825 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3826 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3827 all_args = expression.expressions 3828 expression.set("coalesce", True) 3829 return self.sql( 3830 exp.case() 3831 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3832 .else_(expression) 3833 ) 3834 3835 return self.func( 3836 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3837 )
3843 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3844 expressions = self.expressions(expression, flat=True) 3845 expressions = f" ({expressions})" if expressions else "" 3846 reference = self.sql(expression, "reference") 3847 reference = f" {reference}" if reference else "" 3848 delete = self.sql(expression, "delete") 3849 delete = f" ON DELETE {delete}" if delete else "" 3850 update = self.sql(expression, "update") 3851 update = f" ON UPDATE {update}" if update else "" 3852 options = self.expressions(expression, key="options", flat=True, sep=" ") 3853 options = f" {options}" if options else "" 3854 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3856 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3857 this = self.sql(expression, "this") 3858 this = f" {this}" if this else "" 3859 expressions = self.expressions(expression, flat=True) 3860 include = self.sql(expression, "include") 3861 options = self.expressions(expression, key="options", flat=True, sep=" ") 3862 options = f" {options}" if options else "" 3863 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3872 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3873 if self.MATCH_AGAINST_TABLE_PREFIX: 3874 expressions = [] 3875 for expr in expression.expressions: 3876 if isinstance(expr, exp.Table): 3877 expressions.append(f"TABLE {self.sql(expr)}") 3878 else: 3879 expressions.append(expr) 3880 else: 3881 expressions = expression.expressions 3882 3883 modifier = expression.args.get("modifier") 3884 modifier = f" {modifier}" if modifier else "" 3885 return ( 3886 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3887 )
3901 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3902 if isinstance(expression, exp.JSONPathPart): 3903 transform = self.TRANSFORMS.get(expression.__class__) 3904 if not callable(transform): 3905 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3906 return "" 3907 3908 return transform(self, expression) 3909 3910 if isinstance(expression, int): 3911 return str(expression) 3912 3913 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3914 escaped = expression.replace("'", "\\'") 3915 escaped = f"'{escaped}'" 3916 else: 3917 escaped = expression.replace('"', '\\"') 3918 escaped = f'"{escaped}"' 3919 3920 return escaped
3925 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3926 # Output the Teradata column FORMAT override. 3927 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3928 this = self.sql(expression, "this") 3929 fmt = self.sql(expression, "format") 3930 return f"{this} (FORMAT {fmt})"
3958 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3959 null_handling = expression.args.get("null_handling") 3960 null_handling = f" {null_handling}" if null_handling else "" 3961 return_type = self.sql(expression, "return_type") 3962 return_type = f" RETURNING {return_type}" if return_type else "" 3963 strict = " STRICT" if expression.args.get("strict") else "" 3964 return self.func( 3965 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3966 )
3968 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3969 this = self.sql(expression, "this") 3970 order = self.sql(expression, "order") 3971 null_handling = expression.args.get("null_handling") 3972 null_handling = f" {null_handling}" if null_handling else "" 3973 return_type = self.sql(expression, "return_type") 3974 return_type = f" RETURNING {return_type}" if return_type else "" 3975 strict = " STRICT" if expression.args.get("strict") else "" 3976 return self.func( 3977 "JSON_ARRAYAGG", 3978 this, 3979 suffix=f"{order}{null_handling}{return_type}{strict})", 3980 )
3982 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3983 path = self.sql(expression, "path") 3984 path = f" PATH {path}" if path else "" 3985 nested_schema = self.sql(expression, "nested_schema") 3986 3987 if nested_schema: 3988 return f"NESTED{path} {nested_schema}" 3989 3990 this = self.sql(expression, "this") 3991 kind = self.sql(expression, "kind") 3992 kind = f" {kind}" if kind else "" 3993 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3994 3995 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3996 return f"{this}{kind}{format_json}{path}{ordinality}"
4001 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4002 this = self.sql(expression, "this") 4003 path = self.sql(expression, "path") 4004 path = f", {path}" if path else "" 4005 error_handling = expression.args.get("error_handling") 4006 error_handling = f" {error_handling}" if error_handling else "" 4007 empty_handling = expression.args.get("empty_handling") 4008 empty_handling = f" {empty_handling}" if empty_handling else "" 4009 schema = self.sql(expression, "schema") 4010 return self.func( 4011 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4012 )
4014 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4015 this = self.sql(expression, "this") 4016 kind = self.sql(expression, "kind") 4017 path = self.sql(expression, "path") 4018 path = f" {path}" if path else "" 4019 as_json = " AS JSON" if expression.args.get("as_json") else "" 4020 return f"{this} {kind}{path}{as_json}"
4022 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4023 this = self.sql(expression, "this") 4024 path = self.sql(expression, "path") 4025 path = f", {path}" if path else "" 4026 expressions = self.expressions(expression) 4027 with_ = ( 4028 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4029 if expressions 4030 else "" 4031 ) 4032 return f"OPENJSON({this}{path}){with_}"
4034 def in_sql(self, expression: exp.In) -> str: 4035 query = expression.args.get("query") 4036 unnest = expression.args.get("unnest") 4037 field = expression.args.get("field") 4038 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4039 4040 if query: 4041 in_sql = self.sql(query) 4042 elif unnest: 4043 in_sql = self.in_unnest_op(unnest) 4044 elif field: 4045 in_sql = self.sql(field) 4046 else: 4047 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4048 4049 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4054 def interval_sql(self, expression: exp.Interval) -> str: 4055 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4056 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4057 exp.AutoRefreshProperty, 4058 ) 4059 interval_keyword = "INTERVAL" if include_keyword else "" 4060 unit_expression = expression.args.get("unit") 4061 unit = self.sql(unit_expression) if unit_expression else "" 4062 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4063 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4064 unit = f" {unit}" if unit else "" 4065 4066 if self.SINGLE_STRING_INTERVAL: 4067 this = expression.this.name if expression.this else "" 4068 if this: 4069 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4070 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4071 return f"{interval_keyword}'{this}'{unit}" 4072 return f"{interval_keyword}'{this}{unit}'" 4073 return f"{interval_keyword}{unit}" 4074 4075 this = self.sql(expression, "this") 4076 if this: 4077 if not include_keyword and expression.this.is_string: 4078 this = expression.this.name 4079 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4080 this = f"({this})" 4081 if include_keyword: 4082 this = f" {this}" 4083 4084 return f"{interval_keyword}{this}{unit}"
4089 def reference_sql(self, expression: exp.Reference) -> str: 4090 this = self.sql(expression, "this") 4091 expressions = self.expressions(expression, flat=True) 4092 expressions = f"({expressions})" if expressions else "" 4093 options = self.expressions(expression, key="options", flat=True, sep=" ") 4094 options = f" {options}" if options else "" 4095 return f"REFERENCES {this}{expressions}{options}"
4097 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4098 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4099 parent = expression.parent 4100 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4101 4102 return self.func( 4103 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4104 )
4124 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4125 alias = expression.args["alias"] 4126 4127 parent = expression.parent 4128 pivot = parent and parent.parent 4129 4130 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4131 identifier_alias = isinstance(alias, exp.Identifier) 4132 literal_alias = isinstance(alias, exp.Literal) 4133 4134 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4135 alias.replace(exp.Literal.string(alias.output_name)) 4136 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4137 alias.replace(exp.to_identifier(alias.output_name)) 4138 4139 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:
4180 def connector_sql( 4181 self, 4182 expression: exp.Connector, 4183 op: str, 4184 stack: list[str | exp.Expr] | None = None, 4185 ) -> str: 4186 if stack is not None: 4187 stack.append(expression.right) 4188 if expression.comments and self.comments: 4189 op = self.maybe_comment(op, comments=expression.comments) 4190 4191 stack.extend((op, expression.left)) 4192 return op 4193 4194 stack = [expression] 4195 sqls: list[str] = [] 4196 ops = set() 4197 4198 while stack: 4199 node = stack.pop() 4200 if isinstance(node, exp.Connector): 4201 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4202 else: 4203 sql = self.sql(node) 4204 if sqls and sqls[-1] in ops: 4205 sqls[-1] += f" {sql}" 4206 else: 4207 sqls.append(sql) 4208 4209 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4210 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4230 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4231 format_sql = self.sql(expression, "format") 4232 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4233 to_sql = self.sql(expression, "to") 4234 to_sql = f" {to_sql}" if to_sql else "" 4235 action = self.sql(expression, "action") 4236 action = f" {action}" if action else "" 4237 default = self.sql(expression, "default") 4238 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4239 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4269 def comment_sql(self, expression: exp.Comment) -> str: 4270 this = self.sql(expression, "this") 4271 kind = expression.args["kind"] 4272 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4273 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4274 expression_sql = self.sql(expression, "expression") 4275 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4277 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4278 this = self.sql(expression, "this") 4279 delete = " DELETE" if expression.args.get("delete") else "" 4280 recompress = self.sql(expression, "recompress") 4281 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4282 to_disk = self.sql(expression, "to_disk") 4283 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4284 to_volume = self.sql(expression, "to_volume") 4285 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4286 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4288 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4289 where = self.sql(expression, "where") 4290 group = self.sql(expression, "group") 4291 aggregates = self.expressions(expression, key="aggregates") 4292 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4293 4294 if not (where or group or aggregates) and len(expression.expressions) == 1: 4295 return f"TTL {self.expressions(expression, flat=True)}" 4296 4297 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4316 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4317 this = self.sql(expression, "this") 4318 4319 exists = "" 4320 if expression.args.get("exists"): 4321 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4322 exists = " IF EXISTS" 4323 else: 4324 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4325 4326 dtype = self.sql(expression, "dtype") 4327 if dtype: 4328 collate = self.sql(expression, "collate") 4329 collate = f" COLLATE {collate}" if collate else "" 4330 using = self.sql(expression, "using") 4331 using = f" USING {using}" if using else "" 4332 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4333 null_constraint = self._alter_column_null_constraint_sql(expression) 4334 4335 return ( 4336 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4337 f"{collate}{using}{null_constraint}" 4338 ) 4339 4340 default = self.sql(expression, "default") 4341 if default: 4342 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4343 4344 comment = self.sql(expression, "comment") 4345 if comment: 4346 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4347 4348 visible = expression.args.get("visible") 4349 if visible: 4350 return f"ALTER COLUMN{exists} {this} SET {visible}" 4351 4352 allow_null = expression.args.get("allow_null") 4353 drop = expression.args.get("drop") 4354 4355 if not drop and not allow_null: 4356 self.unsupported("Unsupported ALTER COLUMN syntax") 4357 4358 if allow_null is not None: 4359 keyword = "DROP" if drop else "SET" 4360 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4361 4362 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4375 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4376 this = self.sql(expression, "this") 4377 rename_from = self.sql(expression, "rename_from") 4378 if rename_from: 4379 if not self.SUPPORTS_CHANGE_COLUMN: 4380 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4381 return f"CHANGE COLUMN {rename_from} {this}" 4382 if not self.SUPPORTS_MODIFY_COLUMN: 4383 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4384 return f"MODIFY COLUMN {this}"
4400 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4401 compound = " COMPOUND" if expression.args.get("compound") else "" 4402 this = self.sql(expression, "this") 4403 expressions = self.expressions(expression, flat=True) 4404 expressions = f"({expressions})" if expressions else "" 4405 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4407 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4408 if not self.RENAME_TABLE_WITH_DB: 4409 # Remove db from tables 4410 expression = expression.transform( 4411 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4412 ).assert_is(exp.AlterRename) 4413 this = self.sql(expression, "this") 4414 to_kw = " TO" if include_to else "" 4415 return f"RENAME{to_kw} {this}"
4430 def alter_sql(self, expression: exp.Alter) -> str: 4431 actions = expression.actions 4432 4433 if ( 4434 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4435 and actions 4436 and isinstance(actions[0], exp.ColumnDef) 4437 ): 4438 actions_sql = self.expressions(expression, key="actions", flat=True) 4439 actions_sql = f"ADD {actions_sql}" 4440 else: 4441 actions_list = [] 4442 for action in actions: 4443 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4444 action_sql = self.add_column_sql(action) 4445 else: 4446 action_sql = self.sql(action) 4447 if isinstance(action, exp.Query): 4448 action_sql = f"AS {action_sql}" 4449 4450 actions_list.append(action_sql) 4451 4452 actions_sql = self.format_args(*actions_list).lstrip("\n") 4453 4454 iceberg = ( 4455 "ICEBERG " 4456 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4457 else "" 4458 ) 4459 exists = " IF EXISTS" if expression.args.get("exists") else "" 4460 on_cluster = self.sql(expression, "cluster") 4461 on_cluster = f" {on_cluster}" if on_cluster else "" 4462 only = " ONLY" if expression.args.get("only") else "" 4463 options = expression.args.get("options") or [] 4464 options_sql = self.expressions( 4465 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4466 ) 4467 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4468 partitions_sql = self.expressions( 4469 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4470 sep=" ", 4471 flat=True, 4472 ) 4473 if partitions_sql and (actions_sql or options_sql): 4474 partitions_sql = f" {partitions_sql}" 4475 kind = self.sql(expression, "kind") 4476 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4477 check = " WITH CHECK" if expression.args.get("check") else "" 4478 cascade = ( 4479 " CASCADE" 4480 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4481 else "" 4482 ) 4483 this = self.sql(expression, "this") 4484 this = f" {this}" if this else "" 4485 4486 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4493 def add_column_sql(self, expression: exp.Expr) -> str: 4494 sql = self.sql(expression) 4495 if isinstance(expression, exp.Schema): 4496 column_text = " COLUMNS" 4497 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4498 column_text = " COLUMN" 4499 else: 4500 column_text = "" 4501 4502 return f"ADD{column_text} {sql}"
4515 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4516 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4517 location = self.sql(expression, "location") 4518 location = f" {location}" if location else "" 4519 return f"ADD {exists}{self.sql(expression.this)}{location}"
4521 def distinct_sql(self, expression: exp.Distinct) -> str: 4522 this = self.expressions(expression, flat=True) 4523 4524 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4525 case = exp.case() 4526 for arg in expression.expressions: 4527 case = case.when(arg.is_(exp.null()), exp.null()) 4528 this = self.sql(case.else_(f"({this})")) 4529 4530 this = f" {this}" if this else "" 4531 4532 on = self.sql(expression, "on") 4533 on = f" ON {on}" if on else "" 4534 return f"DISTINCT{this}{on}"
4563 def div_sql(self, expression: exp.Div) -> str: 4564 l, r = expression.left, expression.right 4565 4566 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4567 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4568 4569 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4570 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4571 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4572 4573 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4574 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4575 # Native integer division depends on the target's operand types, which may differ 4576 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4577 # integer literals or casts 4578 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4579 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4580 ): 4581 return self.sql(exp.IntDiv(this=l, expression=r)) 4582 return self._truncated_div_sql(l, r) 4583 4584 return self.binary(expression, "/")
4609 def escape_sql(self, expression: exp.Escape) -> str: 4610 this = expression.this 4611 if ( 4612 isinstance(this, (exp.Like, exp.ILike)) 4613 and isinstance(this.expression, (exp.All, exp.Any)) 4614 and not self.SUPPORTS_LIKE_QUANTIFIERS 4615 ): 4616 return self._like_sql(this, escape=expression) 4617 return self.binary(expression, "ESCAPE")
4628 def is_sql(self, expression: exp.Is) -> str: 4629 negate = expression.args.get("negate") 4630 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4631 positive = bool(expression.expression.this) != bool(negate) 4632 return self.sql(expression.this if positive else exp.not_(expression.this)) 4633 return self.binary(expression, "IS NOT" if negate else "IS")
4703 def mod_sql(self, expression: exp.Mod) -> str: 4704 this = self.sql(expression, "this") 4705 expr = self.sql(expression, "expression") 4706 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4707 4708 parent = expression.parent 4709 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4710 return f"({sql})" 4711 4712 return sql
4742 def log_sql(self, expression: exp.Log) -> str: 4743 this = expression.this 4744 expr = expression.expression 4745 4746 if self.dialect.LOG_BASE_FIRST is False: 4747 this, expr = expr, this 4748 elif self.dialect.LOG_BASE_FIRST is None and expr: 4749 if this.name in ("2", "10"): 4750 return self.func(f"LOG{this.name}", expr) 4751 4752 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4753 4754 return self.func("LOG", this, expr)
4763 def binary(self, expression: exp.Binary, op: str) -> str: 4764 sqls: list[str] = [] 4765 stack: list[None | str | exp.Expr] = [expression] 4766 binary_type = type(expression) 4767 4768 while stack: 4769 node = stack.pop() 4770 4771 if type(node) is binary_type: 4772 op_func = node.args.get("operator") 4773 if op_func: 4774 op = f"OPERATOR({self.sql(op_func)})" 4775 4776 stack.append(node.args.get("expression")) 4777 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4778 stack.append(node.args.get("this")) 4779 else: 4780 sqls.append(self.sql(node)) 4781 4782 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4791 def function_fallback_sql(self, expression: exp.Func) -> str: 4792 args = [] 4793 4794 for key in expression.arg_types: 4795 arg_value = expression.args.get(key) 4796 4797 if isinstance(arg_value, list): 4798 for value in arg_value: 4799 args.append(value) 4800 elif arg_value is not None: 4801 args.append(arg_value) 4802 4803 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:
4820 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4821 arg_sqls = tuple( 4822 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4823 ) 4824 if self.pretty and self.too_wide(arg_sqls): 4825 return self.indent( 4826 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4827 ) 4828 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:
4833 def format_time( 4834 self, 4835 expression: exp.Expr, 4836 inverse_time_mapping: dict[str, str] | None = None, 4837 inverse_time_trie: dict | None = None, 4838 ) -> str | None: 4839 return format_time( 4840 self.sql(expression, "format"), 4841 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4842 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4843 )
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:
4845 def expressions( 4846 self, 4847 expression: exp.Expr | None = None, 4848 key: str | None = None, 4849 sqls: t.Collection[str | exp.Expr] | None = None, 4850 flat: bool = False, 4851 indent: bool = True, 4852 skip_first: bool = False, 4853 skip_last: bool = False, 4854 sep: str = ", ", 4855 prefix: str = "", 4856 dynamic: bool = False, 4857 new_line: bool = False, 4858 ) -> str: 4859 expressions = expression.args.get(key or "expressions") if expression else sqls 4860 4861 if not expressions: 4862 return "" 4863 4864 if flat: 4865 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4866 4867 num_sqls = len(expressions) 4868 result_sqls = [] 4869 4870 for i, e in enumerate(expressions): 4871 sql = self.sql(e, comment=False) 4872 if not sql: 4873 continue 4874 4875 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4876 4877 if self.pretty: 4878 if self.leading_comma: 4879 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4880 else: 4881 result_sqls.append( 4882 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4883 ) 4884 else: 4885 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4886 4887 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4888 if new_line: 4889 result_sqls.insert(0, "") 4890 result_sqls.append("") 4891 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4892 else: 4893 result_sql = "".join(result_sqls) 4894 4895 return ( 4896 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4897 if indent 4898 else result_sql 4899 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4901 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4902 flat = flat or isinstance(expression.parent, exp.Properties) 4903 expressions_sql = self.expressions(expression, flat=flat) 4904 if flat: 4905 return f"{op} {expressions_sql}" 4906 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4908 def naked_property(self, expression: exp.Property) -> str: 4909 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4910 if not property_name: 4911 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4912 return f"{property_name} {self.sql(expression, 'this')}"
4920 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4921 this = self.sql(expression, "this") 4922 expressions = self.no_identify(self.expressions, expression) 4923 expressions = ( 4924 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4925 ) 4926 return f"{this}{expressions}" if expressions.strip() != "" else this
4945 def when_sql(self, expression: exp.When) -> str: 4946 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4947 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4948 condition = self.sql(expression, "condition") 4949 condition = f" AND {condition}" if condition else "" 4950 4951 then_expression = expression.args.get("then") 4952 if isinstance(then_expression, exp.Insert): 4953 this = self.sql(then_expression, "this") 4954 this = f"INSERT {this}" if this else "INSERT" 4955 then = self.sql(then_expression, "expression") 4956 then = f"{this} VALUES {then}" if then else this 4957 elif isinstance(then_expression, exp.Update): 4958 if isinstance(then_expression.args.get("expressions"), exp.Star): 4959 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4960 else: 4961 expressions_sql = self.expressions(then_expression) 4962 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4963 else: 4964 then = self.sql(then_expression) 4965 4966 if isinstance(then_expression, (exp.Insert, exp.Update)): 4967 where = self.sql(then_expression, "where") 4968 if where and not self.SUPPORTS_MERGE_WHERE: 4969 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4970 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4971 where = "" 4972 then = f"{then}{where}" 4973 return f"WHEN {matched}{source}{condition} THEN {then}"
4978 def merge_sql(self, expression: exp.Merge) -> str: 4979 table = expression.this 4980 table_alias = "" 4981 4982 hints = table.args.get("hints") 4983 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4984 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4985 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4986 4987 this = self.sql(table) 4988 using = f"USING {self.sql(expression, 'using')}" 4989 whens = self.sql(expression, "whens") 4990 4991 on = self.sql(expression, "on") 4992 on = f"ON {on}" if on else "" 4993 4994 if not on: 4995 on = self.expressions(expression, key="using_cond") 4996 on = f"USING ({on})" if on else "" 4997 4998 returning = self.sql(expression, "returning") 4999 if returning: 5000 whens = f"{whens}{returning}" 5001 5002 sep = self.sep() 5003 5004 return self.prepend_ctes( 5005 expression, 5006 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5007 )
@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:
5013 @unsupported_args("default") 5014 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5015 if not self.SUPPORTS_TO_NUMBER: 5016 self.unsupported("Unsupported TO_NUMBER function") 5017 return self._tonumber_cast_sql(expression) 5018 5019 fmt = expression.args.get("format") 5020 if not fmt: 5021 self.unsupported("Conversion format is required for TO_NUMBER") 5022 return self._tonumber_cast_sql(expression) 5023 5024 return self.func("TO_NUMBER", expression.this, fmt)
5031 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5032 this = self.sql(expression, "this") 5033 kind = self.sql(expression, "kind") 5034 settings_sql = self.expressions(expression, key="settings", sep=" ") 5035 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5036 return f"{this}({kind}{args})"
def
duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
def
distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
5057 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5058 expressions = self.expressions(expression, flat=True) 5059 expressions = f" {self.wrap(expressions)}" if expressions else "" 5060 buckets = self.sql(expression, "buckets") 5061 kind = self.sql(expression, "kind") 5062 buckets = f" BUCKETS {buckets}" if buckets else "" 5063 order = self.sql(expression, "order") 5064 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
5069 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5070 expressions = self.expressions(expression, key="expressions", flat=True) 5071 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5072 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5073 buckets = self.sql(expression, "buckets") 5074 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5076 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5077 this = self.sql(expression, "this") 5078 having = self.sql(expression, "having") 5079 5080 if having: 5081 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5082 5083 return self.func("ANY_VALUE", this)
5085 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5086 transform = self.func("TRANSFORM", *expression.expressions) 5087 row_format_before = self.sql(expression, "row_format_before") 5088 row_format_before = f" {row_format_before}" if row_format_before else "" 5089 record_writer = self.sql(expression, "record_writer") 5090 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5091 using = f" USING {self.sql(expression, 'command_script')}" 5092 schema = self.sql(expression, "schema") 5093 schema = f" AS {schema}" if schema else "" 5094 row_format_after = self.sql(expression, "row_format_after") 5095 row_format_after = f" {row_format_after}" if row_format_after else "" 5096 record_reader = self.sql(expression, "record_reader") 5097 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5098 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:
5100 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5101 key_block_size = self.sql(expression, "key_block_size") 5102 if key_block_size: 5103 return f"KEY_BLOCK_SIZE = {key_block_size}" 5104 5105 using = self.sql(expression, "using") 5106 if using: 5107 return f"USING {using}" 5108 5109 parser = self.sql(expression, "parser") 5110 if parser: 5111 return f"WITH PARSER {parser}" 5112 5113 comment = self.sql(expression, "comment") 5114 if comment: 5115 return f"COMMENT {comment}" 5116 5117 visible = expression.args.get("visible") 5118 if visible is not None: 5119 return "VISIBLE" if visible else "INVISIBLE" 5120 5121 engine_attr = self.sql(expression, "engine_attr") 5122 if engine_attr: 5123 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5124 5125 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5126 if secondary_engine_attr: 5127 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5128 5129 self.unsupported("Unsupported index constraint option.") 5130 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5136 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5137 kind = self.sql(expression, "kind") 5138 kind = f"{kind} INDEX" if kind else "INDEX" 5139 this = self.sql(expression, "this") 5140 this = f" {this}" if this else "" 5141 index_type = self.sql(expression, "index_type") 5142 index_type = f" USING {index_type}" if index_type else "" 5143 expressions = self.expressions(expression, flat=True) 5144 expressions = f" ({expressions})" if expressions else "" 5145 options = self.expressions(expression, key="options", sep=" ") 5146 options = f" {options}" if options else "" 5147 return f"{kind}{this}{index_type}{expressions}{options}"
5149 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5150 if self.NVL2_SUPPORTED: 5151 return self.function_fallback_sql(expression) 5152 5153 case = exp.Case().when( 5154 expression.this.is_(exp.null()).not_(copy=False), 5155 expression.args["true"], 5156 copy=False, 5157 ) 5158 else_cond = expression.args.get("false") 5159 if else_cond: 5160 case.else_(else_cond, copy=False) 5161 5162 return self.sql(case)
5170 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5171 this = self.sql(expression, "this") 5172 expr = self.sql(expression, "expression") 5173 position = self.sql(expression, "position") 5174 position = f", {position}" if position else "" 5175 iterator = self.sql(expression, "iterator") 5176 condition = self.sql(expression, "condition") 5177 condition = f" IF {condition}" if condition else "" 5178 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5228 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5229 this_sql = self.sql(expression, "this") 5230 if isinstance(expression.this, exp.Table): 5231 this_sql = f"TABLE {this_sql}" 5232 5233 return self.func( 5234 "FORECAST", 5235 this_sql, 5236 expression.args.get("data_col"), 5237 expression.args.get("timestamp_col"), 5238 expression.args.get("model"), 5239 expression.args.get("id_cols"), 5240 expression.args.get("horizon"), 5241 expression.args.get("forecast_end_timestamp"), 5242 expression.args.get("confidence_level"), 5243 expression.args.get("output_historical_time_series"), 5244 expression.args.get("context_window"), 5245 )
5247 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5248 this_sql = self.sql(expression, "this") 5249 if isinstance(expression.this, exp.Table): 5250 this_sql = f"TABLE {this_sql}" 5251 5252 return self.func( 5253 "FEATURES_AT_TIME", 5254 this_sql, 5255 expression.args.get("time"), 5256 expression.args.get("num_rows"), 5257 expression.args.get("ignore_feature_nulls"), 5258 )
5260 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5261 this_sql = self.sql(expression, "this") 5262 if isinstance(expression.this, exp.Table): 5263 this_sql = f"TABLE {this_sql}" 5264 5265 query_table = self.sql(expression, "query_table") 5266 if isinstance(expression.args["query_table"], exp.Table): 5267 query_table = f"TABLE {query_table}" 5268 5269 return self.func( 5270 "VECTOR_SEARCH", 5271 this_sql, 5272 expression.args.get("column_to_search"), 5273 query_table, 5274 expression.args.get("query_column_to_search"), 5275 expression.args.get("top_k"), 5276 expression.args.get("distance_type"), 5277 expression.args.get("options"), 5278 )
5290 def toarray_sql(self, expression: exp.ToArray) -> str: 5291 arg = expression.this 5292 if not arg.type: 5293 import sqlglot.optimizer.annotate_types 5294 5295 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5296 5297 if arg.is_type(exp.DType.ARRAY): 5298 return self.sql(arg) 5299 5300 cond_for_null = arg.is_(exp.null()) 5301 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5303 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5304 this = expression.this 5305 time_format = self.format_time(expression) 5306 5307 if time_format: 5308 return self.sql( 5309 exp.cast( 5310 exp.StrToTime(this=this, format=expression.args["format"]), 5311 exp.DType.TIME, 5312 ) 5313 ) 5314 5315 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5316 return self.sql(this) 5317 5318 return self.sql(exp.cast(this, exp.DType.TIME))
5320 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5321 this = expression.this 5322 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5323 return self.sql(this) 5324 5325 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5327 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5328 this = expression.this 5329 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5330 return self.sql(this) 5331 5332 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5334 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5335 this = expression.this 5336 time_format = self.format_time(expression) 5337 safe = expression.args.get("safe") 5338 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5339 return self.sql( 5340 exp.cast( 5341 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5342 exp.DType.DATE, 5343 ) 5344 ) 5345 5346 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5347 return self.sql(this) 5348 5349 if safe: 5350 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5351 5352 return self.sql(exp.cast(this, exp.DType.DATE))
5364 def lastday_sql(self, expression: exp.LastDay) -> str: 5365 if self.LAST_DAY_SUPPORTS_DATE_PART: 5366 return self.function_fallback_sql(expression) 5367 5368 unit = expression.args.get("unit") 5369 if unit and unit.name.upper() != "MONTH": 5370 self.unsupported("Date parts are not supported in LAST_DAY.") 5371 5372 return self.func("LAST_DAY", expression.this)
def
arrayinsert_sql( self, expression: sqlglot.expressions.array.ArrayInsert, index_offset: int = 0) -> str:
5384 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5385 this = expression.this 5386 position = expression.args["position"] 5387 offset = index_offset - (expression.args.get("offset") or 0) 5388 5389 if offset: 5390 if position.is_int: 5391 value = position.to_py() 5392 if value >= 0: 5393 position = exp.Literal.number(value + offset) 5394 elif offset < 0 and value == -1: 5395 # 1-based -1 appends, which a 0-based position can only express as the size 5396 position = exp.ArraySize(this=this.copy()) 5397 else: 5398 # Negative positions count from the end, so they shift in the opposite 5399 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5400 position = exp.Literal.number(value - offset) 5401 else: 5402 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5403 5404 return self.func("ARRAY_INSERT", this, position, expression.expression)
5406 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5407 if self.CAN_IMPLEMENT_ARRAY_ANY: 5408 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5409 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5410 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5411 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5412 5413 import sqlglot.dialects.dialect 5414 5415 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5416 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5417 self.unsupported("ARRAY_ANY is unsupported") 5418 5419 return self.function_fallback_sql(expression)
5421 def struct_sql(self, expression: exp.Struct) -> str: 5422 expression.set( 5423 "expressions", 5424 [ 5425 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5426 if isinstance(e, exp.PropertyEQ) 5427 else e 5428 for e in expression.expressions 5429 ], 5430 ) 5431 5432 return self.function_fallback_sql(expression)
5440 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5441 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5442 tables = f" {self.expressions(expression)}" 5443 5444 exists = " IF EXISTS" if expression.args.get("exists") else "" 5445 5446 on_cluster = self.sql(expression, "cluster") 5447 on_cluster = f" {on_cluster}" if on_cluster else "" 5448 5449 identity = self.sql(expression, "identity") 5450 identity = f" {identity} IDENTITY" if identity else "" 5451 5452 option = self.sql(expression, "option") 5453 option = f" {option}" if option else "" 5454 5455 partition = self.sql(expression, "partition") 5456 partition = f" {partition}" if partition else "" 5457 5458 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5462 def convert_sql(self, expression: exp.Convert) -> str: 5463 to = expression.this 5464 value = expression.expression 5465 style = expression.args.get("style") 5466 safe = expression.args.get("safe") 5467 strict = expression.args.get("strict") 5468 5469 if not to or not value: 5470 return "" 5471 5472 # Retrieve length of datatype and override to default if not specified 5473 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5474 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5475 5476 transformed: exp.Expr | None = None 5477 cast = exp.Cast if strict else exp.TryCast 5478 5479 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5480 if isinstance(style, exp.Literal) and style.is_int: 5481 import sqlglot.dialects.tsql 5482 5483 style_value = style.name 5484 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5485 if not converted_style: 5486 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5487 5488 fmt = exp.Literal.string(converted_style) 5489 5490 if to.this == exp.DType.DATE: 5491 transformed = exp.StrToDate(this=value, format=fmt) 5492 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5493 transformed = exp.StrToTime(this=value, format=fmt) 5494 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5495 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5496 elif to.this == exp.DType.TEXT: 5497 transformed = exp.TimeToStr(this=value, format=fmt) 5498 5499 if not transformed: 5500 transformed = cast(this=value, to=to, safe=safe) 5501 5502 return self.sql(transformed)
5579 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5580 option = self.sql(expression, "this") 5581 5582 if expression.expressions: 5583 upper = option.upper() 5584 5585 # Snowflake FILE_FORMAT options are separated by whitespace 5586 sep = " " if upper == "FILE_FORMAT" else ", " 5587 5588 # Databricks copy/format options do not set their list of values with EQ 5589 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5590 values = self.expressions(expression, flat=True, sep=sep) 5591 return f"{option}{op}({values})" 5592 5593 value = self.sql(expression, "expression") 5594 5595 if not value: 5596 return option 5597 5598 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5599 5600 return f"{option}{op}{value}"
5602 def credentials_sql(self, expression: exp.Credentials) -> str: 5603 cred_expr = expression.args.get("credentials") 5604 if isinstance(cred_expr, exp.Literal): 5605 # Redshift case: CREDENTIALS <string> 5606 credentials = self.sql(expression, "credentials") 5607 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5608 else: 5609 # Snowflake case: CREDENTIALS = (...) 5610 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5611 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5612 5613 storage = self.sql(expression, "storage") 5614 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5615 5616 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5617 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5618 5619 iam_role = self.sql(expression, "iam_role") 5620 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5621 5622 region = self.sql(expression, "region") 5623 region = f" REGION {region}" if region else "" 5624 5625 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5627 def copy_sql(self, expression: exp.Copy) -> str: 5628 this = self.sql(expression, "this") 5629 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5630 5631 credentials = self.sql(expression, "credentials") 5632 credentials = self.seg(credentials) if credentials else "" 5633 files = self.expressions(expression, key="files", flat=True) 5634 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5635 5636 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5637 params = self.expressions( 5638 expression, 5639 key="params", 5640 sep=sep, 5641 new_line=True, 5642 skip_last=True, 5643 skip_first=True, 5644 indent=self.COPY_PARAMS_ARE_WRAPPED, 5645 ) 5646 5647 if params: 5648 if self.COPY_PARAMS_ARE_WRAPPED: 5649 params = f" WITH ({params})" 5650 elif not self.pretty and (files or credentials): 5651 params = f" {params}" 5652 5653 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5658 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5659 on_sql = "ON" if expression.args.get("on") else "OFF" 5660 filter_col: str | None = self.sql(expression, "filter_column") 5661 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5662 retention_period: str | None = self.sql(expression, "retention_period") 5663 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5664 5665 if filter_col or retention_period: 5666 on_sql = self.func("ON", filter_col, retention_period) 5667 5668 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5670 def maskingpolicycolumnconstraint_sql( 5671 self, expression: exp.MaskingPolicyColumnConstraint 5672 ) -> str: 5673 this = self.sql(expression, "this") 5674 expressions = self.expressions(expression, flat=True) 5675 expressions = f" USING ({expressions})" if expressions else "" 5676 return f"MASKING POLICY {this}{expressions}"
5686 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5687 this = self.sql(expression, "this") 5688 expr = expression.expression 5689 5690 if isinstance(expr, exp.Func): 5691 # T-SQL's CLR functions are case sensitive 5692 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5693 else: 5694 expr = self.sql(expression, "expression") 5695 5696 return self.scope_resolution(expr, this)
5704 def rand_sql(self, expression: exp.Rand) -> str: 5705 lower = self.sql(expression, "lower") 5706 upper = self.sql(expression, "upper") 5707 5708 if lower and upper: 5709 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5710 return self.func("RAND", expression.this)
5712 def changes_sql(self, expression: exp.Changes) -> str: 5713 information = self.sql(expression, "information") 5714 information = f"INFORMATION => {information}" 5715 at_before = self.sql(expression, "at_before") 5716 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5717 end = self.sql(expression, "end") 5718 end = f"{self.seg('')}{end}" if end else "" 5719 5720 return f"CHANGES ({information}){at_before}{end}"
5722 def pad_sql(self, expression: exp.Pad) -> str: 5723 prefix = "L" if expression.args.get("is_left") else "R" 5724 5725 fill_pattern = self.sql(expression, "fill_pattern") or None 5726 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5727 fill_pattern = "' '" 5728 5729 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5735 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5736 generate_series = exp.GenerateSeries(**expression.args) 5737 5738 parent = expression.parent 5739 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5740 parent = parent.parent 5741 5742 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5743 return self.sql(exp.Unnest(expressions=[generate_series])) 5744 5745 if isinstance(parent, exp.Select): 5746 self.unsupported("GenerateSeries projection unnesting is not supported.") 5747 5748 return self.sql(generate_series)
5750 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5751 if self.SUPPORTS_CONVERT_TIMEZONE: 5752 return self.function_fallback_sql(expression) 5753 5754 source_tz = expression.args.get("source_tz") 5755 target_tz = expression.args.get("target_tz") 5756 timestamp = expression.args.get("timestamp") 5757 5758 if source_tz and timestamp: 5759 timestamp = exp.AtTimeZone( 5760 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5761 ) 5762 5763 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5764 5765 return self.sql(expr)
5767 def json_sql(self, expression: exp.JSON) -> str: 5768 this = self.sql(expression, "this") 5769 this = f" {this}" if this else "" 5770 5771 _with = expression.args.get("with_") 5772 5773 if _with is None: 5774 with_sql = "" 5775 elif not _with: 5776 with_sql = " WITHOUT" 5777 else: 5778 with_sql = " WITH" 5779 5780 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5781 5782 return f"JSON{this}{with_sql}{unique_sql}"
5784 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5785 path = self.sql(expression, "path") 5786 returning = self.sql(expression, "returning") 5787 returning = f" RETURNING {returning}" if returning else "" 5788 5789 on_condition = self.sql(expression, "on_condition") 5790 on_condition = f" {on_condition}" if on_condition else "" 5791 5792 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5798 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5799 else_ = "ELSE " if expression.args.get("else_") else "" 5800 condition = self.sql(expression, "expression") 5801 condition = f"WHEN {condition} THEN " if condition else else_ 5802 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5803 return f"{condition}{insert}"
5805 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5806 kind = self.sql(expression, "kind") 5807 source = self.sql(expression, "source") 5808 5809 if kind: 5810 expressions = self.seg(self.expressions(expression, sep=" ")) 5811 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5812 else: 5813 # If kind isn't present, we have Hive's FROM-first form: 5814 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5815 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5816 sql = f"FROM {source}{self.seg(inserts)}" 5817 5818 return self.prepend_ctes(expression, sql)
5820 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5821 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5822 empty = expression.args.get("empty") 5823 empty = ( 5824 f"DEFAULT {empty} ON EMPTY" 5825 if isinstance(empty, exp.Expr) 5826 else self.sql(expression, "empty") 5827 ) 5828 5829 error = expression.args.get("error") 5830 error = ( 5831 f"DEFAULT {error} ON ERROR" 5832 if isinstance(error, exp.Expr) 5833 else self.sql(expression, "error") 5834 ) 5835 5836 if error and empty: 5837 error = ( 5838 f"{empty} {error}" 5839 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5840 else f"{error} {empty}" 5841 ) 5842 empty = "" 5843 5844 null = self.sql(expression, "null") 5845 5846 return f"{empty}{error}{null}"
5852 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5853 this = self.sql(expression, "this") 5854 path = self.sql(expression, "path") 5855 5856 passing = self.expressions(expression, "passing") 5857 passing = f" PASSING {passing}" if passing else "" 5858 5859 on_condition = self.sql(expression, "on_condition") 5860 on_condition = f" {on_condition}" if on_condition else "" 5861 5862 path = f"{path}{passing}{on_condition}" 5863 5864 return self.func("JSON_EXISTS", this, path)
5906 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5907 array_agg = self.function_fallback_sql(expression) 5908 column_expr = expression.this 5909 if isinstance(column_expr, exp.Order): 5910 column_expr = column_expr.this 5911 5912 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5993 def overlay_sql(self, expression: exp.Overlay) -> str: 5994 this = self.sql(expression, "this") 5995 expr = self.sql(expression, "expression") 5996 from_sql = self.sql(expression, "from_") 5997 for_sql = self.sql(expression, "for_") 5998 for_sql = f" FOR {for_sql}" if for_sql else "" 5999 6000 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
6007 def string_sql(self, expression: exp.String) -> str: 6008 this = expression.this 6009 zone = expression.args.get("zone") 6010 6011 if zone: 6012 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6013 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6014 # set for source_tz to transpile the time conversion before the STRING cast 6015 this = exp.ConvertTimezone( 6016 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6017 ) 6018 6019 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
6029 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6030 filler = self.sql(expression, "this") 6031 filler = f" {filler}" if filler else "" 6032 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6033 return f"TRUNCATE{filler} {with_count}"
6035 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6036 if self.SUPPORTS_UNIX_SECONDS: 6037 return self.function_fallback_sql(expression) 6038 6039 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6040 6041 return self.sql( 6042 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6043 )
6045 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6046 dim = expression.expression 6047 6048 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6049 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6050 if not (dim.is_int and dim.name == "1"): 6051 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6052 dim = None 6053 6054 # If dimension is required but not specified, default initialize it 6055 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6056 dim = exp.Literal.number(1) 6057 6058 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6060 def attach_sql(self, expression: exp.Attach) -> str: 6061 this = self.sql(expression, "this") 6062 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6063 expressions = self.expressions(expression) 6064 expressions = f" ({expressions})" if expressions else "" 6065 6066 return f"ATTACH{exists_sql} {this}{expressions}"
6068 def detach_sql(self, expression: exp.Detach) -> str: 6069 kind = self.sql(expression, "kind") 6070 kind = f" {kind}" if kind else "" 6071 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6072 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6073 exists = " IF EXISTS" if expression.args.get("exists") else "" 6074 if exists: 6075 kind = kind or " DATABASE" 6076 6077 this = self.sql(expression, "this") 6078 this = f" {this}" if this else "" 6079 cluster = self.sql(expression, "cluster") 6080 cluster = f" {cluster}" if cluster else "" 6081 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6082 sync = " SYNC" if expression.args.get("sync") else "" 6083 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
6096 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6097 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6098 encode = f"{encode} {self.sql(expression, 'this')}" 6099 6100 properties = expression.args.get("properties") 6101 if properties: 6102 encode = f"{encode} {self.properties(properties)}" 6103 6104 return encode
6106 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6107 this = self.sql(expression, "this") 6108 include = f"INCLUDE {this}" 6109 6110 column_def = self.sql(expression, "column_def") 6111 if column_def: 6112 include = f"{include} {column_def}" 6113 6114 alias = self.sql(expression, "alias") 6115 if alias: 6116 include = f"{include} AS {alias}" 6117 6118 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6131 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6132 partitions = self.expressions(expression, "partition_expressions") 6133 create = self.expressions(expression, "create_expressions") 6134 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6136 def partitionbyrangepropertydynamic_sql( 6137 self, expression: exp.PartitionByRangePropertyDynamic 6138 ) -> str: 6139 start = self.sql(expression, "start") 6140 end = self.sql(expression, "end") 6141 6142 every = expression.args["every"] 6143 if isinstance(every, exp.Interval) and every.this.is_string: 6144 every.this.replace(exp.Literal.number(every.name)) 6145 6146 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6159 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6160 kind = self.sql(expression, "kind") 6161 option = self.sql(expression, "option") 6162 option = f" {option}" if option else "" 6163 this = self.sql(expression, "this") 6164 this = f" {this}" if this else "" 6165 columns = self.expressions(expression) 6166 columns = f" {columns}" if columns else "" 6167 return f"{kind}{option} STATISTICS{this}{columns}"
6169 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6170 this = self.sql(expression, "this") 6171 columns = self.expressions(expression) 6172 inner_expression = self.sql(expression, "expression") 6173 inner_expression = f" {inner_expression}" if inner_expression else "" 6174 update_options = self.sql(expression, "update_options") 6175 update_options = f" {update_options} UPDATE" if update_options else "" 6176 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6187 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6188 kind = self.sql(expression, "kind") 6189 this = self.sql(expression, "this") 6190 this = f" {this}" if this else "" 6191 inner_expression = self.sql(expression, "expression") 6192 return f"VALIDATE {kind}{this}{inner_expression}"
6194 def analyze_sql(self, expression: exp.Analyze) -> str: 6195 options = self.expressions(expression, key="options", sep=" ") 6196 options = f" {options}" if options else "" 6197 kind = self.sql(expression, "kind") 6198 kind = f" {kind}" if kind else "" 6199 tables = self.expressions(expression, key="tables", flat=True) 6200 tables = f" {tables}" if tables else "" 6201 mode = self.sql(expression, "mode") 6202 mode = f" {mode}" if mode else "" 6203 properties = self.sql(expression, "properties") 6204 properties = f" {properties}" if properties else "" 6205 partition = self.sql(expression, "partition") 6206 partition = f" {partition}" if partition else "" 6207 inner_expression = self.sql(expression, "expression") 6208 inner_expression = f" {inner_expression}" if inner_expression else "" 6209 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6211 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6212 this = self.sql(expression, "this") 6213 namespaces = self.expressions(expression, key="namespaces") 6214 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6215 passing = self.expressions(expression, key="passing") 6216 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6217 columns = self.expressions(expression, key="columns") 6218 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6219 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6220 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6226 def export_sql(self, expression: exp.Export) -> str: 6227 this = self.sql(expression, "this") 6228 connection = self.sql(expression, "connection") 6229 connection = f"WITH CONNECTION {connection} " if connection else "" 6230 options = self.sql(expression, "options") 6231 return f"EXPORT DATA {connection}{options} AS {this}"
6237 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6238 variables = self.expressions(expression, "this") 6239 default = self.sql(expression, "default") 6240 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6241 6242 kind = self.sql(expression, "kind") 6243 if isinstance(expression.args.get("kind"), exp.Schema): 6244 kind = f"TABLE {kind}" 6245 6246 kind = f" {kind}" if kind else "" 6247 6248 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6250 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6251 kind = self.sql(expression, "kind") 6252 this = self.sql(expression, "this") 6253 set = self.sql(expression, "expression") 6254 to = self.sql(expression, "to") 6255 to = f" TO {to}" if to else "" 6256 default = self.sql(expression, "default") 6257 default = f" DEFAULT {default}" if default else "" 6258 using = self.sql(expression, "using") 6259 using = f" USING {using}" if using else "" 6260 6261 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6262 6263 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:
6291 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6292 # Snowflake GET/PUT statements: 6293 # PUT <file> <internalStage> <properties> 6294 # GET <internalStage> <file> <properties> 6295 props = expression.args.get("properties") 6296 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6297 this = self.sql(expression, "this") 6298 target = self.sql(expression, "target") 6299 6300 if isinstance(expression, exp.Put): 6301 return f"PUT {this} {target}{props_sql}" 6302 else: 6303 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6305 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6306 this = self.sql(expression, "this") 6307 expr = self.sql(expression, "expression") 6308 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6309 return f"TRANSLATE({this} USING {expr}{with_error})"
6311 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6312 if self.SUPPORTS_DECODE_CASE: 6313 return self.func("DECODE", *expression.expressions) 6314 6315 decode_expr, *expressions = expression.expressions 6316 6317 ifs = [] 6318 for search, result in zip(expressions[::2], expressions[1::2]): 6319 if isinstance(search, exp.Literal): 6320 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6321 elif isinstance(search, exp.Null): 6322 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6323 else: 6324 if isinstance(search, exp.Binary): 6325 search = exp.paren(search) 6326 6327 cond = exp.or_( 6328 decode_expr.eq(search), 6329 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6330 copy=False, 6331 ) 6332 ifs.append(exp.If(this=cond, true=result)) 6333 6334 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6335 return self.sql(case)
6337 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6338 this = self.sql(expression, "this") 6339 this = self.seg(this, sep="") 6340 dimensions = self.expressions( 6341 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6342 ) 6343 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6344 metrics = self.expressions( 6345 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6346 ) 6347 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6348 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6349 facts = self.seg(f"FACTS {facts}") if facts else "" 6350 where = self.sql(expression, "where") 6351 where = self.seg(f"WHERE {where}") if where else "" 6352 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6353 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6355 def getextract_sql(self, expression: exp.GetExtract) -> str: 6356 this = expression.this 6357 expr = expression.expression 6358 6359 if not this.type or not expression.type: 6360 import sqlglot.optimizer.annotate_types 6361 6362 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6363 6364 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6365 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6366 6367 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6384 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6385 method = self.sql(expression, "method") 6386 kind = expression.args.get("kind") 6387 if not kind: 6388 return f"REFRESH {method}" 6389 6390 every = self.sql(expression, "every") 6391 unit = self.sql(expression, "unit") 6392 every = f" EVERY {every} {unit}" if every else "" 6393 starts = self.sql(expression, "starts") 6394 starts = f" STARTS {starts}" if starts else "" 6395 6396 return f"REFRESH {method} ON {kind}{every}{starts}"
6405 def uuid_sql(self, expression: exp.Uuid) -> str: 6406 is_string = expression.args.get("is_string", False) 6407 uuid_func_sql = self.func("UUID") 6408 6409 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6410 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6411 6412 return uuid_func_sql
6414 def initcap_sql(self, expression: exp.Initcap) -> str: 6415 delimiters = expression.expression 6416 6417 if delimiters: 6418 # do not generate delimiters arg if we are round-tripping from default delimiters 6419 if ( 6420 delimiters.is_string 6421 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6422 ): 6423 delimiters = None 6424 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6425 self.unsupported("INITCAP does not support custom delimiters") 6426 delimiters = None 6427 6428 return self.func("INITCAP", expression.this, delimiters)
6438 def weekstart_name(self, expression: exp.WeekStart) -> str: 6439 import sqlglot.dialects.dialect 6440 6441 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6442 this = expression.this.name.upper() 6443 6444 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6445 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6446 6447 if dow_from_week_start_day != dow_from_week_offset: 6448 self.unsupported( 6449 f"WEEK({this}) is not supported; falling back to the default week start day" 6450 ) 6451 6452 return "WEEK"
6454 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6455 name = self.weekstart_name(expression) 6456 6457 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6458 if isinstance(expression.parent, exp.DateTrunc): 6459 return self.sql(exp.Literal.string(name)) 6460 6461 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: